From bd2dea9d64282063912eed4fea6e1380beed7699 Mon Sep 17 00:00:00 2001 From: eric Date: Mon, 5 Jan 2026 00:56:08 +0800 Subject: [PATCH] 2017-2025->2017-2026 --- README.md | 2 +- README_zh_CN.md | 2 +- codes/adt/include/AdtTree.h | 2 +- codes/array/include/HXArray.h | 2 +- codes/array/include/Marray.h | 2 +- codes/array/src/Marray.cpp | 2 +- codes/basic/include/Constant.h | 2 +- codes/basic/include/HXDefine.h | 2 +- codes/basic/include/HXMid.h | 2 +- codes/basic/include/HXPointer.h | 2 +- codes/basic/include/HXSort.h | 2 +- codes/basic/include/HXStd.h | 2 +- codes/basic/include/HXType.h | 2 +- codes/basic/include/HXTypeBasic.h | 2 +- codes/basic/include/HXVector.h | 2 +- codes/basic/src/HXDefine.cpp | 2 +- codes/basic/src/HXMid.cpp | 2 +- codes/cgns/include/CgnsBase.h | 2 +- codes/cgns/include/CgnsBaseUtil.h | 2 +- codes/cgns/include/CgnsBc1to1.h | 2 +- codes/cgns/include/CgnsBcBoco.h | 2 +- codes/cgns/include/CgnsBcConn.h | 2 +- codes/cgns/include/CgnsBcLink.h | 2 +- codes/cgns/include/CgnsCoor.h | 2 +- codes/cgns/include/CgnsFactory.h | 2 +- codes/cgns/include/CgnsFamilyBc.h | 2 +- codes/cgns/include/CgnsFile.h | 2 +- codes/cgns/include/CgnsGlobal.h | 2 +- codes/cgns/include/CgnsPeriod.h | 2 +- codes/cgns/include/CgnsSection.h | 2 +- codes/cgns/include/CgnsVariable.h | 2 +- codes/cgns/include/CgnsZbase.h | 2 +- codes/cgns/include/CgnsZbaseUtil.h | 2 +- codes/cgns/include/CgnsZbc.h | 2 +- codes/cgns/include/CgnsZbc1to1.h | 2 +- codes/cgns/include/CgnsZbcBoco.h | 2 +- codes/cgns/include/CgnsZbcConn.h | 2 +- codes/cgns/include/CgnsZone.h | 2 +- codes/cgns/include/CgnsZoneUtil.h | 2 +- codes/cgns/include/CgnsZsection.h | 2 +- codes/cgns/include/HXCgns.h | 2 +- codes/cgns/src/CgnsBase.cpp | 2 +- codes/cgns/src/CgnsBaseUtil.cpp | 2 +- codes/cgns/src/CgnsBc1to1.cpp | 2 +- codes/cgns/src/CgnsBcBoco.cpp | 2 +- codes/cgns/src/CgnsBcConn.cpp | 2 +- codes/cgns/src/CgnsBcLink.cpp | 2 +- codes/cgns/src/CgnsCoor.cpp | 2 +- codes/cgns/src/CgnsFactory.cpp | 2 +- codes/cgns/src/CgnsFamilyBc.cpp | 2 +- codes/cgns/src/CgnsFile.cpp | 2 +- codes/cgns/src/CgnsGlobal.cpp | 2 +- codes/cgns/src/CgnsPeriod.cpp | 2 +- codes/cgns/src/CgnsSection.cpp | 2 +- codes/cgns/src/CgnsVariable.cpp | 2 +- codes/cgns/src/CgnsZbase.cpp | 2 +- codes/cgns/src/CgnsZbaseUtil.cpp | 2 +- codes/cgns/src/CgnsZbc.cpp | 2 +- codes/cgns/src/CgnsZbc1to1.cpp | 2 +- codes/cgns/src/CgnsZbcBoco.cpp | 2 +- codes/cgns/src/CgnsZbcConn.cpp | 2 +- codes/cgns/src/CgnsZone.cpp | 2 +- codes/cgns/src/CgnsZoneUtil.cpp | 2 +- codes/cgns/src/CgnsZsection.cpp | 2 +- codes/cgns/src/HXCgns.cpp | 2 +- codes/chemical/include/BlotterCurve.h | 2 +- codes/chemical/include/Chemical.h | 2 +- codes/chemical/include/MolecularProperty.h | 2 +- codes/chemical/include/ReactionRate.h | 2 +- codes/chemical/include/SchmidtNumber.h | 2 +- codes/chemical/include/Stoichiometric.h | 2 +- codes/chemical/include/Thermodynamic.h | 2 +- codes/chemical/src/BlotterCurve.cpp | 2 +- codes/chemical/src/Chemical.cpp | 2 +- codes/chemical/src/MolecularProperty.cpp | 2 +- codes/chemical/src/ReactionRate.cpp | 2 +- codes/chemical/src/SchmidtNumber.cpp | 2 +- codes/chemical/src/Stoichiometric.cpp | 2 +- codes/chemical/src/Thermodynamic.cpp | 2 +- codes/cuda/include/HybridParallel.h | 2 +- codes/cuda/src/HybridParallel.cpp | 2 +- codes/database/include/DataBase.h | 2 +- codes/database/include/DataBaseIO.h | 2 +- codes/database/include/DataBaseType.h | 2 +- codes/database/include/DataBook.h | 2 +- codes/database/include/DataField.h | 2 +- codes/database/include/DataObject.h | 2 +- codes/database/include/DataPage.h | 2 +- codes/database/include/DataPara.h | 2 +- codes/database/include/DataPointer.h | 2 +- codes/database/include/DataStorage.h | 2 +- codes/database/src/DataBase.cpp | 2 +- codes/database/src/DataBaseIO.cpp | 2 +- codes/database/src/DataBaseType.cpp | 2 +- codes/database/src/DataBook.cpp | 2 +- codes/database/src/DataField.cpp | 2 +- codes/database/src/DataPage.cpp | 2 +- codes/database/src/DataPara.cpp | 2 +- codes/database/src/DataStorage.cpp | 2 +- codes/force/include/AeroForce.h | 2 +- codes/force/include/AeroForceTask.h | 2 +- codes/force/include/AeroForceTaskReg.h | 2 +- codes/force/include/Force.h | 2 +- codes/force/src/AeroForce.cpp | 2 +- codes/force/src/AeroForceTask.cpp | 2 +- codes/force/src/AeroForceTaskReg.cpp | 2 +- codes/force/src/Force.cpp | 2 +- codes/geometry/include/BcRecord.h | 2 +- codes/geometry/include/Boundary.h | 2 +- codes/geometry/include/CalcGrid.h | 2 +- codes/geometry/include/CellMesh.h | 2 +- codes/geometry/include/CellTopo.h | 2 +- codes/geometry/include/DomainInp.h | 2 +- codes/geometry/include/ElemFeature.h | 2 +- codes/geometry/include/ElementHome.h | 2 +- codes/geometry/include/FaceMesh.h | 2 +- codes/geometry/include/FaceSearch.h | 2 +- codes/geometry/include/FaceSolver.h | 2 +- codes/geometry/include/FaceTopo.h | 2 +- codes/geometry/include/Grid.h | 2 +- codes/geometry/include/GridDef.h | 2 +- codes/geometry/include/GridElem.h | 2 +- codes/geometry/include/GridFactory.h | 2 +- codes/geometry/include/GridMediator.h | 2 +- codes/geometry/include/GridPara.h | 2 +- codes/geometry/include/GridTask.h | 2 +- codes/geometry/include/IFaceLink.h | 2 +- codes/geometry/include/InterFace.h | 2 +- codes/geometry/include/NodeMesh.h | 2 +- codes/geometry/include/Plot3D.h | 2 +- codes/geometry/include/Point.h | 2 +- codes/geometry/include/PointFactory.h | 2 +- codes/geometry/include/PointSearch.h | 2 +- codes/geometry/include/SlipFace.h | 2 +- codes/geometry/include/StrGrid.h | 2 +- codes/geometry/include/StrRegion.h | 2 +- codes/geometry/include/Su2Grid.h | 2 +- codes/geometry/include/UnitElement.h | 2 +- codes/geometry/include/UnsGrid.h | 2 +- codes/geometry/include/Visual.h | 2 +- codes/geometry/include/WallDist.h | 2 +- codes/geometry/src/BcRecord.cpp | 2 +- codes/geometry/src/Boundary.cpp | 2 +- codes/geometry/src/CalcGrid.cpp | 2 +- codes/geometry/src/CellMesh.cpp | 2 +- codes/geometry/src/CellTopo.cpp | 2 +- codes/geometry/src/DomainInp.cpp | 2 +- codes/geometry/src/ElemFeature.cpp | 2 +- codes/geometry/src/ElementHome.cpp | 2 +- codes/geometry/src/FaceMesh.cpp | 2 +- codes/geometry/src/FaceSearch.cpp | 2 +- codes/geometry/src/FaceSolver.cpp | 2 +- codes/geometry/src/FaceTopo.cpp | 2 +- codes/geometry/src/Grid.cpp | 2 +- codes/geometry/src/GridDef.cpp | 2 +- codes/geometry/src/GridElem.cpp | 2 +- codes/geometry/src/GridFactory.cpp | 2 +- codes/geometry/src/GridMediator.cpp | 2 +- codes/geometry/src/GridPara.cpp | 2 +- codes/geometry/src/GridTask.cpp | 2 +- codes/geometry/src/IFaceLink.cpp | 2 +- codes/geometry/src/InterFace.cpp | 2 +- codes/geometry/src/NodeMesh.cpp | 2 +- codes/geometry/src/Plot3D.cpp | 2 +- codes/geometry/src/PointFactory.cpp | 2 +- codes/geometry/src/PointSearch.cpp | 2 +- codes/geometry/src/SlipFace.cpp | 2 +- codes/geometry/src/StrGrid.cpp | 2 +- codes/geometry/src/StrRegion.cpp | 2 +- codes/geometry/src/Su2Grid.cpp | 2 +- codes/geometry/src/UnitElement.cpp | 2 +- codes/geometry/src/UnsGrid.cpp | 2 +- codes/geometry/src/Visual.cpp | 2 +- codes/geometry/src/WallDist.cpp | 2 +- codes/global/include/Com.h | 2 +- codes/global/include/Ctrl.h | 2 +- codes/global/include/Dimension.h | 2 +- codes/global/include/DimensionImp.h | 2 +- codes/global/include/FieldAlloc.h | 2 +- codes/global/include/FieldBase.h | 2 +- codes/global/include/FieldImp.h | 2 +- codes/global/include/FieldRecord.h | 2 +- codes/global/include/FieldSimu.h | 2 +- codes/global/include/FileMap.h | 2 +- codes/global/include/Iteration.h | 2 +- codes/global/include/SimuDef.h | 2 +- codes/global/include/SolverDef.h | 2 +- codes/global/include/SolverName.h | 2 +- codes/global/include/SolverRegister.h | 2 +- codes/global/include/SolverTaskReg.h | 2 +- codes/global/include/System.h | 2 +- codes/global/include/Tolerence.h | 2 +- codes/global/src/Com.cpp | 2 +- codes/global/src/Ctrl.cpp | 2 +- codes/global/src/Dimension.cpp | 2 +- codes/global/src/DimensionImp.cpp | 2 +- codes/global/src/FieldAlloc.cpp | 2 +- codes/global/src/FieldBase.cpp | 2 +- codes/global/src/FieldImp.cpp | 2 +- codes/global/src/FieldRecord.cpp | 2 +- codes/global/src/FieldSimu.cpp | 2 +- codes/global/src/FileMap.cpp | 2 +- codes/global/src/Interation.cpp | 2 +- codes/global/src/SimuDef.cpp | 2 +- codes/global/src/SolverDef.cpp | 2 +- codes/global/src/SolverName.cpp | 2 +- codes/global/src/SolverRegister.cpp | 2 +- codes/global/src/SolverTaskReg.cpp | 2 +- codes/global/src/System.cpp | 2 +- codes/global/src/Tolerence.cpp | 2 +- codes/grad/include/Grad.h | 2 +- codes/grad/src/Grad.cpp | 2 +- codes/implicit/include/ImplicitTaskReg.h | 2 +- codes/implicit/include/Lusgs.h | 2 +- codes/implicit/src/ImplicitTaskReg.cpp | 2 +- codes/implicit/src/Lusgs.cpp | 2 +- codes/ins/include/INsBcSolver.h | 2 +- codes/ins/include/INsCom.h | 2 +- codes/ins/include/INsCtrl.h | 2 +- codes/ins/include/INsIdx.h | 2 +- codes/ins/include/INsInvterm.h | 2 +- codes/ins/include/INsLusgs.h | 2 +- codes/ins/include/INsRestart.h | 2 +- codes/ins/include/INsRhs.h | 2 +- codes/ins/include/INsSolver.h | 2 +- codes/ins/include/INsSolverImp.h | 2 +- codes/ins/include/INsSpectrum.h | 2 +- codes/ins/include/INsUnsteady.h | 2 +- codes/ins/include/INsUpdate.h | 2 +- codes/ins/include/INsVisterm.h | 2 +- codes/ins/src/INsBcSolver.cpp | 2 +- codes/ins/src/INsCom.cpp | 2 +- codes/ins/src/INsCtrl.cpp | 2 +- codes/ins/src/INsInvterm.cpp | 2 +- codes/ins/src/INsLusgs.cpp | 2 +- codes/ins/src/INsRestart.cpp | 2 +- codes/ins/src/INsRhs.cpp | 2 +- codes/ins/src/INsSolver.cpp | 2 +- codes/ins/src/INsSolverImp.cpp | 2 +- codes/ins/src/INsSpectrum.cpp | 2 +- codes/ins/src/INsUnsteady.cpp | 2 +- codes/ins/src/INsUpdate.cpp | 2 +- codes/ins/src/INsVisterm.cpp | 2 +- codes/interface/include/InterField.h | 2 +- codes/interface/include/InterfaceTaskReg.h | 2 +- codes/interface/src/InterField.cpp | 2 +- codes/interface/src/InterfaceTaskReg.cpp | 2 +- codes/io/include/CommentLine.h | 2 +- codes/io/include/FileIO.h | 2 +- codes/io/include/FileO.h | 2 +- codes/io/include/LogFile.h | 2 +- codes/io/include/PIO.h | 2 +- codes/io/include/ParaFile.h | 2 +- codes/io/include/StrUtil.h | 2 +- codes/io/include/Word.h | 2 +- codes/io/src/CommentLine.cpp | 2 +- codes/io/src/FileIO.cpp | 2 +- codes/io/src/FileO.cpp | 2 +- codes/io/src/LogFile.cpp | 2 +- codes/io/src/PIO.cpp | 2 +- codes/io/src/ParaFile.cpp | 2 +- codes/io/src/StrUtil.cpp | 2 +- codes/io/src/Word.cpp | 2 +- codes/lhs/include/Lhs.h | 2 +- codes/lhs/include/LhsTaskReg.h | 2 +- codes/lhs/src/Lhs.cpp | 2 +- codes/lhs/src/LhsTaskReg.cpp | 2 +- codes/main/include/SimpleSimu.h | 2 +- codes/main/include/SimuImp.h | 2 +- codes/main/include/Simulation.h | 2 +- codes/main/src/OneFlow.cpp | 2 +- codes/main/src/SimpleSimu.cpp | 2 +- codes/main/src/SimuImp.cpp | 2 +- codes/main/src/Simulation.cpp | 2 +- codes/math/include/HXMath.h | 2 +- codes/math/include/HXMathExt.h | 2 +- codes/math/src/HXMath.cpp | 2 +- codes/math/src/HXMathExt.cpp | 2 +- codes/model/include/FlowModel.h | 2 +- codes/model/include/Sutherland.h | 2 +- codes/model/src/FlowModel.cpp | 2 +- codes/model/src/Sutherland.cpp | 2 +- codes/multiarray/include/Memop.h | 2 +- codes/multiarray/include/Multiarray.h | 2 +- codes/multiarray/include/Range.h | 2 +- codes/multiarray/src/Memop.cpp | 2 +- codes/multiarray/src/Multiarray.cpp | 2 +- codes/multiarray/src/Range.cpp | 2 +- codes/multigrid/include/BgField.h | 2 +- codes/multigrid/include/BgGrid.h | 2 +- codes/multigrid/include/Multigrid.h | 2 +- codes/multigrid/include/MultigridTaskReg.h | 2 +- codes/multigrid/src/BgField.cpp | 2 +- codes/multigrid/src/BgGrid.cpp | 2 +- codes/multigrid/src/Multigrid.cpp | 2 +- codes/multigrid/src/MultigridTaskReg.cpp | 2 +- codes/ns/include/NsBcSolver.h | 2 +- codes/ns/include/NsCom.h | 2 +- codes/ns/include/NsCtrl.h | 2 +- codes/ns/include/NsIdx.h | 2 +- codes/ns/include/NsInvFlux.h | 2 +- codes/ns/include/NsLusgs.h | 2 +- codes/ns/include/NsRestart.h | 2 +- codes/ns/include/NsRhs.h | 2 +- codes/ns/include/NsSolver.h | 2 +- codes/ns/include/NsSolverImp.h | 2 +- codes/ns/include/NsSpectrum.h | 2 +- codes/ns/include/NsUnsteady.h | 2 +- codes/ns/include/NsUpdate.h | 2 +- codes/ns/include/NsVisFlux.h | 2 +- codes/ns/include/TimeStep.h | 2 +- codes/ns/src/NsBcSolver.cpp | 2 +- codes/ns/src/NsCom.cpp | 2 +- codes/ns/src/NsCtrl.cpp | 2 +- codes/ns/src/NsInvFlux.cpp | 2 +- codes/ns/src/NsLusgs.cpp | 2 +- codes/ns/src/NsRestart.cpp | 2 +- codes/ns/src/NsRhs.cpp | 2 +- codes/ns/src/NsSolver.cpp | 2 +- codes/ns/src/NsSolverImp.cpp | 2 +- codes/ns/src/NsSpectrum.cpp | 2 +- codes/ns/src/NsUnsteady.cpp | 2 +- codes/ns/src/NsUpdate.cpp | 2 +- codes/ns/src/NsVisFlux.cpp | 2 +- codes/ns/src/TimeStep.cpp | 2 +- codes/parallel/include/BasicParallel.h | 2 +- codes/parallel/include/Parallel.h | 2 +- codes/parallel/src/BasicParallel.cpp | 2 +- codes/parallel/src/Parallel.cpp | 2 +- codes/partition/include/Partition.h | 2 +- codes/partition/include/SmartGrid.h | 2 +- codes/partition/src/Partition.cpp | 2 +- codes/partition/src/SmartGrid.cpp | 2 +- codes/physics/include/Atmosphere.h | 2 +- codes/physics/src/Atmosphere.cpp | 2 +- codes/postprocess/include/PostProcess.h | 2 +- codes/postprocess/src/PostProcess.cpp | 2 +- codes/project/include/Configure.h | 2 +- codes/project/include/FileUtil.h | 2 +- codes/project/include/OStream.h | 2 +- codes/project/include/Prj.h | 2 +- codes/project/include/Stop.h | 2 +- codes/project/src/FileUtil.cpp | 2 +- codes/project/src/OStream.cpp | 2 +- codes/project/src/Prj.cpp | 2 +- codes/project/src/Stop.cpp | 2 +- codes/register/include/HXClone.h | 2 +- codes/register/include/Message.h | 2 +- codes/register/include/MsgMapImp.h | 2 +- codes/register/include/Register.h | 2 +- codes/register/include/RegisterUtil.h | 2 +- codes/register/src/HXClone.cpp | 2 +- codes/register/src/Message.cpp | 2 +- codes/register/src/MsgMapImp.cpp | 2 +- codes/register/src/Register.cpp | 2 +- codes/register/src/RegisterUtil.cpp | 2 +- codes/residual/include/Residual.h | 2 +- codes/residual/include/ResidualTask.h | 2 +- codes/residual/include/ResidualTaskReg.h | 2 +- codes/residual/src/Residual.cpp | 2 +- codes/residual/src/ResidualTask.cpp | 2 +- codes/residual/src/ResidualTaskReg.cpp | 2 +- codes/restart/include/Restart.h | 2 +- codes/restart/include/RestartTaskReg.h | 2 +- codes/restart/src/Restart.cpp | 2 +- codes/restart/src/RestartTaskReg.cpp | 2 +- codes/scalar/include/Blasius.h | 2 +- codes/scalar/include/FieldPara.h | 2 +- codes/scalar/include/FieldSolver.h | 2 +- codes/scalar/include/FieldSolverBasic.h | 2 +- codes/scalar/include/FieldSolverCuda.h | 2 +- codes/scalar/include/FieldSolverOpenMP.h | 2 +- codes/scalar/include/MetisGrid.h | 2 +- codes/scalar/include/Numpy.h | 2 +- codes/scalar/include/Scalar.h | 2 +- codes/scalar/include/ScalarAlloc.h | 2 +- codes/scalar/include/ScalarCgns.h | 2 +- codes/scalar/include/ScalarDataIO.h | 2 +- codes/scalar/include/ScalarField.h | 2 +- codes/scalar/include/ScalarFieldRecord.h | 2 +- codes/scalar/include/ScalarGrid.h | 2 +- codes/scalar/include/ScalarIFace.h | 2 +- codes/scalar/include/ScalarMetis.h | 2 +- codes/scalar/include/ScalarOrder.h | 2 +- codes/scalar/include/ScalarSolver.h | 2 +- codes/scalar/include/ScalarZone.h | 2 +- codes/scalar/include/SolverDevice.h | 2 +- codes/scalar/src/Blasius.cpp | 2 +- codes/scalar/src/FieldPara.cpp | 2 +- codes/scalar/src/FieldSolver.cpp | 2 +- codes/scalar/src/FieldSolverBasic.cpp | 2 +- codes/scalar/src/FieldSolverCuda.cpp | 2 +- codes/scalar/src/FieldSolverOpenMP.cpp | 2 +- codes/scalar/src/MetisGrid.cpp | 2 +- codes/scalar/src/Numpy.cpp | 2 +- codes/scalar/src/Scalar.cpp | 2 +- codes/scalar/src/ScalarAlloc.cpp | 2 +- codes/scalar/src/ScalarCgns.cpp | 2 +- codes/scalar/src/ScalarDataIO.cpp | 2 +- codes/scalar/src/ScalarField.cpp | 2 +- codes/scalar/src/ScalarFieldRecord.cpp | 2 +- codes/scalar/src/ScalarGrid.cpp | 2 +- codes/scalar/src/ScalarIFace.cpp | 2 +- codes/scalar/src/ScalarMetis.cpp | 2 +- codes/scalar/src/ScalarOrder.cpp | 2 +- codes/scalar/src/ScalarSolver.cpp | 2 +- codes/scalar/src/ScalarZone.cpp | 2 +- codes/solver/include/BcData.h | 2 +- codes/solver/include/BcSolver.h | 2 +- codes/solver/include/Converge.h | 2 +- codes/solver/include/FieldTaskReg.h | 2 +- codes/solver/include/FieldWrap.h | 2 +- codes/solver/include/Rhs.h | 2 +- codes/solver/include/Solver.h | 2 +- codes/solver/include/SolverImp.h | 2 +- codes/solver/include/SolverInfo.h | 2 +- codes/solver/include/SolverMap.h | 2 +- codes/solver/include/SolverRegData.h | 2 +- codes/solver/include/SolverState.h | 2 +- codes/solver/include/Stress.h | 2 +- codes/solver/include/TimeIntegral.h | 2 +- codes/solver/src/BcData.cpp | 2 +- codes/solver/src/BcSolver.cpp | 2 +- codes/solver/src/Converge.cpp | 2 +- codes/solver/src/FieldTaskReg.cpp | 2 +- codes/solver/src/FieldWrap.cpp | 2 +- codes/solver/src/Rhs.cpp | 2 +- codes/solver/src/Solver.cpp | 2 +- codes/solver/src/SolverImp.cpp | 2 +- codes/solver/src/SolverInfo.cpp | 2 +- codes/solver/src/SolverMap.cpp | 2 +- codes/solver/src/SolverRegData.cpp | 2 +- codes/solver/src/SolverState.cpp | 2 +- codes/solver/src/Stress.cpp | 2 +- codes/solver/src/TimeIntegral.cpp | 2 +- codes/special/include/BlkMesh.h | 2 +- codes/special/include/Block2D.h | 2 +- codes/special/include/Block3D.h | 2 +- codes/special/include/BlockElem.h | 2 +- codes/special/include/BlockFaceSolver.h | 2 +- codes/special/include/BlockMachine.h | 2 +- codes/special/include/CalcCoor.h | 2 +- codes/special/include/Cavity.h | 2 +- codes/special/include/CgnsTest.h | 2 +- codes/special/include/CgnsTestTmp.h | 2 +- codes/special/include/CircleInfo.h | 2 +- codes/special/include/CircleLineMesh.h | 2 +- codes/special/include/ClassicGrid.h | 2 +- codes/special/include/CurveInfo.h | 2 +- codes/special/include/CurveLine.h | 2 +- codes/special/include/CurveMachine.h | 2 +- codes/special/include/CurveMesh.h | 2 +- codes/special/include/Cylinder.h | 2 +- codes/special/include/DomainMachine.h | 2 +- codes/special/include/GridCreate.h | 2 +- codes/special/include/GridMachine.h | 2 +- codes/special/include/LineInfo.h | 2 +- codes/special/include/LineMachine.h | 2 +- codes/special/include/LineMesh.h | 2 +- codes/special/include/LineMeshImp.h | 2 +- codes/special/include/MDomain.h | 2 +- codes/special/include/MLine.h | 2 +- codes/special/include/Mesh.h | 2 +- codes/special/include/MpiTest.h | 2 +- codes/special/include/PointMachine.h | 2 +- codes/special/include/Rae2822.h | 2 +- codes/special/include/SDomain.h | 2 +- codes/special/include/SegmentCtrl.h | 2 +- codes/special/include/SimpleDomain.h | 2 +- codes/special/include/StrBcSetting.h | 2 +- codes/special/include/Transfinite.h | 2 +- codes/special/src/BlkMesh.cpp | 2 +- codes/special/src/Block2D.cpp | 2 +- codes/special/src/Block3D.cpp | 2 +- codes/special/src/BlockElem.cpp | 2 +- codes/special/src/BlockFaceSolver.cpp | 2 +- codes/special/src/BlockMachine.cpp | 2 +- codes/special/src/CalcCoor.cpp | 2 +- codes/special/src/Cavity.cpp | 2 +- codes/special/src/CgnsTest.cpp | 2 +- codes/special/src/CgnsTestTmp.cpp | 2 +- codes/special/src/CircleInfo.cpp | 2 +- codes/special/src/CircleLineMesh.cpp | 2 +- codes/special/src/ClassicGrid.cpp | 2 +- codes/special/src/CurveInfo.cpp | 2 +- codes/special/src/CurveLine.cpp | 2 +- codes/special/src/CurveMachine.cpp | 2 +- codes/special/src/CurveMesh.cpp | 2 +- codes/special/src/Cylinder.cpp | 2 +- codes/special/src/DomainMachine.cpp | 2 +- codes/special/src/GridCreate.cpp | 2 +- codes/special/src/GridMachine.cpp | 2 +- codes/special/src/LineInfo.cpp | 2 +- codes/special/src/LineMachine.cpp | 2 +- codes/special/src/LineMesh.cpp | 2 +- codes/special/src/LineMeshImp.cpp | 2 +- codes/special/src/MDomain.cpp | 2 +- codes/special/src/MLine.cpp | 2 +- codes/special/src/Mesh.cpp | 2 +- codes/special/src/MpiTest.cpp | 2 +- codes/special/src/PointMachine.cpp | 2 +- codes/special/src/Rae2822.cpp | 2 +- codes/special/src/SDomain.cpp | 2 +- codes/special/src/SegmentCtrl.cpp | 2 +- codes/special/src/SimpleDomain.cpp | 2 +- codes/special/src/StrBcSetting.cpp | 2 +- codes/special/src/Transfinite.cpp | 2 +- codes/task/include/ActionMap.h | 2 +- codes/task/include/ActionState.h | 2 +- codes/task/include/Category.h | 2 +- codes/task/include/CmxTask.h | 2 +- codes/task/include/Command.h | 2 +- codes/task/include/FileInfo.h | 2 +- codes/task/include/GridState.h | 2 +- codes/task/include/InterfaceTask.h | 2 +- codes/task/include/OversetTask.h | 2 +- codes/task/include/ReadTask.h | 2 +- codes/task/include/SimpleTask.h | 2 +- codes/task/include/State.h | 2 +- codes/task/include/Task.h | 2 +- codes/task/include/TaskCom.h | 2 +- codes/task/include/TaskImp.h | 2 +- codes/task/include/TaskRegister.h | 2 +- codes/task/include/TaskState.h | 2 +- codes/task/include/WriteTask.h | 2 +- codes/task/src/ActionMap.cpp | 2 +- codes/task/src/ActionState.cpp | 2 +- codes/task/src/Category.cpp | 2 +- codes/task/src/CmxTask.cpp | 2 +- codes/task/src/Command.cpp | 2 +- codes/task/src/FileInfo.cpp | 2 +- codes/task/src/GridState.cpp | 2 +- codes/task/src/InterfaceTask.cpp | 2 +- codes/task/src/OversetTask.cpp | 2 +- codes/task/src/ReadTask.cpp | 2 +- codes/task/src/SimpleTask.cpp | 2 +- codes/task/src/State.cpp | 2 +- codes/task/src/Task.cpp | 2 +- codes/task/src/TaskCom.cpp | 2 +- codes/task/src/TaskImp.cpp | 2 +- codes/task/src/TaskRegister.cpp | 2 +- codes/task/src/TaskState.cpp | 2 +- codes/task/src/WriteTask.cpp | 2 +- codes/test/include/JsonTest.h | 2 +- codes/test/include/Test.h | 2 +- codes/test/src/JsonTest.cpp | 2 +- codes/test/src/Test.cpp | 2 +- codes/theory/include/Sod.h | 2 +- codes/theory/include/Theory.h | 2 +- codes/theory/src/Sod.cpp | 2 +- codes/theory/src/Theory.cpp | 2 +- codes/time/include/TimeSpan.h | 2 +- codes/time/include/TimeTest.h | 2 +- codes/time/src/TimeSpan.cpp | 2 +- codes/time/src/TimeTest.cpp | 2 +- codes/turb/include/TurbBcSolver.h | 2 +- codes/turb/include/TurbCom.h | 2 +- codes/turb/include/TurbCtrl.h | 2 +- codes/turb/include/TurbInvFlux.h | 2 +- codes/turb/include/TurbLusgs.h | 2 +- codes/turb/include/TurbRestart.h | 2 +- codes/turb/include/TurbRhs.h | 2 +- codes/turb/include/TurbSolver.h | 2 +- codes/turb/include/TurbSolverImp.h | 2 +- codes/turb/include/TurbSpectrum.h | 2 +- codes/turb/include/TurbSrcFlux.h | 2 +- codes/turb/include/TurbTrans.h | 2 +- codes/turb/include/TurbUnsteady.h | 2 +- codes/turb/include/TurbUpdate.h | 2 +- codes/turb/include/TurbVisFlux.h | 2 +- codes/turb/src/TurbBcSolver.cpp | 2 +- codes/turb/src/TurbCom.cpp | 2 +- codes/turb/src/TurbCtrl.cpp | 2 +- codes/turb/src/TurbInvFlux.cpp | 2 +- codes/turb/src/TurbLusgs.cpp | 2 +- codes/turb/src/TurbRestart.cpp | 2 +- codes/turb/src/TurbRhs.cpp | 2 +- codes/turb/src/TurbSolver.cpp | 2 +- codes/turb/src/TurbSolverImp.cpp | 2 +- codes/turb/src/TurbSpectrum.cpp | 2 +- codes/turb/src/TurbSrcFlux.cpp | 2 +- codes/turb/src/TurbTrans.cpp | 2 +- codes/turb/src/TurbUnsteady.cpp | 2 +- codes/turb/src/TurbUpdate.cpp | 2 +- codes/turb/src/TurbVisFlux.cpp | 2 +- codes/uins/include/UINsBcSolver.h | 2 +- codes/uins/include/UINsCom.h | 2 +- codes/uins/include/UINsGrad.h | 2 +- codes/uins/include/UINsInvterm.h | 2 +- codes/uins/include/UINsLimiter.h | 2 +- codes/uins/include/UINsLusgs.h | 2 +- codes/uins/include/UINsRestart.h | 2 +- codes/uins/include/UINsSolver.h | 2 +- codes/uins/include/UINsSpectrum.h | 2 +- codes/uins/include/UINsUnsteady.h | 2 +- codes/uins/include/UINsUpdate.h | 2 +- codes/uins/include/UINsVisterm.h | 2 +- codes/uins/src/UINsBcSolver.cpp | 2 +- codes/uins/src/UINsCom.cpp | 2 +- codes/uins/src/UINsGrad.cpp | 2 +- codes/uins/src/UINsInvterm.cpp | 2 +- codes/uins/src/UINsLimiter.cpp | 2 +- codes/uins/src/UINsLusgs.cpp | 2 +- codes/uins/src/UINsRestart.cpp | 2 +- codes/uins/src/UINsSolver.cpp | 2 +- codes/uins/src/UINsSpectrum.cpp | 2 +- codes/uins/src/UINsUnsteady.cpp | 2 +- codes/uins/src/UINsUpdate.cpp | 2 +- codes/uins/src/UINsVisterm.cpp | 2 +- codes/uns/include/UNsBcSolver.h | 2 +- codes/uns/include/UNsCom.h | 2 +- codes/uns/include/UNsGrad.h | 2 +- codes/uns/include/UNsInvFlux.h | 2 +- codes/uns/include/UNsLimiter.h | 2 +- codes/uns/include/UNsLusgs.h | 2 +- codes/uns/include/UNsRestart.h | 2 +- codes/uns/include/UNsSolver.h | 2 +- codes/uns/include/UNsSpectrum.h | 2 +- codes/uns/include/UNsUnsteady.h | 2 +- codes/uns/include/UNsUpdate.h | 2 +- codes/uns/include/UNsVisFlux.h | 2 +- codes/uns/include/UTimeStep.h | 2 +- codes/uns/src/UNsBcSolver.cpp | 2 +- codes/uns/src/UNsCom.cpp | 2 +- codes/uns/src/UNsGrad.cpp | 2 +- codes/uns/src/UNsInvFlux.cpp | 2 +- codes/uns/src/UNsLimiter.cpp | 2 +- codes/uns/src/UNsLusgs.cpp | 2 +- codes/uns/src/UNsRestart.cpp | 2 +- codes/uns/src/UNsSolver.cpp | 2 +- codes/uns/src/UNsSpectrum.cpp | 2 +- codes/uns/src/UNsUnsteady.cpp | 2 +- codes/uns/src/UNsUpdate.cpp | 2 +- codes/uns/src/UNsVisFlux.cpp | 2 +- codes/uns/src/UTimeStep.cpp | 2 +- codes/unsteady/include/Unsteady.h | 2 +- codes/unsteady/include/UnsteadyImp.h | 2 +- codes/unsteady/include/UnsteadyTaskReg.h | 2 +- codes/unsteady/include/UsdBasic.h | 2 +- codes/unsteady/include/UsdData.h | 2 +- codes/unsteady/include/UsdField.h | 2 +- codes/unsteady/include/UsdPara.h | 2 +- codes/unsteady/src/Unsteady.cpp | 2 +- codes/unsteady/src/UnsteadyImp.cpp | 2 +- codes/unsteady/src/UnsteadyTaskReg.cpp | 2 +- codes/unsteady/src/UsdBasic.cpp | 2 +- codes/unsteady/src/UsdData.cpp | 2 +- codes/unsteady/src/UsdField.cpp | 2 +- codes/unsteady/src/UsdPara.cpp | 2 +- codes/update/include/Update.h | 2 +- codes/update/include/UpdateTaskReg.h | 2 +- codes/update/src/Update.cpp | 2 +- codes/update/src/UpdateTaskReg.cpp | 2 +- codes/usolver/include/UBcSolver.h | 2 +- codes/usolver/include/UCom.h | 2 +- codes/usolver/include/UGrad.h | 2 +- codes/usolver/include/ULhs.h | 2 +- codes/usolver/include/ULimiter.h | 2 +- codes/usolver/include/UResidual.h | 2 +- codes/usolver/include/UUnsteady.h | 2 +- codes/usolver/include/UVisualize.h | 2 +- codes/usolver/include/VisGrad.h | 2 +- codes/usolver/src/UBcSolver.cpp | 2 +- codes/usolver/src/UCom.cpp | 2 +- codes/usolver/src/UGrad.cpp | 2 +- codes/usolver/src/ULhs.cpp | 2 +- codes/usolver/src/ULimiter.cpp | 2 +- codes/usolver/src/UResidual.cpp | 2 +- codes/usolver/src/UUnsteady.cpp | 2 +- codes/usolver/src/UVisualize.cpp | 2 +- codes/usolver/src/VisGrad.cpp | 2 +- codes/uturb/include/UTurbBcSolver.h | 2 +- codes/uturb/include/UTurbCom.h | 2 +- codes/uturb/include/UTurbGrad.h | 2 +- codes/uturb/include/UTurbInvFlux.h | 2 +- codes/uturb/include/UTurbLimiter.h | 2 +- codes/uturb/include/UTurbLusgs.h | 2 +- codes/uturb/include/UTurbRestart.h | 2 +- codes/uturb/include/UTurbSolver.h | 2 +- codes/uturb/include/UTurbSpectrum.h | 2 +- codes/uturb/include/UTurbSrcFlux.h | 2 +- codes/uturb/include/UTurbUnsteady.h | 2 +- codes/uturb/include/UTurbUpdate.h | 2 +- codes/uturb/include/UTurbVisFlux.h | 2 +- codes/uturb/src/UTurbBcSolver.cpp | 2 +- codes/uturb/src/UTurbCom.cpp | 2 +- codes/uturb/src/UTurbGrad.cpp | 2 +- codes/uturb/src/UTurbInvFlux.cpp | 2 +- codes/uturb/src/UTurbLimiter.cpp | 2 +- codes/uturb/src/UTurbLusgs.cpp | 2 +- codes/uturb/src/UTurbRestart.cpp | 2 +- codes/uturb/src/UTurbSolver.cpp | 2 +- codes/uturb/src/UTurbSpectrum.cpp | 2 +- codes/uturb/src/UTurbSrcFlux.cpp | 2 +- codes/uturb/src/UTurbUnsteady.cpp | 2 +- codes/uturb/src/UTurbUpdate.cpp | 2 +- codes/uturb/src/UTurbVisFlux.cpp | 2 +- codes/visual/include/FaceJoint.h | 2 +- codes/visual/include/HeatFlux.h | 2 +- codes/visual/include/HeatFluxTask.h | 2 +- codes/visual/include/HeatFluxTaskReg.h | 2 +- codes/visual/include/LaminarPlate.h | 2 +- codes/visual/include/NodeField.h | 2 +- codes/visual/include/Plate.h | 2 +- codes/visual/include/TurbPlate.h | 2 +- codes/visual/include/VisualTaskReg.h | 2 +- codes/visual/include/Visualize.h | 2 +- codes/visual/include/WallVisual.h | 2 +- codes/visual/src/FaceJoint.cpp | 2 +- codes/visual/src/HeatFlux.cpp | 2 +- codes/visual/src/HeatFluxTask.cpp | 2 +- codes/visual/src/HeatFluxTaskReg.cpp | 2 +- codes/visual/src/LaminarPlate.cpp | 2 +- codes/visual/src/NodeField.cpp | 2 +- codes/visual/src/Plate.cpp | 2 +- codes/visual/src/TurbPlate.cpp | 2 +- codes/visual/src/VisualTaskReg.cpp | 2 +- codes/visual/src/Visualize.cpp | 2 +- codes/visual/src/WallVisual.cpp | 2 +- codes/zone/include/GridGroup.h | 2 +- codes/zone/include/MultiBlock.h | 2 +- codes/zone/include/Zone.h | 2 +- codes/zone/include/ZoneState.h | 2 +- codes/zone/src/GridGroup.cpp | 2 +- codes/zone/src/MultiBlock.cpp | 2 +- codes/zone/src/Zone.cpp | 2 +- codes/zone/src/ZoneState.cpp | 2 +- .../fortran/FortranLibTest/01/CMakeLists.txt | 15 + .../FortranLibTest/01/src/CMakeLists.txt | 2 + .../fortran/FortranLibTest/01/src/mymath.f90 | 24 + .../FortranLibTest/01/tests/CMakeLists.txt | 7 + .../FortranLibTest/01/tests/test_mymath.f90 | 33 + .../fortran/FortranLibTest/01a/CMakeLists.txt | 15 + .../FortranLibTest/01a/src/CMakeLists.txt | 2 + .../fortran/FortranLibTest/01a/src/mymath.f90 | 21 + .../FortranLibTest/01a/tests/CMakeLists.txt | 7 + .../FortranLibTest/01a/tests/test_mymath.f90 | 33 + .../weno3/fortran/cfd/01/CMakeLists.txt | 27 + .../weno3/fortran/cfd/01/cfd_solver.f90 | 900 +++++++++++++++++ .../weno3/fortran/cfd/01/postprocess.py | 38 + .../weno3/fortran/cfd/01a/CMakeLists.txt | 27 + .../weno3/fortran/cfd/01a/cfd_solver.f90 | 873 ++++++++++++++++ .../weno3/fortran/cfd/01a/postprocess.py | 38 + .../weno3/fortran/cfd/01b/CMakeLists.txt | 22 + .../weno3/fortran/cfd/01b/cfd_solver.f90 | 861 ++++++++++++++++ .../weno3/fortran/cfd/01b/postprocess.py | 52 + .../weno3/fortran/cfd/01c/CMakeLists.txt | 22 + .../weno3/fortran/cfd/01c/cfd_solver.f90 | 939 +++++++++++++++++ .../weno3/fortran/cfd/01c/postprocess.py | 52 + .../weno3/fortran/cfd/01d/CMakeLists.txt | 22 + .../weno3/fortran/cfd/01d/cfd_solver.f90 | 941 +++++++++++++++++ .../weno3/fortran/cfd/01d/postprocess.py | 52 + .../weno3/fortran/cfd/01e/CMakeLists.txt | 22 + .../weno3/fortran/cfd/01e/cfd_solver.f90 | 941 +++++++++++++++++ .../weno3/fortran/cfd/01e/postprocess.py | 52 + .../weno3/fortran/cfd/01f/CMakeLists.txt | 22 + .../weno3/fortran/cfd/01f/README.md | 26 + .../weno3/fortran/cfd/01f/cfd_solver.f90 | 943 ++++++++++++++++++ .../weno3/fortran/cfd/01f/postprocess.py | 52 + .../weno3/fortran/cfd/01f/run_all.py | 482 +++++++++ .../weno3/fortran/cfd/01g/CMakeLists.txt | 20 + .../weno3/fortran/cfd/01g/README.md | 26 + .../weno3/fortran/cfd/01g/cfd_solver.f90 | 937 +++++++++++++++++ .../weno3/fortran/cfd/01g/postprocess.py | 52 + .../weno3/fortran/cfd/01g/run_all.py | 482 +++++++++ .../weno3/fortran/grammar/01/CMakeLists.txt | 24 + .../weno3/fortran/grammar/01/main.f90 | 3 + .../weno3/fortran/grammar/01a/CMakeLists.txt | 24 + .../weno3/fortran/grammar/01a/main.f90 | 22 + .../weno3/fortran/grammar/01b/CMakeLists.txt | 26 + .../weno3/fortran/grammar/01b/main.f90 | 7 + .../weno3/fortran/grammar/01b/sub1.f90 | 6 + .../weno3/fortran/grammar/01b/sub2.f90 | 6 + .../weno3/fortran/grammar/01c/CMakeLists.txt | 24 + .../weno3/fortran/grammar/01c/main.f90 | 33 + .../weno3/fortran/grammar/01d/CMakeLists.txt | 26 + .../weno3/fortran/grammar/01d/main.f90 | 9 + .../weno3/fortran/grammar/01d/mymod1.f90 | 11 + .../weno3/fortran/grammar/01d/mymod2.f90 | 11 + .../weno3/fortran/grammar/01e/CMakeLists.txt | 26 + .../weno3/fortran/grammar/01e/main.f90 | 9 + .../weno3/fortran/grammar/01e/src/mymod1.f90 | 11 + .../weno3/fortran/grammar/01e/src/mymod2.f90 | 11 + .../weno3/fortran/grammar/01f/CMakeLists.txt | 26 + .../weno3/fortran/grammar/01f/main.f90 | 9 + .../weno3/fortran/grammar/01f/src/mymod1.f90 | 11 + .../weno3/fortran/grammar/01f/src/mymod2.f90 | 11 + .../weno3/fortran/grammar/02/CMakeLists.txt | 26 + .../weno3/fortran/grammar/02/main.f90 | 7 + .../weno3/fortran/grammar/02/src/sub1.f90 | 6 + .../weno3/fortran/grammar/02/src/sub2.f90 | 6 + .../weno3/fortran/grammar/02a/CMakeLists.txt | 26 + .../weno3/fortran/grammar/02a/src/sub1.f90 | 6 + .../weno3/fortran/grammar/02a/src/sub2.f90 | 6 + .../weno3/fortran/grammar/02a/test/main.f90 | 7 + .../weno3/fortran/grammar/02b/CMakeLists.txt | 24 + .../weno3/fortran/grammar/02b/src/sub1.f90 | 6 + .../weno3/fortran/grammar/02b/src/sub2.f90 | 6 + .../fortran/grammar/02b/tests/CMakeLists.txt | 16 + .../weno3/fortran/grammar/02b/tests/main.f90 | 7 + .../weno3/fortran/grammar/02c/CMakeLists.txt | 15 + .../weno3/fortran/grammar/02c/src/sub1.f90 | 6 + .../weno3/fortran/grammar/02c/src/sub2.f90 | 6 + .../fortran/grammar/02c/test1/CMakeLists.txt | 16 + .../weno3/fortran/grammar/02c/test1/main1.f90 | 8 + .../fortran/grammar/02c/tests/CMakeLists.txt | 16 + .../weno3/fortran/grammar/02c/tests/main.f90 | 7 + .../weno3/fortran/grammar/02d/CMakeLists.txt | 16 + .../fortran/grammar/02d/src/CMakeLists.txt | 18 + .../weno3/fortran/grammar/02d/src/sub1.f90 | 6 + .../weno3/fortran/grammar/02d/src/sub2.f90 | 6 + .../fortran/grammar/02d/tests/CMakeLists.txt | 23 + .../weno3/fortran/grammar/02d/tests/main.f90 | 7 + .../weno3/fortran/grammar/02e/CMakeLists.txt | 16 + .../fortran/grammar/02e/src/CMakeLists.txt | 18 + .../weno3/fortran/grammar/02e/src/sub1.f90 | 6 + .../weno3/fortran/grammar/02e/src/sub2.f90 | 6 + .../fortran/grammar/02e/tests/CMakeLists.txt | 19 + .../weno3/fortran/grammar/02e/tests/main.f90 | 7 + .../weno3/fortran/grammar/02f/CMakeLists.txt | 20 + .../fortran/grammar/02f/src/CMakeLists.txt | 18 + .../weno3/fortran/grammar/02f/src/sub1.f90 | 6 + .../weno3/fortran/grammar/02f/src/sub2.f90 | 6 + .../fortran/grammar/02f/tests/CMakeLists.txt | 19 + .../weno3/fortran/grammar/02f/tests/main.f90 | 7 + .../weno3/fortran/grammar/02g/CMakeLists.txt | 21 + .../fortran/grammar/02g/src/CMakeLists.txt | 18 + .../weno3/fortran/grammar/02g/src/sub1.f90 | 6 + .../weno3/fortran/grammar/02g/src/sub2.f90 | 6 + .../fortran/grammar/02g/tests/CMakeLists.txt | 24 + .../weno3/fortran/grammar/02g/tests/main.f90 | 7 + .../weno3/fortran/grammar/02h/CMakeLists.txt | 21 + .../fortran/grammar/02h/src/CMakeLists.txt | 22 + .../weno3/fortran/grammar/02h/src/sub1.f90 | 6 + .../weno3/fortran/grammar/02h/src/sub2.f90 | 6 + .../fortran/grammar/02h/tests/CMakeLists.txt | 24 + .../weno3/fortran/grammar/02h/tests/main.f90 | 7 + .../weno3/fortran/grammar/03/CMakeLists.txt | 38 + .../weno3/fortran/grammar/03/main.f90 | 33 + .../weno3/fortran/grammar/03a/CMakeLists.txt | 40 + .../weno3/fortran/grammar/03a/main.f90 | 9 + .../weno3/fortran/grammar/03a/mymod1.f90 | 11 + .../weno3/fortran/grammar/03a/mymod2.f90 | 11 + .../weno3/fortran/grammar/03b/CMakeLists.txt | 40 + .../weno3/fortran/grammar/03b/main.f90 | 9 + .../weno3/fortran/grammar/03b/src/mymod1.f90 | 11 + .../weno3/fortran/grammar/03b/src/mymod2.f90 | 11 + .../weno3/fortran/grammar/03c/CMakeLists.txt | 47 + .../weno3/fortran/grammar/03c/main.f90 | 9 + .../fortran/grammar/03c/src/CMakeLists.txt | 22 + .../weno3/fortran/grammar/03c/src/mymod1.f90 | 11 + .../weno3/fortran/grammar/03c/src/mymod2.f90 | 11 + .../weno3/fortran/grammar/03d/CMakeLists.txt | 23 + .../fortran/grammar/03d/src/CMakeLists.txt | 22 + .../weno3/fortran/grammar/03d/src/mymod1.f90 | 11 + .../weno3/fortran/grammar/03d/src/mymod2.f90 | 11 + .../fortran/grammar/03d/tests/CMakeLists.txt | 23 + .../weno3/fortran/grammar/03d/tests/main.f90 | 9 + .../weno3/fortran/grammar/03e/CMakeLists.txt | 23 + .../fortran/grammar/03e/src/CMakeLists.txt | 21 + .../weno3/fortran/grammar/03e/src/mymath.f90 | 10 + .../fortran/grammar/03e/tests/CMakeLists.txt | 23 + .../weno3/fortran/grammar/03e/tests/main.f90 | 33 + .../weno3/fortran/grammar/03f/CMakeLists.txt | 23 + .../fortran/grammar/03f/src/CMakeLists.txt | 21 + .../weno3/fortran/grammar/03f/src/mymath.f90 | 24 + .../fortran/grammar/03f/tests/CMakeLists.txt | 23 + .../weno3/fortran/grammar/03f/tests/main.f90 | 33 + .../weno3/fortran/registry/01/CMakeLists.txt | 26 + .../fortran/registry/01/src/CMakeLists.txt | 19 + .../registry/01/src/core/CMakeLists.txt | 16 + .../fortran/registry/01/src/core/registry.f90 | 187 ++++ .../fortran/registry/01/tests/CMakeLists.txt | 15 + .../registry/01/tests/test_registry.f90 | 65 ++ .../weno3/fortran/registry/01a/CMakeLists.txt | 26 + .../fortran/registry/01a/src/CMakeLists.txt | 19 + .../registry/01a/src/core/CMakeLists.txt | 16 + .../registry/01a/src/core/registry.f90 | 241 +++++ .../fortran/registry/01a/tests/CMakeLists.txt | 15 + .../registry/01a/tests/test_registry.f90 | 75 ++ .../weno3/fortran/registry/01b/CMakeLists.txt | 50 + .../fortran/registry/01b/src/CMakeLists.txt | 14 + .../registry/01b/src/core/CMakeLists.txt | 24 + .../01b/src/core/factory_interfaces.f90 | 23 + .../registry/01b/src/core/registry.f90 | 423 ++++++++ .../01b/src/core/registry_advanced.f90 | 215 ++++ .../01b/src/infrastructure/CMakeLists.txt | 20 + .../01b/src/infrastructure/config.f90 | 117 +++ .../registry/01b/src/infrastructure/mesh.f90 | 74 ++ .../registry/01b/src/numerics/CMakeLists.txt | 7 + .../01b/src/numerics/flux/CMakeLists.txt | 21 + .../registry/01b/src/numerics/flux/base.f90 | 35 + .../01b/src/numerics/flux/factory.f90 | 47 + .../01b/src/numerics/flux/rusanov.f90 | 70 ++ .../src/numerics/reconstructor/CMakeLists.txt | 23 + .../01b/src/numerics/reconstructor/base.f90 | 40 + .../01b/src/numerics/reconstructor/eno.f90 | 111 +++ .../src/numerics/reconstructor/eno_type.f90 | 59 ++ .../src/numerics/reconstructor/factory.f90 | 47 + .../01b/src/numerics/reconstructor/weno3.f90 | 88 ++ .../01b/src/numerics/reconstructor/weno5.f90 | 106 ++ .../fortran/registry/01b/tests/CMakeLists.txt | 27 + .../01b/tests/test_factory_system.f90 | 86 ++ .../registry/01b/tests/test_minimal.f90 | 77 ++ .../01b/tests/test_registry_basic.f90 | 68 ++ .../weno3/fortran/registry/01c/CMakeLists.txt | 50 + .../fortran/registry/01c/src/CMakeLists.txt | 14 + .../registry/01c/src/core/CMakeLists.txt | 24 + .../01c/src/core/factory_interfaces.f90 | 17 + .../registry/01c/src/core/registry.f90 | 311 ++++++ .../01c/src/infrastructure/CMakeLists.txt | 20 + .../01c/src/infrastructure/config.f90 | 98 ++ .../registry/01c/src/infrastructure/mesh.f90 | 74 ++ .../registry/01c/src/numerics/CMakeLists.txt | 7 + .../01c/src/numerics/flux/CMakeLists.txt | 20 + .../registry/01c/src/numerics/flux/base.f90 | 24 + .../src/numerics/reconstructor/CMakeLists.txt | 20 + .../01c/src/numerics/reconstructor/base.f90 | 27 + .../fortran/registry/01c/tests/CMakeLists.txt | 27 + .../registry/01c/tests/test_minimal.f90 | 108 ++ .../weno3/fortran/registry/01d/CMakeLists.txt | 50 + .../weno3/fortran/registry/01d/build.bat | 72 ++ .../weno3/fortran/registry/01d/build.ps1 | 137 +++ .../weno3/fortran/registry/01d/build.py | 234 +++++ .../fortran/registry/01d/build_config.yaml | 29 + .../fortran/registry/01d/build_with_config.py | 130 +++ .../fortran/registry/01d/clean_build.bat | 48 + .../fortran/registry/01d/src/CMakeLists.txt | 14 + .../registry/01d/src/core/CMakeLists.txt | 24 + .../01d/src/core/factory_interfaces.f90 | 17 + .../registry/01d/src/core/registry.f90 | 386 +++++++ .../01d/src/infrastructure/CMakeLists.txt | 20 + .../01d/src/infrastructure/config.f90 | 98 ++ .../registry/01d/src/infrastructure/mesh.f90 | 74 ++ .../registry/01d/src/numerics/CMakeLists.txt | 7 + .../01d/src/numerics/flux/CMakeLists.txt | 20 + .../registry/01d/src/numerics/flux/base.f90 | 35 + .../01d/src/numerics/flux/rusanov.f90 | 70 ++ .../src/numerics/reconstructor/CMakeLists.txt | 21 + .../01d/src/numerics/reconstructor/base.f90 | 38 + .../01d/src/numerics/reconstructor/eno.f90 | 70 ++ .../01d/src/numerics/reconstructor/weno3.f90 | 91 ++ .../fortran/registry/01d/tests/CMakeLists.txt | 45 + .../registry/01d/tests/test_factory.f90 | 154 +++ .../registry/01d/tests/test_minimal.f90 | 108 ++ .../01d/tests/test_minimal_simple.f90 | 108 ++ .../weno3/fortran/registry/01e/CMakeLists.txt | 43 + .../weno3/fortran/registry/01e/README.md | 221 ++++ .../fortran/registry/01e/scripts/build.bat | 26 + .../fortran/registry/01e/scripts/build.ps1 | 32 + .../fortran/registry/01e/scripts/build.py | 331 ++++++ .../registry/01e/scripts/requirements.txt | 2 + .../fortran/registry/01e/src/CMakeLists.txt | 14 + .../registry/01e/src/core/CMakeLists.txt | 24 + .../01e/src/core/factory_interfaces.f90 | 17 + .../registry/01e/src/core/registry.f90 | 318 ++++++ .../01e/src/infrastructure/CMakeLists.txt | 20 + .../01e/src/infrastructure/config.f90 | 90 ++ .../registry/01e/src/infrastructure/mesh.f90 | 74 ++ .../registry/01e/src/numerics/CMakeLists.txt | 7 + .../01e/src/numerics/flux/CMakeLists.txt | 20 + .../registry/01e/src/numerics/flux/base.f90 | 24 + .../01e/src/numerics/flux/rusanov.f90 | 25 + .../src/numerics/reconstructor/CMakeLists.txt | 20 + .../01e/src/numerics/reconstructor/base.f90 | 27 + .../01e/src/numerics/reconstructor/eno.f90 | 25 + .../01e/src/numerics/reconstructor/weno3.f90 | 25 + .../fortran/registry/01e/tests/CMakeLists.txt | 16 + .../registry/01e/tests/test_factory.f90 | 154 +++ .../registry/01e/tests/test_minimal.f90 | 108 ++ .../01e/tests/test_minimal_simple.f90 | 84 ++ .../weno3/fortran/registry/01f/CMakeLists.txt | 43 + .../weno3/fortran/registry/01f/README.md | 221 ++++ .../fortran/registry/01f/scripts/build.bat | 26 + .../fortran/registry/01f/scripts/build.ps1 | 32 + .../fortran/registry/01f/scripts/build.py | 355 +++++++ .../registry/01f/scripts/requirements.txt | 2 + .../fortran/registry/01f/src/CMakeLists.txt | 14 + .../registry/01f/src/core/CMakeLists.txt | 24 + .../01f/src/core/factory_interfaces.f90 | 17 + .../registry/01f/src/core/registry.f90 | 318 ++++++ .../01f/src/infrastructure/CMakeLists.txt | 20 + .../01f/src/infrastructure/config.f90 | 90 ++ .../registry/01f/src/infrastructure/mesh.f90 | 74 ++ .../registry/01f/src/numerics/CMakeLists.txt | 7 + .../01f/src/numerics/flux/CMakeLists.txt | 20 + .../registry/01f/src/numerics/flux/base.f90 | 24 + .../01f/src/numerics/flux/rusanov.f90 | 25 + .../src/numerics/reconstructor/CMakeLists.txt | 20 + .../01f/src/numerics/reconstructor/base.f90 | 27 + .../01f/src/numerics/reconstructor/eno.f90 | 25 + .../01f/src/numerics/reconstructor/weno3.f90 | 25 + .../fortran/registry/01f/tests/CMakeLists.txt | 16 + .../registry/01f/tests/test_factory.f90 | 154 +++ .../registry/01f/tests/test_minimal.f90 | 108 ++ .../01f/tests/test_minimal_simple.f90 | 84 ++ .../weno3/fortran/registry/01g/CMakeLists.txt | 19 + .../weno3/fortran/registry/01g/README.md | 221 ++++ .../fortran/registry/01g/scripts/build.bat | 26 + .../fortran/registry/01g/scripts/build.ps1 | 32 + .../fortran/registry/01g/scripts/build.py | 357 +++++++ .../registry/01g/scripts/requirements.txt | 2 + .../fortran/registry/01g/src/CMakeLists.txt | 14 + .../registry/01g/src/core/CMakeLists.txt | 24 + .../01g/src/core/factory_interfaces.f90 | 17 + .../registry/01g/src/core/registry.f90 | 318 ++++++ .../01g/src/infrastructure/CMakeLists.txt | 20 + .../01g/src/infrastructure/config.f90 | 90 ++ .../registry/01g/src/infrastructure/mesh.f90 | 74 ++ .../registry/01g/src/numerics/CMakeLists.txt | 7 + .../01g/src/numerics/flux/CMakeLists.txt | 20 + .../registry/01g/src/numerics/flux/base.f90 | 24 + .../01g/src/numerics/flux/rusanov.f90 | 48 + .../src/numerics/reconstructor/CMakeLists.txt | 20 + .../01g/src/numerics/reconstructor/base.f90 | 40 + .../01g/src/numerics/reconstructor/eno.f90 | 56 ++ .../01g/src/numerics/reconstructor/weno3.f90 | 55 + .../fortran/registry/01g/tests/CMakeLists.txt | 4 + .../registry/01g/tests/test_factory.f90 | 154 +++ .../01g/tests/test_factory_simple.f90 | 106 ++ .../registry/01g/tests/test_minimal.f90 | 108 ++ .../01g/tests/test_minimal_simple.f90 | 5 + .../01g/tests/test_minimal_simpleBAK.f90 | 84 ++ .../weno3/fortran/registry/01h/CMakeLists.txt | 19 + .../weno3/fortran/registry/01h/README.md | 221 ++++ .../fortran/registry/01h/scripts/build.bat | 26 + .../fortran/registry/01h/scripts/build.ps1 | 32 + .../fortran/registry/01h/scripts/build.py | 357 +++++++ .../registry/01h/scripts/requirements.txt | 2 + .../fortran/registry/01h/src/CMakeLists.txt | 14 + .../registry/01h/src/core/CMakeLists.txt | 24 + .../01h/src/core/factory_interfaces.f90 | 17 + .../registry/01h/src/core/registry.f90 | 318 ++++++ .../01h/src/infrastructure/CMakeLists.txt | 20 + .../01h/src/infrastructure/config.f90 | 90 ++ .../registry/01h/src/infrastructure/mesh.f90 | 74 ++ .../registry/01h/src/numerics/CMakeLists.txt | 7 + .../01h/src/numerics/flux/CMakeLists.txt | 20 + .../registry/01h/src/numerics/flux/base.f90 | 24 + .../01h/src/numerics/flux/rusanov.f90 | 48 + .../src/numerics/reconstructor/CMakeLists.txt | 20 + .../01h/src/numerics/reconstructor/base.f90 | 40 + .../01h/src/numerics/reconstructor/eno.f90 | 56 ++ .../01h/src/numerics/reconstructor/weno3.f90 | 55 + .../fortran/registry/01h/tests/CMakeLists.txt | 17 + .../registry/01h/tests/test_factory.f90 | 154 +++ .../01h/tests/test_factory_simple.f90 | 106 ++ .../registry/01h/tests/test_minimal.f90 | 108 ++ .../01h/tests/test_minimal_simple.f90 | 84 ++ .../01h/tests/test_minimal_simpleBAK.f90 | 84 ++ .../weno3/fortran/registry/01i/CMakeLists.txt | 19 + .../weno3/fortran/registry/01i/README.md | 221 ++++ .../fortran/registry/01i/scripts/build.bat | 26 + .../fortran/registry/01i/scripts/build.ps1 | 32 + .../fortran/registry/01i/scripts/build.py | 357 +++++++ .../registry/01i/scripts/requirements.txt | 2 + .../fortran/registry/01i/src/CMakeLists.txt | 14 + .../registry/01i/src/core/CMakeLists.txt | 24 + .../01i/src/core/factory_interfaces.f90 | 17 + .../registry/01i/src/core/registry.f90 | 318 ++++++ .../01i/src/infrastructure/CMakeLists.txt | 20 + .../01i/src/infrastructure/config.f90 | 90 ++ .../registry/01i/src/infrastructure/mesh.f90 | 74 ++ .../registry/01i/src/numerics/CMakeLists.txt | 7 + .../01i/src/numerics/flux/CMakeLists.txt | 28 + .../registry/01i/src/numerics/flux/base.f90 | 23 + .../01i/src/numerics/flux/rusanov.f90 | 46 + .../src/numerics/reconstructor/CMakeLists.txt | 22 + .../01i/src/numerics/reconstructor/base.f90 | 40 + .../01i/src/numerics/reconstructor/eno.f90 | 56 ++ .../01i/src/numerics/reconstructor/weno3.f90 | 55 + .../fortran/registry/01i/tests/CMakeLists.txt | 29 + .../registry/01i/tests/test_factory.f90 | 154 +++ .../01i/tests/test_factory_simple.f90 | 4 + .../01i/tests/test_factory_simpleBAK.f90 | 106 ++ .../registry/01i/tests/test_minimal.f90 | 108 ++ .../01i/tests/test_minimal_simple.f90 | 84 ++ .../01i/tests/test_minimal_simpleBAK.f90 | 84 ++ .../weno3/fortran/registry/01j/CMakeLists.txt | 19 + .../weno3/fortran/registry/01j/README.md | 221 ++++ .../fortran/registry/01j/scripts/build.bat | 26 + .../fortran/registry/01j/scripts/build.ps1 | 32 + .../fortran/registry/01j/scripts/build.py | 357 +++++++ .../registry/01j/scripts/requirements.txt | 2 + .../fortran/registry/01j/src/CMakeLists.txt | 14 + .../registry/01j/src/core/CMakeLists.txt | 24 + .../01j/src/core/factory_interfaces.f90 | 17 + .../registry/01j/src/core/registry.f90 | 318 ++++++ .../01j/src/infrastructure/CMakeLists.txt | 20 + .../01j/src/infrastructure/config.f90 | 90 ++ .../registry/01j/src/infrastructure/mesh.f90 | 74 ++ .../registry/01j/src/numerics/CMakeLists.txt | 7 + .../01j/src/numerics/flux/CMakeLists.txt | 28 + .../registry/01j/src/numerics/flux/base.f90 | 23 + .../01j/src/numerics/flux/rusanov.f90 | 37 + .../src/numerics/reconstructor/CMakeLists.txt | 22 + .../01j/src/numerics/reconstructor/base.f90 | 26 + .../01j/src/numerics/reconstructor/eno.f90 | 38 + .../01j/src/numerics/reconstructor/weno3.f90 | 55 + .../fortran/registry/01j/tests/CMakeLists.txt | 34 + .../registry/01j/tests/test_factory.f90 | 154 +++ .../01j/tests/test_factory_simple.f90 | 72 ++ .../01j/tests/test_factory_simpleBAK.f90 | 106 ++ .../registry/01j/tests/test_minimal.f90 | 108 ++ .../01j/tests/test_minimal_simple.f90 | 84 ++ .../01j/tests/test_minimal_simpleBAK.f90 | 84 ++ .../registry/01j/tests/test_simple_link.f90 | 108 ++ .../weno3/fortran/registry/01k/CMakeLists.txt | 19 + .../weno3/fortran/registry/01k/README.md | 221 ++++ .../fortran/registry/01k/scripts/build.bat | 26 + .../fortran/registry/01k/scripts/build.ps1 | 32 + .../fortran/registry/01k/scripts/build.py | 357 +++++++ .../registry/01k/scripts/requirements.txt | 2 + .../fortran/registry/01k/src/CMakeLists.txt | 14 + .../registry/01k/src/core/CMakeLists.txt | 24 + .../01k/src/core/factory_interfaces.f90 | 17 + .../registry/01k/src/core/registry.f90 | 318 ++++++ .../01k/src/infrastructure/CMakeLists.txt | 20 + .../01k/src/infrastructure/config.f90 | 90 ++ .../registry/01k/src/infrastructure/mesh.f90 | 74 ++ .../registry/01k/src/numerics/CMakeLists.txt | 7 + .../01k/src/numerics/flux/CMakeLists.txt | 28 + .../registry/01k/src/numerics/flux/base.f90 | 30 + .../01k/src/numerics/flux/rusanov.f90 | 41 + .../src/numerics/reconstructor/CMakeLists.txt | 22 + .../01k/src/numerics/reconstructor/base.f90 | 33 + .../01k/src/numerics/reconstructor/eno.f90 | 42 + .../01k/src/numerics/reconstructor/weno3.f90 | 42 + .../fortran/registry/01k/tests/CMakeLists.txt | 43 + .../registry/01k/tests/test_factory.f90 | 154 +++ .../01k/tests/test_factory_simple.f90 | 86 ++ .../registry/01k/tests/test_minimal.f90 | 108 ++ .../01k/tests/test_minimal_simple.f90 | 84 ++ .../registry/01k/tests/test_simple_link.f90 | 108 ++ .../weno3/fortran/registry/01l/CMakeLists.txt | 19 + .../weno3/fortran/registry/01l/README.md | 221 ++++ .../fortran/registry/01l/scripts/build.bat | 38 + .../fortran/registry/01l/scripts/build.py | 629 ++++++++++++ .../registry/01l/scripts/requirements.txt | 2 + .../fortran/registry/01l/src/CMakeLists.txt | 14 + .../registry/01l/src/core/CMakeLists.txt | 24 + .../01l/src/core/factory_interfaces.f90 | 17 + .../registry/01l/src/core/registry.f90 | 318 ++++++ .../01l/src/infrastructure/CMakeLists.txt | 20 + .../01l/src/infrastructure/config.f90 | 90 ++ .../registry/01l/src/infrastructure/mesh.f90 | 74 ++ .../registry/01l/src/numerics/CMakeLists.txt | 7 + .../01l/src/numerics/flux/CMakeLists.txt | 28 + .../registry/01l/src/numerics/flux/base.f90 | 30 + .../01l/src/numerics/flux/rusanov.f90 | 41 + .../src/numerics/reconstructor/CMakeLists.txt | 22 + .../01l/src/numerics/reconstructor/base.f90 | 33 + .../01l/src/numerics/reconstructor/eno.f90 | 42 + .../01l/src/numerics/reconstructor/weno3.f90 | 42 + .../fortran/registry/01l/tests/CMakeLists.txt | 43 + .../registry/01l/tests/test_factory.f90 | 154 +++ .../01l/tests/test_factory_simple.f90 | 86 ++ .../registry/01l/tests/test_minimal.f90 | 108 ++ .../01l/tests/test_minimal_simple.f90 | 84 ++ .../registry/01l/tests/test_simple_link.f90 | 108 ++ .../weno3/fortran/registry/01m/CMakeLists.txt | 19 + .../weno3/fortran/registry/01m/README.md | 221 ++++ .../fortran/registry/01m/scripts/build.bat | 38 + .../fortran/registry/01m/scripts/build.py | 629 ++++++++++++ .../registry/01m/scripts/requirements.txt | 2 + .../fortran/registry/01m/src/CMakeLists.txt | 14 + .../registry/01m/src/core/CMakeLists.txt | 24 + .../01m/src/core/factory_interfaces.f90 | 17 + .../registry/01m/src/core/registry.f90 | 318 ++++++ .../01m/src/infrastructure/CMakeLists.txt | 20 + .../01m/src/infrastructure/config.f90 | 90 ++ .../registry/01m/src/infrastructure/mesh.f90 | 74 ++ .../registry/01m/src/numerics/CMakeLists.txt | 7 + .../01m/src/numerics/flux/CMakeLists.txt | 28 + .../registry/01m/src/numerics/flux/base.f90 | 30 + .../01m/src/numerics/flux/rusanov.f90 | 41 + .../src/numerics/reconstructor/CMakeLists.txt | 22 + .../01m/src/numerics/reconstructor/base.f90 | 33 + .../01m/src/numerics/reconstructor/eno.f90 | 42 + .../01m/src/numerics/reconstructor/weno3.f90 | 42 + .../fortran/registry/01m/tests/CMakeLists.txt | 43 + .../01m/tests/test_factory_simple.f90 | 86 ++ .../01m/tests/test_minimal_simple.f90 | 84 ++ .../registry/01m/tests/test_simple_link.f90 | 108 ++ .../weno3/fortran/registry/02/CMakeLists.txt | 19 + .../weno3/fortran/registry/02/README.md | 221 ++++ .../fortran/registry/02/scripts/build.bat | 38 + .../fortran/registry/02/scripts/build.py | 629 ++++++++++++ .../registry/02/scripts/requirements.txt | 2 + .../fortran/registry/02/src/CMakeLists.txt | 14 + .../registry/02/src/core/CMakeLists.txt | 24 + .../02/src/core/factory_interfaces.f90 | 17 + .../fortran/registry/02/src/core/registry.f90 | 668 +++++++++++++ .../02/src/infrastructure/CMakeLists.txt | 20 + .../registry/02/src/infrastructure/config.f90 | 90 ++ .../registry/02/src/infrastructure/mesh.f90 | 74 ++ .../registry/02/src/numerics/CMakeLists.txt | 7 + .../02/src/numerics/flux/CMakeLists.txt | 28 + .../registry/02/src/numerics/flux/base.f90 | 30 + .../registry/02/src/numerics/flux/rusanov.f90 | 41 + .../src/numerics/reconstructor/CMakeLists.txt | 22 + .../02/src/numerics/reconstructor/base.f90 | 33 + .../02/src/numerics/reconstructor/eno.f90 | 42 + .../02/src/numerics/reconstructor/weno3.f90 | 42 + .../fortran/registry/02/tests/CMakeLists.txt | 43 + .../registry/02/tests/test_factory_simple.f90 | 86 ++ .../registry/02/tests/test_minimal_simple.f90 | 84 ++ .../registry/02/tests/test_simple_link.f90 | 108 ++ .../weno3/fortran/registry/02a/CMakeLists.txt | 19 + .../weno3/fortran/registry/02a/README.md | 221 ++++ .../fortran/registry/02a/scripts/build.bat | 38 + .../fortran/registry/02a/scripts/build.py | 629 ++++++++++++ .../registry/02a/scripts/requirements.txt | 2 + .../fortran/registry/02a/src/CMakeLists.txt | 14 + .../registry/02a/src/core/CMakeLists.txt | 24 + .../02a/src/core/factory_interfaces.f90 | 17 + .../registry/02a/src/core/registry.f90 | 656 ++++++++++++ .../02a/src/infrastructure/CMakeLists.txt | 20 + .../02a/src/infrastructure/config.f90 | 90 ++ .../registry/02a/src/infrastructure/mesh.f90 | 74 ++ .../registry/02a/src/numerics/CMakeLists.txt | 7 + .../02a/src/numerics/flux/CMakeLists.txt | 28 + .../registry/02a/src/numerics/flux/base.f90 | 30 + .../02a/src/numerics/flux/rusanov.f90 | 41 + .../src/numerics/reconstructor/CMakeLists.txt | 22 + .../02a/src/numerics/reconstructor/base.f90 | 33 + .../02a/src/numerics/reconstructor/eno.f90 | 42 + .../02a/src/numerics/reconstructor/weno3.f90 | 42 + .../fortran/registry/02a/tests/CMakeLists.txt | 43 + .../02a/tests/test_factory_simple.f90 | 86 ++ .../02a/tests/test_minimal_simple.f90 | 84 ++ .../registry/02a/tests/test_simple_link.f90 | 108 ++ .../weno3/fortran/registry/02b/CMakeLists.txt | 19 + .../weno3/fortran/registry/02b/README.md | 221 ++++ .../fortran/registry/02b/scripts/build.bat | 38 + .../fortran/registry/02b/scripts/build.py | 629 ++++++++++++ .../registry/02b/scripts/requirements.txt | 2 + .../fortran/registry/02b/src/CMakeLists.txt | 15 + .../registry/02b/src/core/CMakeLists.txt | 25 + .../02b/src/core/factory_interfaces.f90 | 17 + .../registry/02b/src/core/registry.f90 | 656 ++++++++++++ .../02b/src/infrastructure/CMakeLists.txt | 20 + .../02b/src/infrastructure/config.f90 | 90 ++ .../registry/02b/src/infrastructure/mesh.f90 | 74 ++ .../registry/02b/src/manager/CMakeLists.txt | 24 + .../02b/src/manager/component_factory.f90 | 127 +++ .../02b/src/manager/component_manager.f90 | 75 ++ .../registry/02b/src/numerics/CMakeLists.txt | 7 + .../02b/src/numerics/flux/CMakeLists.txt | 28 + .../registry/02b/src/numerics/flux/base.f90 | 30 + .../02b/src/numerics/flux/rusanov.f90 | 41 + .../src/numerics/reconstructor/CMakeLists.txt | 22 + .../02b/src/numerics/reconstructor/base.f90 | 33 + .../02b/src/numerics/reconstructor/eno.f90 | 42 + .../02b/src/numerics/reconstructor/weno3.f90 | 42 + .../fortran/registry/02b/tests/CMakeLists.txt | 42 + .../02b/tests/test_component_manager.f90 | 111 +++ .../02b/tests/test_factory_simple.f90 | 86 ++ .../02b/tests/test_minimal_simple.f90 | 84 ++ .../registry/02b/tests/test_simple_link.f90 | 108 ++ .../weno3/fortran/registry/02c/CMakeLists.txt | 19 + .../weno3/fortran/registry/02c/README.md | 221 ++++ .../fortran/registry/02c/scripts/build.bat | 38 + .../fortran/registry/02c/scripts/build.py | 629 ++++++++++++ .../registry/02c/scripts/requirements.txt | 2 + .../fortran/registry/02c/src/CMakeLists.txt | 15 + .../registry/02c/src/core/CMakeLists.txt | 25 + .../02c/src/core/factory_interfaces.f90 | 17 + .../registry/02c/src/core/registry.f90 | 656 ++++++++++++ .../02c/src/infrastructure/CMakeLists.txt | 20 + .../02c/src/infrastructure/config.f90 | 90 ++ .../registry/02c/src/infrastructure/mesh.f90 | 74 ++ .../registry/02c/src/manager/CMakeLists.txt | 24 + .../02c/src/manager/component_factory.f90 | 127 +++ .../02c/src/manager/component_manager.f90 | 75 ++ .../registry/02c/src/numerics/CMakeLists.txt | 7 + .../02c/src/numerics/flux/CMakeLists.txt | 28 + .../registry/02c/src/numerics/flux/base.f90 | 30 + .../02c/src/numerics/flux/rusanov.f90 | 41 + .../src/numerics/reconstructor/CMakeLists.txt | 22 + .../02c/src/numerics/reconstructor/base.f90 | 33 + .../02c/src/numerics/reconstructor/eno.f90 | 42 + .../02c/src/numerics/reconstructor/weno3.f90 | 42 + .../fortran/registry/02c/tests/CMakeLists.txt | 50 + .../registry/02c/tests/test_architecture.f90 | 117 +++ .../02c/tests/test_component_manager.f90 | 111 +++ .../02c/tests/test_factory_simple.f90 | 86 ++ .../02c/tests/test_minimal_simple.f90 | 84 ++ .../registry/02c/tests/test_simple_link.f90 | 108 ++ .../weno3/fortran/registry/02d/CMakeLists.txt | 19 + .../weno3/fortran/registry/02d/README.md | 221 ++++ .../fortran/registry/02d/scripts/build.bat | 38 + .../fortran/registry/02d/scripts/build.py | 629 ++++++++++++ .../registry/02d/scripts/requirements.txt | 2 + .../fortran/registry/02d/src/CMakeLists.txt | 16 + .../registry/02d/src/base/CMakeLists.txt | 15 + .../fortran/registry/02d/src/base/modules.f90 | 42 + .../registry/02d/src/core/CMakeLists.txt | 14 + .../registry/02d/src/core/factory_base.f90 | 57 ++ .../02d/src/core/factory_interfaces.f90 | 17 + .../registry/02d/src/core/registry.f90 | 203 ++++ .../02d/src/infrastructure/CMakeLists.txt | 15 + .../02d/src/infrastructure/config.f90 | 64 ++ .../02d/src/infrastructure/domain.f90 | 80 ++ .../registry/02d/src/infrastructure/mesh.f90 | 73 ++ .../02d/src/infrastructure/solution.f90 | 114 +++ .../registry/02d/src/manager/CMakeLists.txt | 24 + .../02d/src/manager/component_factory.f90 | 127 +++ .../02d/src/manager/component_manager.f90 | 75 ++ .../registry/02d/src/numerics/CMakeLists.txt | 7 + .../02d/src/numerics/flux/CMakeLists.txt | 28 + .../registry/02d/src/numerics/flux/base.f90 | 30 + .../02d/src/numerics/flux/rusanov.f90 | 41 + .../src/numerics/reconstructor/CMakeLists.txt | 22 + .../02d/src/numerics/reconstructor/base.f90 | 33 + .../02d/src/numerics/reconstructor/eno.f90 | 42 + .../02d/src/numerics/reconstructor/weno3.f90 | 42 + .../fortran/registry/02d/tests/CMakeLists.txt | 69 ++ .../registry/02d/tests/test_architecture.f90 | 117 +++ .../registry/02d/tests/test_basic_only.f90 | 56 ++ .../02d/tests/test_cfd_architecture.f90 | 117 +++ .../02d/tests/test_component_manager.f90 | 111 +++ .../02d/tests/test_factory_simple.f90 | 86 ++ .../02d/tests/test_minimal_simple.f90 | 84 ++ .../registry/02d/tests/test_simple_link.f90 | 108 ++ .../02d/tests/test_solver_framework.f90 | 91 ++ .../weno3/fortran/registry/02e/CMakeLists.txt | 19 + .../weno3/fortran/registry/02e/README.md | 221 ++++ .../fortran/registry/02e/scripts/build.bat | 38 + .../fortran/registry/02e/scripts/build.py | 629 ++++++++++++ .../registry/02e/scripts/requirements.txt | 2 + .../fortran/registry/02e/src/CMakeLists.txt | 16 + .../registry/02e/src/base/CMakeLists.txt | 15 + .../fortran/registry/02e/src/base/modules.f90 | 42 + .../registry/02e/src/core/CMakeLists.txt | 14 + .../registry/02e/src/core/factory_base.f90 | 57 ++ .../02e/src/core/factory_interfaces.f90 | 17 + .../registry/02e/src/core/registry.f90 | 203 ++++ .../02e/src/infrastructure/CMakeLists.txt | 15 + .../02e/src/infrastructure/config.f90 | 64 ++ .../02e/src/infrastructure/domain.f90 | 80 ++ .../registry/02e/src/infrastructure/mesh.f90 | 73 ++ .../02e/src/infrastructure/solution.f90 | 114 +++ .../registry/02e/src/manager/CMakeLists.txt | 24 + .../02e/src/manager/component_factory.f90 | 127 +++ .../02e/src/manager/component_manager.f90 | 75 ++ .../registry/02e/src/numerics/CMakeLists.txt | 7 + .../02e/src/numerics/flux/CMakeLists.txt | 28 + .../registry/02e/src/numerics/flux/base.f90 | 30 + .../02e/src/numerics/flux/rusanov.f90 | 41 + .../src/numerics/reconstructor/CMakeLists.txt | 22 + .../02e/src/numerics/reconstructor/base.f90 | 33 + .../02e/src/numerics/reconstructor/eno.f90 | 42 + .../02e/src/numerics/reconstructor/weno3.f90 | 42 + .../fortran/registry/02e/tests/CMakeLists.txt | 71 ++ .../registry/02e/tests/test_architecture.f90 | 117 +++ .../registry/02e/tests/test_basic_only.f90 | 56 ++ .../02e/tests/test_cfd_architecture.f90 | 117 +++ .../02e/tests/test_component_manager.f90 | 111 +++ .../02e/tests/test_factory_simple.f90 | 58 ++ .../02e/tests/test_minimal_simple.f90 | 87 ++ .../registry/02e/tests/test_simple_link.f90 | 78 ++ .../02e/tests/test_solver_framework.f90 | 91 ++ .../weno3/fortran/registry/02f/CMakeLists.txt | 19 + .../weno3/fortran/registry/02f/README.md | 221 ++++ .../fortran/registry/02f/scripts/build.bat | 38 + .../fortran/registry/02f/scripts/build.py | 629 ++++++++++++ .../registry/02f/scripts/requirements.txt | 2 + .../fortran/registry/02f/src/CMakeLists.txt | 16 + .../registry/02f/src/base/CMakeLists.txt | 15 + .../fortran/registry/02f/src/base/modules.f90 | 42 + .../registry/02f/src/core/CMakeLists.txt | 14 + .../registry/02f/src/core/factory_base.f90 | 57 ++ .../02f/src/core/factory_interfaces.f90 | 17 + .../registry/02f/src/core/registry.f90 | 203 ++++ .../02f/src/infrastructure/CMakeLists.txt | 17 + .../02f/src/infrastructure/config.f90 | 64 ++ .../02f/src/infrastructure/domain.f90 | 102 ++ .../registry/02f/src/infrastructure/mesh.f90 | 73 ++ .../02f/src/infrastructure/solution.f90 | 131 +++ .../registry/02f/src/manager/CMakeLists.txt | 24 + .../02f/src/manager/component_factory.f90 | 127 +++ .../02f/src/manager/component_manager.f90 | 75 ++ .../registry/02f/src/numerics/CMakeLists.txt | 7 + .../02f/src/numerics/flux/CMakeLists.txt | 28 + .../registry/02f/src/numerics/flux/base.f90 | 30 + .../02f/src/numerics/flux/rusanov.f90 | 41 + .../src/numerics/reconstructor/CMakeLists.txt | 22 + .../02f/src/numerics/reconstructor/base.f90 | 33 + .../02f/src/numerics/reconstructor/eno.f90 | 42 + .../02f/src/numerics/reconstructor/weno3.f90 | 42 + .../fortran/registry/02f/tests/CMakeLists.txt | 78 ++ .../registry/02f/tests/test_architecture.f90 | 117 +++ .../registry/02f/tests/test_basic_only.f90 | 56 ++ .../02f/tests/test_cfd_architecture.f90 | 117 +++ .../02f/tests/test_component_manager.f90 | 111 +++ .../02f/tests/test_domain_solution.f90 | 102 ++ .../02f/tests/test_factory_simple.f90 | 58 ++ .../02f/tests/test_minimal_simple.f90 | 87 ++ .../02f/tests/test_physics_equations.f90 | 82 ++ .../registry/02f/tests/test_simple_link.f90 | 78 ++ .../weno3/fortran/registry/02g/CMakeLists.txt | 19 + .../weno3/fortran/registry/02g/README.md | 221 ++++ .../fortran/registry/02g/scripts/build.bat | 38 + .../fortran/registry/02g/scripts/build.py | 629 ++++++++++++ .../registry/02g/scripts/requirements.txt | 2 + .../registry/02g/scripts/run_step1.bat | 39 + .../fortran/registry/02g/scripts/run_step1.py | 319 ++++++ .../fortran/registry/02g/src/CMakeLists.txt | 17 + .../registry/02g/src/base/CMakeLists.txt | 16 + .../fortran/registry/02g/src/base/modules.f90 | 36 + .../registry/02g/src/base/precision.f90 | 9 + .../registry/02g/src/core/CMakeLists.txt | 14 + .../registry/02g/src/core/factory_base.f90 | 57 ++ .../02g/src/core/factory_interfaces.f90 | 17 + .../registry/02g/src/core/registry.f90 | 203 ++++ .../02g/src/infrastructure/CMakeLists.txt | 17 + .../02g/src/infrastructure/config.f90 | 64 ++ .../02g/src/infrastructure/domain.f90 | 102 ++ .../registry/02g/src/infrastructure/mesh.f90 | 73 ++ .../02g/src/infrastructure/solution.f90 | 131 +++ .../registry/02g/src/manager/CMakeLists.txt | 24 + .../02g/src/manager/component_factory.f90 | 127 +++ .../02g/src/manager/component_manager.f90 | 75 ++ .../registry/02g/src/numerics/CMakeLists.txt | 7 + .../02g/src/numerics/flux/CMakeLists.txt | 28 + .../registry/02g/src/numerics/flux/base.f90 | 30 + .../02g/src/numerics/flux/rusanov.f90 | 41 + .../src/numerics/reconstructor/CMakeLists.txt | 22 + .../02g/src/numerics/reconstructor/base.f90 | 33 + .../02g/src/numerics/reconstructor/eno.f90 | 42 + .../02g/src/numerics/reconstructor/weno3.f90 | 42 + .../registry/02g/src/physics/CMakeLists.txt | 19 + .../physics/equations/linear_convection.f90 | 83 ++ .../02g/src/physics/physics_interface.f90 | 64 ++ .../problems/linear_convection_problem.f90 | 116 +++ .../fortran/registry/02g/tests/CMakeLists.txt | 85 ++ .../registry/02g/tests/test_architecture.f90 | 117 +++ .../registry/02g/tests/test_basic_only.f90 | 56 ++ .../02g/tests/test_cfd_architecture.f90 | 117 +++ .../02g/tests/test_component_manager.f90 | 111 +++ .../02g/tests/test_domain_solution.f90 | 102 ++ .../02g/tests/test_factory_simple.f90 | 58 ++ .../02g/tests/test_minimal_simple.f90 | 87 ++ .../02g/tests/test_physics_minimal.f90 | 91 ++ .../registry/02g/tests/test_simple_link.f90 | 78 ++ .../02g/tests/test_solver_framework.f90 | 91 ++ .../weno3/fortran/registry/02h/CMakeLists.txt | 19 + .../weno3/fortran/registry/02h/README.md | 221 ++++ .../fortran/registry/02h/scripts/build.bat | 38 + .../fortran/registry/02h/scripts/build.py | 629 ++++++++++++ .../registry/02h/scripts/requirements.txt | 2 + .../registry/02h/scripts/run_all_steps.bat | 38 + .../registry/02h/scripts/run_step1.bat | 39 + .../fortran/registry/02h/scripts/run_step1.py | 319 ++++++ .../registry/02h/scripts/run_step2.bat | 39 + .../fortran/registry/02h/scripts/run_step2.py | 284 ++++++ .../fortran/registry/02h/src/CMakeLists.txt | 17 + .../registry/02h/src/base/CMakeLists.txt | 16 + .../fortran/registry/02h/src/base/modules.f90 | 36 + .../registry/02h/src/base/precision.f90 | 9 + .../registry/02h/src/core/CMakeLists.txt | 14 + .../registry/02h/src/core/factory_base.f90 | 57 ++ .../02h/src/core/factory_interfaces.f90 | 17 + .../registry/02h/src/core/registry.f90 | 203 ++++ .../02h/src/infrastructure/CMakeLists.txt | 17 + .../02h/src/infrastructure/config.f90 | 144 +++ .../02h/src/infrastructure/domain.f90 | 102 ++ .../registry/02h/src/infrastructure/mesh.f90 | 73 ++ .../02h/src/infrastructure/solution.f90 | 131 +++ .../registry/02h/src/manager/CMakeLists.txt | 24 + .../02h/src/manager/component_factory.f90 | 127 +++ .../02h/src/manager/component_manager.f90 | 75 ++ .../registry/02h/src/numerics/CMakeLists.txt | 7 + .../02h/src/numerics/flux/CMakeLists.txt | 28 + .../registry/02h/src/numerics/flux/base.f90 | 30 + .../02h/src/numerics/flux/rusanov.f90 | 41 + .../src/numerics/reconstructor/CMakeLists.txt | 22 + .../02h/src/numerics/reconstructor/base.f90 | 33 + .../02h/src/numerics/reconstructor/eno.f90 | 42 + .../02h/src/numerics/reconstructor/weno3.f90 | 42 + .../registry/02h/src/physics/CMakeLists.txt | 19 + .../physics/equations/linear_convection.f90 | 83 ++ .../02h/src/physics/physics_interface.f90 | 64 ++ .../problems/linear_convection_problem.f90 | 116 +++ .../fortran/registry/02h/tests/CMakeLists.txt | 66 ++ .../registry/02h/tests/test_architecture.f90 | 117 +++ .../registry/02h/tests/test_basic_only.f90 | 56 ++ .../02h/tests/test_cfd_architecture.f90 | 117 +++ .../02h/tests/test_component_manager.f90 | 111 +++ .../02h/tests/test_config_physics.f90 | 141 +++ .../02h/tests/test_domain_solution.f90 | 102 ++ .../02h/tests/test_factory_simple.f90 | 58 ++ .../02h/tests/test_minimal_simple.f90 | 87 ++ .../02h/tests/test_physics_minimal.f90 | 91 ++ .../registry/02h/tests/test_simple_link.f90 | 78 ++ .../02h/tests/test_solver_framework.f90 | 91 ++ .../weno3/fortran/registry/03/CMakeLists.txt | 19 + .../weno3/fortran/registry/03/README.md | 221 ++++ .../fortran/registry/03/scripts/build.bat | 38 + .../fortran/registry/03/scripts/build.py | 629 ++++++++++++ .../registry/03/scripts/requirements.txt | 2 + .../registry/03/scripts/run_all_steps.bat | 38 + .../fortran/registry/03/scripts/run_step1.bat | 39 + .../fortran/registry/03/scripts/run_step1.py | 319 ++++++ .../fortran/registry/03/scripts/run_step2.bat | 39 + .../fortran/registry/03/scripts/run_step2.py | 284 ++++++ .../fortran/registry/03/src/CMakeLists.txt | 18 + .../registry/03/src/base/CMakeLists.txt | 16 + .../fortran/registry/03/src/base/modules.f90 | 36 + .../registry/03/src/base/precision.f90 | 9 + .../registry/03/src/core/CMakeLists.txt | 14 + .../registry/03/src/core/factory_base.f90 | 57 ++ .../03/src/core/factory_interfaces.f90 | 17 + .../fortran/registry/03/src/core/registry.f90 | 203 ++++ .../03/src/infrastructure/CMakeLists.txt | 17 + .../registry/03/src/infrastructure/config.f90 | 144 +++ .../registry/03/src/infrastructure/domain.f90 | 102 ++ .../registry/03/src/infrastructure/mesh.f90 | 73 ++ .../03/src/infrastructure/solution.f90 | 131 +++ .../registry/03/src/manager/CMakeLists.txt | 24 + .../03/src/manager/component_factory.f90 | 127 +++ .../03/src/manager/component_manager.f90 | 75 ++ .../registry/03/src/numerics/CMakeLists.txt | 7 + .../03/src/numerics/flux/CMakeLists.txt | 28 + .../registry/03/src/numerics/flux/base.f90 | 30 + .../registry/03/src/numerics/flux/rusanov.f90 | 41 + .../src/numerics/reconstructor/CMakeLists.txt | 22 + .../03/src/numerics/reconstructor/base.f90 | 33 + .../03/src/numerics/reconstructor/eno.f90 | 42 + .../03/src/numerics/reconstructor/weno3.f90 | 42 + .../registry/03/src/physics/CMakeLists.txt | 19 + .../physics/equations/linear_convection.f90 | 83 ++ .../03/src/physics/physics_interface.f90 | 64 ++ .../problems/linear_convection_problem.f90 | 116 +++ .../registry/03/src/solver/CMakeLists.txt | 18 + .../fortran/registry/03/src/solver/base.f90 | 258 +++++ .../fortran/registry/03/tests/CMakeLists.txt | 74 ++ .../registry/03/tests/test_architecture.f90 | 117 +++ .../registry/03/tests/test_basic_only.f90 | 56 ++ .../03/tests/test_cfd_architecture.f90 | 117 +++ .../03/tests/test_component_manager.f90 | 111 +++ .../registry/03/tests/test_config_physics.f90 | 141 +++ .../03/tests/test_domain_solution.f90 | 102 ++ .../registry/03/tests/test_factory_simple.f90 | 58 ++ .../registry/03/tests/test_minimal_simple.f90 | 87 ++ .../03/tests/test_physics_minimal.f90 | 91 ++ .../registry/03/tests/test_simple_link.f90 | 78 ++ .../registry/03/tests/test_solver_base.f90 | 99 ++ .../03/tests/test_solver_framework.f90 | 91 ++ .../weno3/fortran/registry/03a/CMakeLists.txt | 19 + .../weno3/fortran/registry/03a/README.md | 221 ++++ .../fortran/registry/03a/scripts/build.bat | 38 + .../fortran/registry/03a/scripts/build.py | 629 ++++++++++++ .../registry/03a/scripts/requirements.txt | 2 + .../registry/03a/scripts/run_all_steps.bat | 38 + .../registry/03a/scripts/run_step1.bat | 39 + .../fortran/registry/03a/scripts/run_step1.py | 319 ++++++ .../registry/03a/scripts/run_step2.bat | 39 + .../fortran/registry/03a/scripts/run_step2.py | 284 ++++++ .../fortran/registry/03a/src/CMakeLists.txt | 18 + .../registry/03a/src/base/CMakeLists.txt | 16 + .../fortran/registry/03a/src/base/modules.f90 | 36 + .../registry/03a/src/base/precision.f90 | 9 + .../registry/03a/src/core/CMakeLists.txt | 14 + .../registry/03a/src/core/factory_base.f90 | 57 ++ .../03a/src/core/factory_interfaces.f90 | 17 + .../registry/03a/src/core/registry.f90 | 203 ++++ .../03a/src/infrastructure/CMakeLists.txt | 17 + .../03a/src/infrastructure/config.f90 | 144 +++ .../03a/src/infrastructure/domain.f90 | 102 ++ .../registry/03a/src/infrastructure/mesh.f90 | 73 ++ .../03a/src/infrastructure/solution.f90 | 131 +++ .../registry/03a/src/manager/CMakeLists.txt | 24 + .../03a/src/manager/component_factory.f90 | 128 +++ .../03a/src/manager/component_manager.f90 | 75 ++ .../registry/03a/src/numerics/CMakeLists.txt | 7 + .../03a/src/numerics/flux/CMakeLists.txt | 28 + .../registry/03a/src/numerics/flux/base.f90 | 30 + .../03a/src/numerics/flux/rusanov.f90 | 41 + .../src/numerics/reconstructor/CMakeLists.txt | 22 + .../03a/src/numerics/reconstructor/base.f90 | 33 + .../03a/src/numerics/reconstructor/eno.f90 | 42 + .../03a/src/numerics/reconstructor/weno3.f90 | 42 + .../registry/03a/src/physics/CMakeLists.txt | 19 + .../physics/equations/linear_convection.f90 | 49 + .../03a/src/physics/physics_interface.f90 | 64 ++ .../problems/linear_convection_problem.f90 | 118 +++ .../registry/03a/src/solver/CMakeLists.txt | 18 + .../fortran/registry/03a/src/solver/base.f90 | 258 +++++ .../fortran/registry/03a/tests/CMakeLists.txt | 83 ++ .../registry/03a/tests/test_architecture.f90 | 117 +++ .../registry/03a/tests/test_basic_only.f90 | 56 ++ .../03a/tests/test_cfd_architecture.f90 | 117 +++ .../03a/tests/test_component_manager.f90 | 111 +++ .../tests/test_component_manager_physics.f90 | 120 +++ .../03a/tests/test_config_physics.f90 | 141 +++ .../03a/tests/test_domain_solution.f90 | 102 ++ .../03a/tests/test_factory_simple.f90 | 58 ++ .../03a/tests/test_minimal_simple.f90 | 87 ++ .../03a/tests/test_physics_minimal.f90 | 91 ++ .../registry/03a/tests/test_simple_link.f90 | 78 ++ .../registry/03a/tests/test_solver_base.f90 | 99 ++ .../03a/tests/test_solver_framework.f90 | 91 ++ .../weno3/fortran/registry/03b/CMakeLists.txt | 20 + .../weno3/fortran/registry/03b/README.md | 221 ++++ .../registry/03b/examples/CMakeLists.txt | 22 + .../registry/03b/examples/run_eno_weno.f90 | 318 ++++++ .../fortran/registry/03b/scripts/build.bat | 38 + .../fortran/registry/03b/scripts/build.py | 629 ++++++++++++ .../registry/03b/scripts/requirements.txt | 2 + .../registry/03b/scripts/run_all_steps.bat | 38 + .../registry/03b/scripts/run_example.py | 90 ++ .../registry/03b/scripts/run_step1.bat | 39 + .../fortran/registry/03b/scripts/run_step1.py | 319 ++++++ .../registry/03b/scripts/run_step2.bat | 39 + .../fortran/registry/03b/scripts/run_step2.py | 284 ++++++ .../fortran/registry/03b/src/CMakeLists.txt | 31 + .../registry/03b/src/base/CMakeLists.txt | 16 + .../fortran/registry/03b/src/base/modules.f90 | 36 + .../registry/03b/src/base/precision.f90 | 9 + .../registry/03b/src/core/CMakeLists.txt | 14 + .../registry/03b/src/core/factory_base.f90 | 57 ++ .../03b/src/core/factory_interfaces.f90 | 17 + .../registry/03b/src/core/registry.f90 | 257 +++++ .../03b/src/core/registry_initializer.f90 | 39 + .../03b/src/infrastructure/CMakeLists.txt | 17 + .../03b/src/infrastructure/config.f90 | 144 +++ .../03b/src/infrastructure/domain.f90 | 102 ++ .../registry/03b/src/infrastructure/mesh.f90 | 73 ++ .../03b/src/infrastructure/solution.f90 | 131 +++ .../registry/03b/src/manager/CMakeLists.txt | 24 + .../03b/src/manager/component_factory.f90 | 142 +++ .../03b/src/manager/component_manager.f90 | 76 ++ .../registry/03b/src/numerics/CMakeLists.txt | 7 + .../03b/src/numerics/flux/CMakeLists.txt | 28 + .../registry/03b/src/numerics/flux/base.f90 | 30 + .../03b/src/numerics/flux/rusanov.f90 | 41 + .../src/numerics/reconstructor/CMakeLists.txt | 23 + .../03b/src/numerics/reconstructor/base.f90 | 33 + .../03b/src/numerics/reconstructor/eno.f90 | 42 + .../03b/src/numerics/reconstructor/weno3.f90 | 42 + .../03b/src/numerics/reconstructor/weno5.f90 | 42 + .../registry/03b/src/physics/CMakeLists.txt | 19 + .../physics/equations/linear_convection.f90 | 49 + .../03b/src/physics/physics_interface.f90 | 64 ++ .../problems/linear_convection_problem.f90 | 118 +++ .../fortran/registry/03b/src/run_eno_weno.f90 | 171 ++++ .../registry/03b/src/solver/CMakeLists.txt | 21 + .../fortran/registry/03b/src/solver/base.f90 | 258 +++++ .../03b/src/solver/physics_solver.f90 | 332 ++++++ .../fortran/registry/03b/tests/CMakeLists.txt | 95 ++ .../registry/03b/tests/test_architecture.f90 | 117 +++ .../03b/tests/test_cfd_architecture.f90 | 117 +++ .../03b/tests/test_component_manager.f90 | 111 +++ .../tests/test_component_manager_physics.f90 | 120 +++ .../03b/tests/test_config_physics.f90 | 141 +++ .../03b/tests/test_domain_solution.f90 | 102 ++ .../03b/tests/test_factory_simple.f90 | 58 ++ .../03b/tests/test_infrastructure.f90 | 56 ++ .../registry/03b/tests/test_physics.f90 | 91 ++ .../03b/tests/test_physics_solver.f90 | 133 +++ .../registry/03b/tests/test_registry.f90 | 87 ++ .../registry/03b/tests/test_simple_link.f90 | 78 ++ .../registry/03b/tests/test_solver_base.f90 | 99 ++ .../03b/tests/test_solver_framework.f90 | 91 ++ .../weno3/fortran/registry/03c/CMakeLists.txt | 20 + .../weno3/fortran/registry/03c/README.md | 221 ++++ .../registry/03c/examples/CMakeLists.txt | 22 + .../registry/03c/examples/run_eno_weno.f90 | 359 +++++++ .../fortran/registry/03c/scripts/build.bat | 38 + .../fortran/registry/03c/scripts/build.py | 629 ++++++++++++ .../registry/03c/scripts/requirements.txt | 2 + .../registry/03c/scripts/run_all_steps.bat | 38 + .../registry/03c/scripts/run_example.py | 90 ++ .../registry/03c/scripts/run_step1.bat | 39 + .../fortran/registry/03c/scripts/run_step1.py | 319 ++++++ .../registry/03c/scripts/run_step2.bat | 39 + .../fortran/registry/03c/scripts/run_step2.py | 284 ++++++ .../fortran/registry/03c/src/CMakeLists.txt | 31 + .../registry/03c/src/base/CMakeLists.txt | 16 + .../fortran/registry/03c/src/base/modules.f90 | 36 + .../registry/03c/src/base/precision.f90 | 9 + .../registry/03c/src/core/CMakeLists.txt | 14 + .../registry/03c/src/core/factory_base.f90 | 57 ++ .../03c/src/core/factory_interfaces.f90 | 17 + .../registry/03c/src/core/registry.f90 | 257 +++++ .../03c/src/core/registry_initializer.f90 | 39 + .../03c/src/infrastructure/CMakeLists.txt | 17 + .../03c/src/infrastructure/config.f90 | 144 +++ .../03c/src/infrastructure/domain.f90 | 102 ++ .../registry/03c/src/infrastructure/mesh.f90 | 73 ++ .../03c/src/infrastructure/solution.f90 | 131 +++ .../registry/03c/src/manager/CMakeLists.txt | 24 + .../03c/src/manager/component_factory.f90 | 142 +++ .../03c/src/manager/component_manager.f90 | 76 ++ .../registry/03c/src/numerics/CMakeLists.txt | 7 + .../03c/src/numerics/flux/CMakeLists.txt | 28 + .../registry/03c/src/numerics/flux/base.f90 | 30 + .../03c/src/numerics/flux/rusanov.f90 | 41 + .../src/numerics/reconstructor/CMakeLists.txt | 23 + .../03c/src/numerics/reconstructor/base.f90 | 33 + .../03c/src/numerics/reconstructor/eno.f90 | 42 + .../03c/src/numerics/reconstructor/weno3.f90 | 42 + .../03c/src/numerics/reconstructor/weno5.f90 | 42 + .../registry/03c/src/physics/CMakeLists.txt | 19 + .../physics/equations/linear_convection.f90 | 49 + .../03c/src/physics/physics_interface.f90 | 64 ++ .../problems/linear_convection_problem.f90 | 118 +++ .../fortran/registry/03c/src/run_eno_weno.f90 | 171 ++++ .../registry/03c/src/solver/CMakeLists.txt | 21 + .../fortran/registry/03c/src/solver/base.f90 | 264 +++++ .../03c/src/solver/physics_solver.f90 | 503 ++++++++++ .../03c/test_physics_solver_simple.f90 | 102 ++ .../fortran/registry/03c/tests/CMakeLists.txt | 106 ++ .../registry/03c/tests/test_architecture.f90 | 117 +++ .../03c/tests/test_cfd_architecture.f90 | 117 +++ .../03c/tests/test_component_manager.f90 | 111 +++ .../tests/test_component_manager_physics.f90 | 120 +++ .../03c/tests/test_config_physics.f90 | 141 +++ .../03c/tests/test_domain_solution.f90 | 102 ++ .../03c/tests/test_factory_simple.f90 | 58 ++ .../03c/tests/test_infrastructure.f90 | 56 ++ .../registry/03c/tests/test_physics.f90 | 91 ++ .../03c/tests/test_physics_solver.f90 | 133 +++ .../03c/tests/test_physics_solver_simple.f90 | 161 +++ .../registry/03c/tests/test_registry.f90 | 87 ++ .../registry/03c/tests/test_simple_link.f90 | 78 ++ .../registry/03c/tests/test_solver_base.f90 | 99 ++ .../03c/tests/test_solver_framework.f90 | 91 ++ .../weno3/fortran/registry/03d/CMakeLists.txt | 20 + .../weno3/fortran/registry/03d/README.md | 221 ++++ .../registry/03d/examples/CMakeLists.txt | 22 + .../registry/03d/examples/run_eno_weno.f90 | 282 ++++++ .../fortran/registry/03d/scripts/build.bat | 38 + .../fortran/registry/03d/scripts/build.py | 629 ++++++++++++ .../registry/03d/scripts/requirements.txt | 2 + .../registry/03d/scripts/run_all_steps.bat | 38 + .../registry/03d/scripts/run_example.py | 90 ++ .../registry/03d/scripts/run_step1.bat | 39 + .../fortran/registry/03d/scripts/run_step1.py | 319 ++++++ .../registry/03d/scripts/run_step2.bat | 39 + .../fortran/registry/03d/scripts/run_step2.py | 284 ++++++ .../fortran/registry/03d/src/CMakeLists.txt | 18 + .../registry/03d/src/base/CMakeLists.txt | 16 + .../fortran/registry/03d/src/base/modules.f90 | 36 + .../registry/03d/src/base/precision.f90 | 9 + .../registry/03d/src/core/CMakeLists.txt | 14 + .../registry/03d/src/core/factory_base.f90 | 57 ++ .../03d/src/core/factory_interfaces.f90 | 17 + .../registry/03d/src/core/registry.f90 | 257 +++++ .../03d/src/core/registry_initializer.f90 | 39 + .../03d/src/infrastructure/CMakeLists.txt | 17 + .../03d/src/infrastructure/config.f90 | 144 +++ .../03d/src/infrastructure/domain.f90 | 102 ++ .../registry/03d/src/infrastructure/mesh.f90 | 73 ++ .../03d/src/infrastructure/solution.f90 | 131 +++ .../registry/03d/src/manager/CMakeLists.txt | 24 + .../03d/src/manager/component_factory.f90 | 142 +++ .../03d/src/manager/component_manager.f90 | 76 ++ .../registry/03d/src/numerics/CMakeLists.txt | 7 + .../03d/src/numerics/flux/CMakeLists.txt | 28 + .../registry/03d/src/numerics/flux/base.f90 | 30 + .../03d/src/numerics/flux/rusanov.f90 | 41 + .../src/numerics/reconstructor/CMakeLists.txt | 23 + .../03d/src/numerics/reconstructor/base.f90 | 33 + .../03d/src/numerics/reconstructor/eno.f90 | 42 + .../03d/src/numerics/reconstructor/weno3.f90 | 42 + .../03d/src/numerics/reconstructor/weno5.f90 | 42 + .../registry/03d/src/physics/CMakeLists.txt | 19 + .../physics/equations/linear_convection.f90 | 49 + .../03d/src/physics/physics_interface.f90 | 64 ++ .../problems/linear_convection_problem.f90 | 118 +++ .../registry/03d/src/solver/CMakeLists.txt | 21 + .../fortran/registry/03d/src/solver/base.f90 | 264 +++++ .../03d/src/solver/physics_solver.f90 | 503 ++++++++++ .../fortran/registry/03d/tests/CMakeLists.txt | 106 ++ .../registry/03d/tests/test_architecture.f90 | 117 +++ .../03d/tests/test_cfd_architecture.f90 | 117 +++ .../03d/tests/test_component_manager.f90 | 111 +++ .../tests/test_component_manager_physics.f90 | 120 +++ .../03d/tests/test_config_physics.f90 | 141 +++ .../03d/tests/test_domain_solution.f90 | 102 ++ .../03d/tests/test_factory_simple.f90 | 58 ++ .../03d/tests/test_infrastructure.f90 | 56 ++ .../registry/03d/tests/test_physics.f90 | 91 ++ .../03d/tests/test_physics_solver.f90 | 133 +++ .../03d/tests/test_physics_solver_simple.f90 | 161 +++ .../registry/03d/tests/test_registry.f90 | 87 ++ .../registry/03d/tests/test_simple_link.f90 | 78 ++ .../registry/03d/tests/test_solver_base.f90 | 99 ++ .../03d/tests/test_solver_framework.f90 | 91 ++ .../weno3/fortran/registry/03e/CMakeLists.txt | 20 + .../weno3/fortran/registry/03e/README.md | 221 ++++ .../registry/03e/examples/CMakeLists.txt | 23 + .../registry/03e/examples/run_eno_weno.f90 | 327 ++++++ .../registry/03e/python/plot_results.py | 419 ++++++++ .../fortran/registry/03e/scripts/build.bat | 38 + .../fortran/registry/03e/scripts/build.py | 629 ++++++++++++ .../registry/03e/scripts/requirements.txt | 2 + .../registry/03e/scripts/run_all_steps.bat | 38 + .../registry/03e/scripts/run_example.py | 90 ++ .../registry/03e/scripts/run_step1.bat | 39 + .../fortran/registry/03e/scripts/run_step1.py | 319 ++++++ .../registry/03e/scripts/run_step2.bat | 39 + .../fortran/registry/03e/scripts/run_step2.py | 284 ++++++ .../fortran/registry/03e/src/CMakeLists.txt | 39 + .../registry/03e/src/base/CMakeLists.txt | 16 + .../fortran/registry/03e/src/base/modules.f90 | 36 + .../registry/03e/src/base/precision.f90 | 9 + .../registry/03e/src/core/CMakeLists.txt | 14 + .../registry/03e/src/core/factory_base.f90 | 57 ++ .../03e/src/core/factory_interfaces.f90 | 17 + .../registry/03e/src/core/registry.f90 | 257 +++++ .../03e/src/core/registry_initializer.f90 | 39 + .../03e/src/infrastructure/CMakeLists.txt | 17 + .../03e/src/infrastructure/config.f90 | 144 +++ .../03e/src/infrastructure/domain.f90 | 102 ++ .../registry/03e/src/infrastructure/mesh.f90 | 73 ++ .../03e/src/infrastructure/solution.f90 | 131 +++ .../registry/03e/src/manager/CMakeLists.txt | 24 + .../03e/src/manager/component_factory.f90 | 142 +++ .../03e/src/manager/component_manager.f90 | 76 ++ .../registry/03e/src/numerics/CMakeLists.txt | 7 + .../03e/src/numerics/flux/CMakeLists.txt | 28 + .../registry/03e/src/numerics/flux/base.f90 | 30 + .../03e/src/numerics/flux/rusanov.f90 | 41 + .../src/numerics/reconstructor/CMakeLists.txt | 23 + .../03e/src/numerics/reconstructor/base.f90 | 33 + .../03e/src/numerics/reconstructor/eno.f90 | 42 + .../03e/src/numerics/reconstructor/weno3.f90 | 42 + .../03e/src/numerics/reconstructor/weno5.f90 | 42 + .../registry/03e/src/physics/CMakeLists.txt | 19 + .../physics/equations/linear_convection.f90 | 49 + .../03e/src/physics/physics_interface.f90 | 64 ++ .../problems/linear_convection_problem.f90 | 118 +++ .../fortran/registry/03e/src/results.f90 | 290 ++++++ .../registry/03e/src/solver/CMakeLists.txt | 22 + .../fortran/registry/03e/src/solver/base.f90 | 264 +++++ .../03e/src/solver/physics_solver.f90 | 518 ++++++++++ .../fortran/registry/03e/tests/CMakeLists.txt | 106 ++ .../registry/03e/tests/test_architecture.f90 | 117 +++ .../03e/tests/test_cfd_architecture.f90 | 117 +++ .../03e/tests/test_component_manager.f90 | 111 +++ .../tests/test_component_manager_physics.f90 | 120 +++ .../03e/tests/test_config_physics.f90 | 141 +++ .../03e/tests/test_domain_solution.f90 | 102 ++ .../03e/tests/test_factory_simple.f90 | 58 ++ .../03e/tests/test_infrastructure.f90 | 56 ++ .../registry/03e/tests/test_physics.f90 | 91 ++ .../03e/tests/test_physics_solver.f90 | 133 +++ .../03e/tests/test_physics_solver_simple.f90 | 161 +++ .../registry/03e/tests/test_registry.f90 | 87 ++ .../registry/03e/tests/test_simple_link.f90 | 78 ++ .../registry/03e/tests/test_solver_base.f90 | 99 ++ .../03e/tests/test_solver_framework.f90 | 91 ++ .../weno3/fortran/registry/03f/CMakeLists.txt | 20 + .../weno3/fortran/registry/03f/README.md | 221 ++++ .../registry/03f/examples/CMakeLists.txt | 39 + .../registry/03f/examples/run_eno_weno.f90 | 327 ++++++ .../03f/examples/run_eno_weno_integrated.f90 | 176 ++++ .../registry/03f/python/plot_results.py | 419 ++++++++ .../fortran/registry/03f/scripts/build.bat | 38 + .../fortran/registry/03f/scripts/build.py | 629 ++++++++++++ .../registry/03f/scripts/requirements.txt | 2 + .../registry/03f/scripts/run_all_steps.bat | 38 + .../registry/03f/scripts/run_example.py | 90 ++ .../registry/03f/scripts/run_step1.bat | 39 + .../fortran/registry/03f/scripts/run_step1.py | 319 ++++++ .../registry/03f/scripts/run_step2.bat | 39 + .../fortran/registry/03f/scripts/run_step2.py | 284 ++++++ .../registry/03f/scripts/test_integrated.py | 50 + .../fortran/registry/03f/src/CMakeLists.txt | 41 + .../registry/03f/src/base/CMakeLists.txt | 16 + .../fortran/registry/03f/src/base/modules.f90 | 36 + .../registry/03f/src/base/precision.f90 | 9 + .../registry/03f/src/boundary/CMakeLists.txt | 23 + .../03f/src/boundary/boundary_base.f90 | 35 + .../registry/03f/src/boundary/dirichlet.f90 | 55 + .../registry/03f/src/boundary/factory.f90 | 41 + .../registry/03f/src/boundary/neumann.f90 | 41 + .../registry/03f/src/boundary/periodic.f90 | 44 + .../registry/03f/src/core/CMakeLists.txt | 14 + .../registry/03f/src/core/factory_base.f90 | 57 ++ .../03f/src/core/factory_integrated.f90 | 41 + .../03f/src/core/factory_interfaces.f90 | 17 + .../src/core/physics_solver_integrated.f90 | 127 +++ .../registry/03f/src/core/registry.f90 | 257 +++++ .../03f/src/core/registry_initializer.f90 | 39 + .../03f/src/infrastructure/CMakeLists.txt | 17 + .../03f/src/infrastructure/config.f90 | 144 +++ .../03f/src/infrastructure/domain.f90 | 102 ++ .../registry/03f/src/infrastructure/mesh.f90 | 73 ++ .../03f/src/infrastructure/solution.f90 | 131 +++ .../03f/src/initial_condition/CMakeLists.txt | 23 + .../03f/src/initial_condition/factory.f90 | 31 + .../03f/src/initial_condition/gaussian.f90 | 62 ++ .../03f/src/initial_condition/ic_base.f90 | 43 + .../03f/src/initial_condition/sine.f90 | 62 ++ .../03f/src/initial_condition/step.f90 | 62 ++ .../registry/03f/src/manager/CMakeLists.txt | 24 + .../03f/src/manager/component_factory.f90 | 127 +++ .../03f/src/manager/component_manager.f90 | 48 + .../registry/03f/src/numerics/CMakeLists.txt | 7 + .../03f/src/numerics/flux/CMakeLists.txt | 28 + .../registry/03f/src/numerics/flux/base.f90 | 30 + .../03f/src/numerics/flux/engquist_osher.f90 | 41 + .../03f/src/numerics/flux/rusanov.f90 | 41 + .../src/numerics/reconstructor/CMakeLists.txt | 23 + .../03f/src/numerics/reconstructor/base.f90 | 33 + .../03f/src/numerics/reconstructor/eno.f90 | 42 + .../03f/src/numerics/reconstructor/weno3.f90 | 40 + .../03f/src/numerics/reconstructor/weno5.f90 | 42 + .../registry/03f/src/physics/CMakeLists.txt | 19 + .../physics/equations/linear_convection.f90 | 43 + .../03f/src/physics/physics_interface.f90 | 64 ++ .../problems/linear_advection_problem.f90 | 70 ++ .../problems/linear_convection_problem.f90 | 118 +++ .../fortran/registry/03f/src/results.f90 | 290 ++++++ .../registry/03f/src/solver/CMakeLists.txt | 44 + .../fortran/registry/03f/src/solver/base.f90 | 264 +++++ .../03f/src/solver/physics_solver.f90 | 188 ++++ .../registry/03f/src/solver/residual.f90 | 69 ++ .../03f/src/solver/solver_integrated.f90 | 390 ++++++++ .../fortran/registry/03f/tests/CMakeLists.txt | 137 +++ .../registry/03f/tests/test_architecture.f90 | 117 +++ .../03f/tests/test_cfd_architecture.f90 | 117 +++ .../03f/tests/test_component_manager.f90 | 56 ++ .../tests/test_component_manager_physics.f90 | 120 +++ .../03f/tests/test_config_physics.f90 | 141 +++ .../03f/tests/test_domain_solution.f90 | 102 ++ .../03f/tests/test_factory_simple.f90 | 58 ++ .../03f/tests/test_infrastructure.f90 | 56 ++ .../03f/tests/test_initial_condition.f90 | 96 ++ .../registry/03f/tests/test_physics.f90 | 20 + .../03f/tests/test_physics_solver.f90 | 85 ++ .../03f/tests/test_physics_solver_simple.f90 | 161 +++ .../registry/03f/tests/test_registry.f90 | 87 ++ .../registry/03f/tests/test_simple_link.f90 | 78 ++ .../registry/03f/tests/test_solver_base.f90 | 99 ++ .../03f/tests/test_solver_framework.f90 | 91 ++ .../weno3/fortran/registry/03g/CMakeLists.txt | 20 + .../weno3/fortran/registry/03g/README.md | 221 ++++ .../registry/03g/examples/CMakeLists.txt | 39 + .../registry/03g/examples/run_eno_weno.f90 | 327 ++++++ .../03g/examples/run_eno_weno_integrated.f90 | 176 ++++ .../registry/03g/python/plot_results.py | 419 ++++++++ .../fortran/registry/03g/scripts/build.bat | 38 + .../fortran/registry/03g/scripts/build.py | 629 ++++++++++++ .../registry/03g/scripts/requirements.txt | 2 + .../registry/03g/scripts/run_all_steps.bat | 38 + .../registry/03g/scripts/run_example.py | 90 ++ .../registry/03g/scripts/run_step1.bat | 39 + .../fortran/registry/03g/scripts/run_step1.py | 319 ++++++ .../registry/03g/scripts/run_step2.bat | 39 + .../fortran/registry/03g/scripts/run_step2.py | 284 ++++++ .../registry/03g/scripts/test_integrated.py | 50 + .../fortran/registry/03g/src/CMakeLists.txt | 41 + .../registry/03g/src/base/CMakeLists.txt | 16 + .../fortran/registry/03g/src/base/modules.f90 | 36 + .../registry/03g/src/base/precision.f90 | 9 + .../registry/03g/src/boundary/CMakeLists.txt | 23 + .../03g/src/boundary/boundary_base.f90 | 35 + .../registry/03g/src/boundary/dirichlet.f90 | 55 + .../registry/03g/src/boundary/factory.f90 | 41 + .../registry/03g/src/boundary/neumann.f90 | 41 + .../registry/03g/src/boundary/periodic.f90 | 44 + .../registry/03g/src/core/CMakeLists.txt | 14 + .../registry/03g/src/core/factory_base.f90 | 57 ++ .../03g/src/core/factory_integrated.f90 | 41 + .../03g/src/core/factory_interfaces.f90 | 17 + .../src/core/physics_solver_integrated.f90 | 127 +++ .../registry/03g/src/core/registry.f90 | 257 +++++ .../03g/src/core/registry_initializer.f90 | 39 + .../03g/src/infrastructure/CMakeLists.txt | 17 + .../03g/src/infrastructure/config.f90 | 144 +++ .../03g/src/infrastructure/domain.f90 | 102 ++ .../registry/03g/src/infrastructure/mesh.f90 | 73 ++ .../03g/src/infrastructure/solution.f90 | 131 +++ .../03g/src/initial_condition/CMakeLists.txt | 23 + .../03g/src/initial_condition/factory.f90 | 31 + .../03g/src/initial_condition/gaussian.f90 | 62 ++ .../03g/src/initial_condition/ic_base.f90 | 43 + .../03g/src/initial_condition/sine.f90 | 62 ++ .../03g/src/initial_condition/step.f90 | 62 ++ .../registry/03g/src/manager/CMakeLists.txt | 24 + .../03g/src/manager/component_factory.f90 | 127 +++ .../03g/src/manager/component_manager.f90 | 48 + .../registry/03g/src/numerics/CMakeLists.txt | 8 + .../03g/src/numerics/flux/CMakeLists.txt | 28 + .../registry/03g/src/numerics/flux/base.f90 | 30 + .../03g/src/numerics/flux/engquist_osher.f90 | 41 + .../03g/src/numerics/flux/rusanov.f90 | 41 + .../src/numerics/reconstructor/CMakeLists.txt | 23 + .../03g/src/numerics/reconstructor/base.f90 | 33 + .../03g/src/numerics/reconstructor/eno.f90 | 42 + .../03g/src/numerics/reconstructor/weno3.f90 | 40 + .../03g/src/numerics/reconstructor/weno5.f90 | 42 + .../numerics/time_integration/CMakeLists.txt | 24 + .../src/numerics/time_integration/base.f90 | 77 ++ .../numerics/time_integration/base_simple.f90 | 53 + .../src/numerics/time_integration/factory.f90 | 51 + .../03g/src/numerics/time_integration/rk1.f90 | 43 + .../03g/src/numerics/time_integration/rk2.f90 | 76 ++ .../03g/src/numerics/time_integration/rk3.f90 | 98 ++ .../registry/03g/src/physics/CMakeLists.txt | 19 + .../physics/equations/linear_convection.f90 | 43 + .../03g/src/physics/physics_interface.f90 | 64 ++ .../problems/linear_advection_problem.f90 | 70 ++ .../problems/linear_convection_problem.f90 | 118 +++ .../fortran/registry/03g/src/results.f90 | 290 ++++++ .../registry/03g/src/solver/CMakeLists.txt | 44 + .../fortran/registry/03g/src/solver/base.f90 | 264 +++++ .../03g/src/solver/physics_solver.f90 | 188 ++++ .../registry/03g/src/solver/residual.f90 | 152 +++ .../03g/src/solver/residual_simple.f90 | 96 ++ .../03g/src/solver/solver_integrated.f90 | 390 ++++++++ .../fortran/registry/03g/tests/CMakeLists.txt | 137 +++ .../registry/03g/tests/test_architecture.f90 | 117 +++ .../03g/tests/test_cfd_architecture.f90 | 117 +++ .../03g/tests/test_component_manager.f90 | 56 ++ .../tests/test_component_manager_physics.f90 | 120 +++ .../03g/tests/test_config_physics.f90 | 141 +++ .../03g/tests/test_domain_solution.f90 | 102 ++ .../03g/tests/test_factory_simple.f90 | 58 ++ .../03g/tests/test_infrastructure.f90 | 56 ++ .../03g/tests/test_initial_condition.f90 | 96 ++ .../registry/03g/tests/test_physics.f90 | 20 + .../03g/tests/test_physics_solver.f90 | 85 ++ .../03g/tests/test_physics_solver_simple.f90 | 161 +++ .../registry/03g/tests/test_registry.f90 | 87 ++ .../registry/03g/tests/test_simple_link.f90 | 78 ++ .../registry/03g/tests/test_solver_base.f90 | 99 ++ .../03g/tests/test_solver_framework.f90 | 91 ++ .../weno3/julia/01/julia/boundary.jl | 90 ++ .../julia/01/julia/test/test_boundary.jl | 57 ++ .../weno3/julia/01/python/boundary.py | 103 ++ .../weno3/julia/01/python/cfd_registry.py | 62 ++ .../weno3/julia/01/python/config.py | 41 + .../weno3/julia/01/python/domain.py | 56 ++ .../julia/01/python/factories/base_factory.py | 49 + .../weno3/julia/01/python/flux.py | 73 ++ .../julia/01/python/gen_boundary_test_data.py | 35 + .../julia/01/python/initial_condition.py | 90 ++ .../weno3/julia/01/python/mesh.py | 26 + .../weno3/julia/01/python/plotter.py | 107 ++ .../julia/01/python/reconstructor/__init__.py | 4 + .../julia/01/python/reconstructor/base.py | 7 + .../julia/01/python/reconstructor/eno.py | 90 ++ .../julia/01/python/reconstructor/factory.py | 42 + .../julia/01/python/reconstructor/weno3.py | 88 ++ .../weno3/julia/01/python/registry.py | 75 ++ .../weno3/julia/01/python/residual.py | 40 + .../weno3/julia/01/python/run_eno_weno.py | 52 + .../weno3/julia/01/python/solution.py | 40 + .../weno3/julia/01/python/solver.py | 86 ++ .../weno3/julia/01/python/time_integration.py | 125 +++ .../weno3/julia/01/python/u_dirichlet_py.npy | Bin 0 -> 480 bytes .../weno3/julia/01/python/u_input.npy | Bin 0 -> 480 bytes .../weno3/julia/01/python/u_neumann_py.npy | Bin 0 -> 480 bytes .../weno3/julia/01/python/u_periodic_py.npy | Bin 0 -> 480 bytes .../weno3/julia/01a/julia/boundary.jl | 90 ++ .../julia/01a/julia/initial_condition.jl | 86 ++ .../julia/01a/julia/test/test_boundary.jl | 57 ++ .../01a/julia/test/test_initial_condition.jl | 45 + .../weno3/julia/01a/python/boundary.py | 103 ++ .../weno3/julia/01a/python/cfd_registry.py | 62 ++ .../weno3/julia/01a/python/config.py | 41 + .../weno3/julia/01a/python/domain.py | 56 ++ .../01a/python/factories/base_factory.py | 49 + .../weno3/julia/01a/python/flux.py | 73 ++ .../01a/python/gen_boundary_test_data.py | 35 + .../julia/01a/python/gen_ic_test_data.py | 27 + .../julia/01a/python/initial_condition.py | 90 ++ .../weno3/julia/01a/python/mesh.py | 26 + .../weno3/julia/01a/python/plotter.py | 107 ++ .../01a/python/reconstructor/__init__.py | 4 + .../julia/01a/python/reconstructor/base.py | 7 + .../julia/01a/python/reconstructor/eno.py | 90 ++ .../julia/01a/python/reconstructor/factory.py | 42 + .../julia/01a/python/reconstructor/weno3.py | 88 ++ .../weno3/julia/01a/python/registry.py | 75 ++ .../weno3/julia/01a/python/residual.py | 40 + .../weno3/julia/01a/python/run_eno_weno.py | 52 + .../weno3/julia/01a/python/solution.py | 40 + .../weno3/julia/01a/python/solver.py | 86 ++ .../julia/01a/python/time_integration.py | 125 +++ .../weno3/julia/01a/python/u_dirichlet_py.npy | Bin 0 -> 480 bytes .../julia/01a/python/u_gaussian_full_py.npy | Bin 0 -> 480 bytes .../01a/python/u_gaussian_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01a/python/u_input.npy | Bin 0 -> 480 bytes .../weno3/julia/01a/python/u_neumann_py.npy | Bin 0 -> 480 bytes .../weno3/julia/01a/python/u_periodic_py.npy | Bin 0 -> 480 bytes .../weno3/julia/01a/python/u_sin_full_py.npy | Bin 0 -> 480 bytes .../julia/01a/python/u_sin_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01a/python/u_step_full_py.npy | Bin 0 -> 480 bytes .../julia/01a/python/u_step_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01b/julia/boundary.jl | 90 ++ .../julia/01b/julia/initial_condition.jl | 86 ++ .../weno3/julia/01b/julia/mesh.jl | 45 + .../julia/01b/julia/test/test_boundary.jl | 57 ++ .../01b/julia/test/test_initial_condition.jl | 45 + .../weno3/julia/01b/julia/test/test_mesh.jl | 37 + .../weno3/julia/01b/python/boundary.py | 103 ++ .../weno3/julia/01b/python/cfd_registry.py | 62 ++ .../weno3/julia/01b/python/config.py | 41 + .../weno3/julia/01b/python/domain.py | 56 ++ .../01b/python/factories/base_factory.py | 49 + .../weno3/julia/01b/python/flux.py | 73 ++ .../01b/python/gen_boundary_test_data.py | 35 + .../julia/01b/python/gen_ic_test_data.py | 27 + .../julia/01b/python/initial_condition.py | 90 ++ .../weno3/julia/01b/python/mesh.py | 26 + .../weno3/julia/01b/python/plotter.py | 107 ++ .../01b/python/reconstructor/__init__.py | 4 + .../julia/01b/python/reconstructor/base.py | 7 + .../julia/01b/python/reconstructor/eno.py | 90 ++ .../julia/01b/python/reconstructor/factory.py | 42 + .../julia/01b/python/reconstructor/weno3.py | 88 ++ .../weno3/julia/01b/python/registry.py | 75 ++ .../weno3/julia/01b/python/residual.py | 40 + .../weno3/julia/01b/python/run_eno_weno.py | 52 + .../weno3/julia/01b/python/solution.py | 40 + .../weno3/julia/01b/python/solver.py | 86 ++ .../julia/01b/python/time_integration.py | 125 +++ .../weno3/julia/01b/python/u_dirichlet_py.npy | Bin 0 -> 480 bytes .../julia/01b/python/u_gaussian_full_py.npy | Bin 0 -> 480 bytes .../01b/python/u_gaussian_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01b/python/u_input.npy | Bin 0 -> 480 bytes .../weno3/julia/01b/python/u_neumann_py.npy | Bin 0 -> 480 bytes .../weno3/julia/01b/python/u_periodic_py.npy | Bin 0 -> 480 bytes .../weno3/julia/01b/python/u_sin_full_py.npy | Bin 0 -> 480 bytes .../julia/01b/python/u_sin_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01b/python/u_step_full_py.npy | Bin 0 -> 480 bytes .../julia/01b/python/u_step_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01c/julia/boundary.jl | 90 ++ .../weno3/julia/01c/julia/domain.jl | 61 ++ .../julia/01c/julia/initial_condition.jl | 86 ++ .../weno3/julia/01c/julia/mesh.jl | 45 + .../julia/01c/julia/test/test_boundary.jl | 57 ++ .../weno3/julia/01c/julia/test/test_domain.jl | 44 + .../01c/julia/test/test_initial_condition.jl | 45 + .../weno3/julia/01c/julia/test/test_mesh.jl | 37 + .../weno3/julia/01c/python/boundary.py | 103 ++ .../weno3/julia/01c/python/cfd_registry.py | 62 ++ .../weno3/julia/01c/python/config.py | 41 + .../weno3/julia/01c/python/domain.py | 56 ++ .../01c/python/factories/base_factory.py | 49 + .../weno3/julia/01c/python/flux.py | 73 ++ .../01c/python/gen_boundary_test_data.py | 35 + .../julia/01c/python/gen_ic_test_data.py | 27 + .../julia/01c/python/initial_condition.py | 90 ++ .../weno3/julia/01c/python/mesh.py | 26 + .../weno3/julia/01c/python/plotter.py | 107 ++ .../01c/python/reconstructor/__init__.py | 4 + .../julia/01c/python/reconstructor/base.py | 7 + .../julia/01c/python/reconstructor/eno.py | 90 ++ .../julia/01c/python/reconstructor/factory.py | 42 + .../julia/01c/python/reconstructor/weno3.py | 88 ++ .../weno3/julia/01c/python/registry.py | 75 ++ .../weno3/julia/01c/python/residual.py | 40 + .../weno3/julia/01c/python/run_eno_weno.py | 52 + .../weno3/julia/01c/python/solution.py | 40 + .../weno3/julia/01c/python/solver.py | 86 ++ .../julia/01c/python/time_integration.py | 125 +++ .../weno3/julia/01c/python/u_dirichlet_py.npy | Bin 0 -> 480 bytes .../julia/01c/python/u_gaussian_full_py.npy | Bin 0 -> 480 bytes .../01c/python/u_gaussian_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01c/python/u_input.npy | Bin 0 -> 480 bytes .../weno3/julia/01c/python/u_neumann_py.npy | Bin 0 -> 480 bytes .../weno3/julia/01c/python/u_periodic_py.npy | Bin 0 -> 480 bytes .../weno3/julia/01c/python/u_sin_full_py.npy | Bin 0 -> 480 bytes .../julia/01c/python/u_sin_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01c/python/u_step_full_py.npy | Bin 0 -> 480 bytes .../julia/01c/python/u_step_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01d/julia/boundary.jl | 90 ++ .../weno3/julia/01d/julia/domain.jl | 61 ++ .../julia/01d/julia/initial_condition.jl | 86 ++ .../weno3/julia/01d/julia/mesh.jl | 45 + .../weno3/julia/01d/julia/solution.jl | 75 ++ .../julia/01d/julia/test/test_boundary.jl | 57 ++ .../weno3/julia/01d/julia/test/test_domain.jl | 44 + .../01d/julia/test/test_initial_condition.jl | 45 + .../weno3/julia/01d/julia/test/test_mesh.jl | 37 + .../julia/01d/julia/test/test_solution.jl | 42 + .../weno3/julia/01d/python/boundary.py | 103 ++ .../weno3/julia/01d/python/cfd_registry.py | 62 ++ .../weno3/julia/01d/python/config.py | 41 + .../weno3/julia/01d/python/domain.py | 56 ++ .../01d/python/factories/base_factory.py | 49 + .../weno3/julia/01d/python/flux.py | 73 ++ .../01d/python/gen_boundary_test_data.py | 35 + .../julia/01d/python/gen_ic_test_data.py | 27 + .../julia/01d/python/initial_condition.py | 90 ++ .../weno3/julia/01d/python/mesh.py | 26 + .../weno3/julia/01d/python/plotter.py | 107 ++ .../01d/python/reconstructor/__init__.py | 4 + .../julia/01d/python/reconstructor/base.py | 7 + .../julia/01d/python/reconstructor/eno.py | 90 ++ .../julia/01d/python/reconstructor/factory.py | 42 + .../julia/01d/python/reconstructor/weno3.py | 88 ++ .../weno3/julia/01d/python/registry.py | 75 ++ .../weno3/julia/01d/python/residual.py | 40 + .../weno3/julia/01d/python/run_eno_weno.py | 52 + .../weno3/julia/01d/python/solution.py | 40 + .../weno3/julia/01d/python/solver.py | 86 ++ .../julia/01d/python/time_integration.py | 125 +++ .../weno3/julia/01d/python/u_dirichlet_py.npy | Bin 0 -> 480 bytes .../julia/01d/python/u_gaussian_full_py.npy | Bin 0 -> 480 bytes .../01d/python/u_gaussian_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01d/python/u_input.npy | Bin 0 -> 480 bytes .../weno3/julia/01d/python/u_neumann_py.npy | Bin 0 -> 480 bytes .../weno3/julia/01d/python/u_periodic_py.npy | Bin 0 -> 480 bytes .../weno3/julia/01d/python/u_sin_full_py.npy | Bin 0 -> 480 bytes .../julia/01d/python/u_sin_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01d/python/u_step_full_py.npy | Bin 0 -> 480 bytes .../julia/01d/python/u_step_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01e/julia/boundary.jl | 90 ++ .../weno3/julia/01e/julia/domain.jl | 61 ++ .../weno3/julia/01e/julia/flux.jl | 70 ++ .../julia/01e/julia/initial_condition.jl | 86 ++ .../weno3/julia/01e/julia/mesh.jl | 45 + .../weno3/julia/01e/julia/solution.jl | 75 ++ .../julia/01e/julia/test/test_boundary.jl | 57 ++ .../weno3/julia/01e/julia/test/test_domain.jl | 44 + .../weno3/julia/01e/julia/test/test_flux.jl | 53 + .../01e/julia/test/test_initial_condition.jl | 45 + .../weno3/julia/01e/julia/test/test_mesh.jl | 37 + .../julia/01e/julia/test/test_solution.jl | 42 + .../weno3/julia/01e/python/boundary.py | 103 ++ .../weno3/julia/01e/python/cfd_registry.py | 62 ++ .../weno3/julia/01e/python/config.py | 41 + .../weno3/julia/01e/python/domain.py | 56 ++ .../01e/python/factories/base_factory.py | 49 + .../weno3/julia/01e/python/flux.py | 74 ++ .../01e/python/gen_boundary_test_data.py | 35 + .../julia/01e/python/gen_ic_test_data.py | 27 + .../julia/01e/python/initial_condition.py | 90 ++ .../weno3/julia/01e/python/mesh.py | 26 + .../weno3/julia/01e/python/plotter.py | 107 ++ .../01e/python/reconstructor/__init__.py | 4 + .../julia/01e/python/reconstructor/base.py | 7 + .../julia/01e/python/reconstructor/eno.py | 90 ++ .../julia/01e/python/reconstructor/factory.py | 42 + .../julia/01e/python/reconstructor/weno3.py | 88 ++ .../weno3/julia/01e/python/registry.py | 75 ++ .../weno3/julia/01e/python/residual.py | 40 + .../weno3/julia/01e/python/run_eno_weno.py | 52 + .../weno3/julia/01e/python/solution.py | 40 + .../weno3/julia/01e/python/solver.py | 86 ++ .../julia/01e/python/time_integration.py | 125 +++ .../weno3/julia/01e/python/u_dirichlet_py.npy | Bin 0 -> 480 bytes .../julia/01e/python/u_gaussian_full_py.npy | Bin 0 -> 480 bytes .../01e/python/u_gaussian_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01e/python/u_input.npy | Bin 0 -> 480 bytes .../weno3/julia/01e/python/u_neumann_py.npy | Bin 0 -> 480 bytes .../weno3/julia/01e/python/u_periodic_py.npy | Bin 0 -> 480 bytes .../weno3/julia/01e/python/u_sin_full_py.npy | Bin 0 -> 480 bytes .../julia/01e/python/u_sin_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01e/python/u_step_full_py.npy | Bin 0 -> 480 bytes .../julia/01e/python/u_step_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01f/julia/boundary.jl | 90 ++ .../weno3/julia/01f/julia/domain.jl | 61 ++ .../weno3/julia/01f/julia/flux.jl | 70 ++ .../julia/01f/julia/initial_condition.jl | 86 ++ .../weno3/julia/01f/julia/mesh.jl | 45 + .../weno3/julia/01f/julia/residual.jl | 65 ++ .../weno3/julia/01f/julia/solution.jl | 75 ++ .../julia/01f/julia/test/test_boundary.jl | 57 ++ .../weno3/julia/01f/julia/test/test_domain.jl | 44 + .../weno3/julia/01f/julia/test/test_flux.jl | 53 + .../01f/julia/test/test_initial_condition.jl | 45 + .../weno3/julia/01f/julia/test/test_mesh.jl | 37 + .../julia/01f/julia/test/test_residual.jl | 53 + .../julia/01f/julia/test/test_solution.jl | 42 + .../weno3/julia/01f/julia/test_residual.jl | 37 + .../weno3/julia/01f/python/boundary.py | 103 ++ .../weno3/julia/01f/python/cfd_registry.py | 62 ++ .../weno3/julia/01f/python/config.py | 41 + .../weno3/julia/01f/python/domain.py | 56 ++ .../01f/python/factories/base_factory.py | 49 + .../weno3/julia/01f/python/flux.py | 74 ++ .../01f/python/gen_boundary_test_data.py | 35 + .../julia/01f/python/gen_ic_test_data.py | 27 + .../julia/01f/python/initial_condition.py | 90 ++ .../weno3/julia/01f/python/mesh.py | 26 + .../weno3/julia/01f/python/plotter.py | 107 ++ .../01f/python/reconstructor/__init__.py | 4 + .../julia/01f/python/reconstructor/base.py | 7 + .../julia/01f/python/reconstructor/eno.py | 90 ++ .../julia/01f/python/reconstructor/factory.py | 42 + .../julia/01f/python/reconstructor/weno3.py | 88 ++ .../weno3/julia/01f/python/registry.py | 75 ++ .../weno3/julia/01f/python/residual.py | 40 + .../weno3/julia/01f/python/run_eno_weno.py | 52 + .../weno3/julia/01f/python/solution.py | 40 + .../weno3/julia/01f/python/solver.py | 86 ++ .../julia/01f/python/time_integration.py | 125 +++ .../weno3/julia/01f/python/u_dirichlet_py.npy | Bin 0 -> 480 bytes .../julia/01f/python/u_gaussian_full_py.npy | Bin 0 -> 480 bytes .../01f/python/u_gaussian_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01f/python/u_input.npy | Bin 0 -> 480 bytes .../weno3/julia/01f/python/u_neumann_py.npy | Bin 0 -> 480 bytes .../weno3/julia/01f/python/u_periodic_py.npy | Bin 0 -> 480 bytes .../weno3/julia/01f/python/u_sin_full_py.npy | Bin 0 -> 480 bytes .../julia/01f/python/u_sin_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01f/python/u_step_full_py.npy | Bin 0 -> 480 bytes .../julia/01f/python/u_step_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01g/julia/boundary.jl | 90 ++ .../weno3/julia/01g/julia/domain.jl | 61 ++ .../weno3/julia/01g/julia/flux.jl | 70 ++ .../julia/01g/julia/initial_condition.jl | 86 ++ .../weno3/julia/01g/julia/mesh.jl | 45 + .../weno3/julia/01g/julia/residual.jl | 65 ++ .../weno3/julia/01g/julia/solution.jl | 75 ++ .../julia/01g/julia/test/test_boundary.jl | 57 ++ .../weno3/julia/01g/julia/test/test_domain.jl | 44 + .../weno3/julia/01g/julia/test/test_flux.jl | 53 + .../01g/julia/test/test_initial_condition.jl | 45 + .../weno3/julia/01g/julia/test/test_mesh.jl | 37 + .../julia/01g/julia/test/test_residual.jl | 53 + .../julia/01g/julia/test/test_solution.jl | 42 + .../01g/julia/test/test_time_integration.jl | 53 + .../weno3/julia/01g/julia/test_residual.jl | 37 + .../weno3/julia/01g/julia/time_integration.jl | 130 +++ .../weno3/julia/01g/python/boundary.py | 103 ++ .../weno3/julia/01g/python/cfd_registry.py | 62 ++ .../weno3/julia/01g/python/config.py | 41 + .../weno3/julia/01g/python/domain.py | 56 ++ .../01g/python/factories/base_factory.py | 49 + .../weno3/julia/01g/python/flux.py | 74 ++ .../01g/python/gen_boundary_test_data.py | 35 + .../julia/01g/python/gen_ic_test_data.py | 27 + .../julia/01g/python/initial_condition.py | 90 ++ .../weno3/julia/01g/python/mesh.py | 26 + .../weno3/julia/01g/python/plotter.py | 107 ++ .../01g/python/reconstructor/__init__.py | 4 + .../julia/01g/python/reconstructor/base.py | 7 + .../julia/01g/python/reconstructor/eno.py | 90 ++ .../julia/01g/python/reconstructor/factory.py | 42 + .../julia/01g/python/reconstructor/weno3.py | 88 ++ .../weno3/julia/01g/python/registry.py | 75 ++ .../weno3/julia/01g/python/residual.py | 40 + .../weno3/julia/01g/python/run_eno_weno.py | 52 + .../weno3/julia/01g/python/solution.py | 40 + .../weno3/julia/01g/python/solver.py | 86 ++ .../julia/01g/python/time_integration.py | 125 +++ .../weno3/julia/01g/python/u_dirichlet_py.npy | Bin 0 -> 480 bytes .../julia/01g/python/u_gaussian_full_py.npy | Bin 0 -> 480 bytes .../01g/python/u_gaussian_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01g/python/u_input.npy | Bin 0 -> 480 bytes .../weno3/julia/01g/python/u_neumann_py.npy | Bin 0 -> 480 bytes .../weno3/julia/01g/python/u_periodic_py.npy | Bin 0 -> 480 bytes .../weno3/julia/01g/python/u_sin_full_py.npy | Bin 0 -> 480 bytes .../julia/01g/python/u_sin_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01g/python/u_step_full_py.npy | Bin 0 -> 480 bytes .../julia/01g/python/u_step_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01h/julia/boundary.jl | 90 ++ .../weno3/julia/01h/julia/domain.jl | 61 ++ .../weno3/julia/01h/julia/flux.jl | 70 ++ .../julia/01h/julia/initial_condition.jl | 86 ++ .../weno3/julia/01h/julia/mesh.jl | 45 + .../julia/01h/julia/reconstructor/weno3.jl | 65 ++ .../weno3/julia/01h/julia/residual.jl | 65 ++ .../weno3/julia/01h/julia/solution.jl | 75 ++ .../julia/01h/julia/test/test_boundary.jl | 57 ++ .../weno3/julia/01h/julia/test/test_domain.jl | 44 + .../weno3/julia/01h/julia/test/test_flux.jl | 53 + .../01h/julia/test/test_initial_condition.jl | 45 + .../weno3/julia/01h/julia/test/test_mesh.jl | 37 + .../julia/01h/julia/test/test_residual.jl | 53 + .../julia/01h/julia/test/test_solution.jl | 42 + .../01h/julia/test/test_time_integration.jl | 53 + .../weno3/julia/01h/julia/test/test_weno3.jl | 47 + .../weno3/julia/01h/julia/test_residual.jl | 37 + .../weno3/julia/01h/julia/time_integration.jl | 130 +++ .../weno3/julia/01h/python/boundary.py | 103 ++ .../weno3/julia/01h/python/cfd_registry.py | 62 ++ .../weno3/julia/01h/python/config.py | 41 + .../weno3/julia/01h/python/domain.py | 56 ++ .../01h/python/factories/base_factory.py | 49 + .../weno3/julia/01h/python/flux.py | 74 ++ .../01h/python/gen_boundary_test_data.py | 35 + .../julia/01h/python/gen_ic_test_data.py | 27 + .../julia/01h/python/initial_condition.py | 90 ++ .../weno3/julia/01h/python/mesh.py | 26 + .../weno3/julia/01h/python/plotter.py | 107 ++ .../01h/python/reconstructor/__init__.py | 4 + .../julia/01h/python/reconstructor/base.py | 7 + .../julia/01h/python/reconstructor/eno.py | 90 ++ .../julia/01h/python/reconstructor/factory.py | 42 + .../julia/01h/python/reconstructor/weno3.py | 59 ++ .../weno3/julia/01h/python/registry.py | 75 ++ .../weno3/julia/01h/python/residual.py | 40 + .../weno3/julia/01h/python/run_eno_weno.py | 52 + .../weno3/julia/01h/python/solution.py | 40 + .../weno3/julia/01h/python/solver.py | 86 ++ .../julia/01h/python/time_integration.py | 125 +++ .../weno3/julia/01h/python/u_dirichlet_py.npy | Bin 0 -> 480 bytes .../julia/01h/python/u_gaussian_full_py.npy | Bin 0 -> 480 bytes .../01h/python/u_gaussian_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01h/python/u_input.npy | Bin 0 -> 480 bytes .../weno3/julia/01h/python/u_neumann_py.npy | Bin 0 -> 480 bytes .../weno3/julia/01h/python/u_periodic_py.npy | Bin 0 -> 480 bytes .../weno3/julia/01h/python/u_sin_full_py.npy | Bin 0 -> 480 bytes .../julia/01h/python/u_sin_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01h/python/u_step_full_py.npy | Bin 0 -> 480 bytes .../julia/01h/python/u_step_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01i/julia/boundary.jl | 90 ++ .../weno3/julia/01i/julia/domain.jl | 61 ++ .../weno3/julia/01i/julia/flux.jl | 70 ++ .../julia/01i/julia/initial_condition.jl | 86 ++ .../weno3/julia/01i/julia/mesh.jl | 45 + .../julia/01i/julia/reconstructor/eno.jl | 107 ++ .../julia/01i/julia/reconstructor/weno3.jl | 65 ++ .../weno3/julia/01i/julia/residual.jl | 65 ++ .../weno3/julia/01i/julia/solution.jl | 75 ++ .../julia/01i/julia/test/test_boundary.jl | 57 ++ .../weno3/julia/01i/julia/test/test_domain.jl | 44 + .../weno3/julia/01i/julia/test/test_eno.jl | 34 + .../weno3/julia/01i/julia/test/test_flux.jl | 53 + .../01i/julia/test/test_initial_condition.jl | 45 + .../weno3/julia/01i/julia/test/test_mesh.jl | 37 + .../julia/01i/julia/test/test_residual.jl | 53 + .../julia/01i/julia/test/test_solution.jl | 42 + .../01i/julia/test/test_time_integration.jl | 53 + .../weno3/julia/01i/julia/test/test_weno3.jl | 47 + .../weno3/julia/01i/julia/test_residual.jl | 37 + .../weno3/julia/01i/julia/time_integration.jl | 130 +++ .../weno3/julia/01i/python/boundary.py | 103 ++ .../weno3/julia/01i/python/cfd_registry.py | 62 ++ .../weno3/julia/01i/python/config.py | 41 + .../weno3/julia/01i/python/domain.py | 56 ++ .../julia/01i/python/eno_weno_comparison.png | Bin 0 -> 224091 bytes .../01i/python/factories/base_factory.py | 49 + .../weno3/julia/01i/python/flux.py | 74 ++ .../01i/python/gen_boundary_test_data.py | 35 + .../julia/01i/python/gen_ic_test_data.py | 27 + .../julia/01i/python/initial_condition.py | 90 ++ .../weno3/julia/01i/python/mesh.py | 26 + .../weno3/julia/01i/python/plotter.py | 107 ++ .../01i/python/reconstructor/__init__.py | 4 + .../julia/01i/python/reconstructor/base.py | 7 + .../julia/01i/python/reconstructor/eno.py | 90 ++ .../julia/01i/python/reconstructor/factory.py | 42 + .../julia/01i/python/reconstructor/weno3.py | 59 ++ .../weno3/julia/01i/python/registry.py | 75 ++ .../weno3/julia/01i/python/residual.py | 40 + .../weno3/julia/01i/python/run_eno_weno.py | 52 + .../weno3/julia/01i/python/solution.py | 40 + .../weno3/julia/01i/python/solver.py | 86 ++ .../julia/01i/python/time_integration.py | 125 +++ .../weno3/julia/01i/python/u_dirichlet_py.npy | Bin 0 -> 480 bytes .../julia/01i/python/u_gaussian_full_py.npy | Bin 0 -> 480 bytes .../01i/python/u_gaussian_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01i/python/u_input.npy | Bin 0 -> 480 bytes .../weno3/julia/01i/python/u_neumann_py.npy | Bin 0 -> 480 bytes .../weno3/julia/01i/python/u_periodic_py.npy | Bin 0 -> 480 bytes .../weno3/julia/01i/python/u_sin_full_py.npy | Bin 0 -> 480 bytes .../julia/01i/python/u_sin_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01i/python/u_step_full_py.npy | Bin 0 -> 480 bytes .../julia/01i/python/u_step_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01j/julia/boundary.jl | 90 ++ .../weno3/julia/01j/julia/config.jl | 68 ++ .../weno3/julia/01j/julia/domain.jl | 61 ++ .../weno3/julia/01j/julia/flux.jl | 70 ++ .../julia/01j/julia/initial_condition.jl | 86 ++ .../weno3/julia/01j/julia/mesh.jl | 45 + .../weno3/julia/01j/julia/plotter.jl | 147 +++ .../julia/01j/julia/reconstructor/eno.jl | 107 ++ .../julia/01j/julia/reconstructor/weno3.jl | 65 ++ .../weno3/julia/01j/julia/residual.jl | 65 ++ .../weno3/julia/01j/julia/run_eno_weno.jl | 50 + .../weno3/julia/01j/julia/solution.jl | 75 ++ .../weno3/julia/01j/julia/solver.jl | 167 ++++ .../julia/01j/julia/test/test_boundary.jl | 57 ++ .../weno3/julia/01j/julia/test/test_domain.jl | 44 + .../weno3/julia/01j/julia/test/test_eno.jl | 34 + .../weno3/julia/01j/julia/test/test_flux.jl | 53 + .../01j/julia/test/test_initial_condition.jl | 45 + .../weno3/julia/01j/julia/test/test_mesh.jl | 37 + .../julia/01j/julia/test/test_residual.jl | 53 + .../julia/01j/julia/test/test_solution.jl | 42 + .../01j/julia/test/test_time_integration.jl | 53 + .../weno3/julia/01j/julia/test/test_weno3.jl | 47 + .../weno3/julia/01j/julia/test_residual.jl | 37 + .../weno3/julia/01j/julia/time_integration.jl | 130 +++ .../weno3/julia/01j/python/boundary.py | 103 ++ .../weno3/julia/01j/python/cfd_registry.py | 62 ++ .../weno3/julia/01j/python/config.py | 41 + .../weno3/julia/01j/python/domain.py | 56 ++ .../julia/01j/python/eno_weno_comparison.png | Bin 0 -> 224091 bytes .../01j/python/factories/base_factory.py | 49 + .../weno3/julia/01j/python/flux.py | 74 ++ .../01j/python/gen_boundary_test_data.py | 35 + .../julia/01j/python/gen_ic_test_data.py | 27 + .../julia/01j/python/initial_condition.py | 90 ++ .../weno3/julia/01j/python/mesh.py | 26 + .../weno3/julia/01j/python/plotter.py | 109 ++ .../01j/python/reconstructor/__init__.py | 4 + .../julia/01j/python/reconstructor/base.py | 7 + .../julia/01j/python/reconstructor/eno.py | 90 ++ .../julia/01j/python/reconstructor/factory.py | 42 + .../julia/01j/python/reconstructor/weno3.py | 59 ++ .../weno3/julia/01j/python/registry.py | 75 ++ .../weno3/julia/01j/python/residual.py | 40 + .../weno3/julia/01j/python/run_eno_weno.py | 54 + .../weno3/julia/01j/python/solution.py | 40 + .../weno3/julia/01j/python/solver.py | 85 ++ .../julia/01j/python/time_integration.py | 125 +++ .../weno3/julia/01j/python/u_dirichlet_py.npy | Bin 0 -> 480 bytes .../julia/01j/python/u_gaussian_full_py.npy | Bin 0 -> 480 bytes .../01j/python/u_gaussian_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01j/python/u_input.npy | Bin 0 -> 480 bytes .../weno3/julia/01j/python/u_neumann_py.npy | Bin 0 -> 480 bytes .../weno3/julia/01j/python/u_periodic_py.npy | Bin 0 -> 480 bytes .../weno3/julia/01j/python/u_sin_full_py.npy | Bin 0 -> 480 bytes .../julia/01j/python/u_sin_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/01j/python/u_step_full_py.npy | Bin 0 -> 480 bytes .../julia/01j/python/u_step_interior_py.npy | Bin 0 -> 448 bytes .../weno3/julia/02/julia/boundary.jl | 90 ++ .../weno3/julia/02/julia/config.jl | 68 ++ .../weno3/julia/02/julia/domain.jl | 61 ++ .../weno3/julia/02/julia/flux.jl | 70 ++ .../weno3/julia/02/julia/initial_condition.jl | 86 ++ .../weno3/julia/02/julia/mesh.jl | 45 + .../weno3/julia/02/julia/plotter.jl | 147 +++ .../weno3/julia/02/julia/reconstructor/eno.jl | 107 ++ .../julia/02/julia/reconstructor/weno3.jl | 65 ++ .../weno3/julia/02/julia/residual.jl | 65 ++ .../weno3/julia/02/julia/run_eno_weno.jl | 50 + .../weno3/julia/02/julia/solution.jl | 75 ++ .../weno3/julia/02/julia/solver.jl | 168 ++++ .../weno3/julia/02/julia/time_integration.jl | 168 ++++ .../weno3/julia/02/python/boundary.py | 103 ++ .../weno3/julia/02/python/cfd_registry.py | 62 ++ .../weno3/julia/02/python/config.py | 41 + .../weno3/julia/02/python/domain.py | 56 ++ .../julia/02/python/factories/base_factory.py | 49 + .../weno3/julia/02/python/flux.py | 74 ++ .../julia/02/python/gen_boundary_test_data.py | 35 + .../weno3/julia/02/python/gen_ic_test_data.py | 27 + .../julia/02/python/initial_condition.py | 90 ++ .../weno3/julia/02/python/mesh.py | 26 + .../weno3/julia/02/python/plotter.py | 109 ++ .../julia/02/python/reconstructor/__init__.py | 4 + .../julia/02/python/reconstructor/base.py | 7 + .../julia/02/python/reconstructor/eno.py | 90 ++ .../julia/02/python/reconstructor/factory.py | 42 + .../julia/02/python/reconstructor/weno3.py | 59 ++ .../weno3/julia/02/python/registry.py | 75 ++ .../weno3/julia/02/python/residual.py | 40 + .../weno3/julia/02/python/run_eno_weno.py | 54 + .../weno3/julia/02/python/solution.py | 40 + .../weno3/julia/02/python/solver.py | 85 ++ .../weno3/julia/02/python/time_integration.py | 125 +++ .../weno3/julia/02a/julia/boundary.jl | 90 ++ .../weno3/julia/02a/julia/config.jl | 68 ++ .../weno3/julia/02a/julia/domain.jl | 61 ++ .../weno3/julia/02a/julia/flux.jl | 116 +++ .../julia/02a/julia/initial_condition.jl | 86 ++ .../weno3/julia/02a/julia/mesh.jl | 45 + .../weno3/julia/02a/julia/plotter.jl | 147 +++ .../julia/02a/julia/reconstructor/eno.jl | 107 ++ .../julia/02a/julia/reconstructor/weno3.jl | 65 ++ .../weno3/julia/02a/julia/residual.jl | 65 ++ .../weno3/julia/02a/julia/run_eno_weno.jl | 50 + .../weno3/julia/02a/julia/solution.jl | 75 ++ .../weno3/julia/02a/julia/solver.jl | 169 ++++ .../weno3/julia/02a/julia/time_integration.jl | 168 ++++ .../weno3/julia/02a/python/boundary.py | 103 ++ .../weno3/julia/02a/python/cfd_registry.py | 62 ++ .../weno3/julia/02a/python/config.py | 41 + .../weno3/julia/02a/python/domain.py | 56 ++ .../02a/python/factories/base_factory.py | 49 + .../weno3/julia/02a/python/flux.py | 74 ++ .../julia/02a/python/initial_condition.py | 90 ++ .../weno3/julia/02a/python/mesh.py | 26 + .../weno3/julia/02a/python/plotter.py | 109 ++ .../02a/python/reconstructor/__init__.py | 4 + .../julia/02a/python/reconstructor/base.py | 7 + .../julia/02a/python/reconstructor/eno.py | 90 ++ .../julia/02a/python/reconstructor/factory.py | 42 + .../julia/02a/python/reconstructor/weno3.py | 59 ++ .../weno3/julia/02a/python/registry.py | 75 ++ .../weno3/julia/02a/python/residual.py | 40 + .../weno3/julia/02a/python/run_eno_weno.py | 54 + .../weno3/julia/02a/python/solution.py | 40 + .../weno3/julia/02a/python/solver.py | 85 ++ .../julia/02a/python/time_integration.py | 125 +++ .../weno3/julia/02b/boundary.jl | 90 ++ .../weno3/julia/02b/config.jl | 68 ++ .../weno3/julia/02b/domain.jl | 61 ++ .../weno3/julia/02b/flux.jl | 112 +++ .../weno3/julia/02b/initial_condition.jl | 86 ++ .../weno3/julia/02b/mesh.jl | 45 + .../weno3/julia/02b/plotter.jl | 147 +++ .../weno3/julia/02b/reconstructor/eno.jl | 107 ++ .../weno3/julia/02b/reconstructor/weno3.jl | 65 ++ .../weno3/julia/02b/residual.jl | 65 ++ .../weno3/julia/02b/run_eno_weno.jl | 50 + .../weno3/julia/02b/solution.jl | 75 ++ .../weno3/julia/02b/solver.jl | 172 ++++ .../weno3/julia/02b/time_integration.jl | 168 ++++ .../weno3/julia/02c/boundary.jl | 90 ++ .../weno3/julia/02c/config.jl | 68 ++ .../weno3/julia/02c/domain.jl | 61 ++ .../weno3/julia/02c/flux.jl | 112 +++ .../weno3/julia/02c/initial_condition.jl | 86 ++ .../weno3/julia/02c/mesh.jl | 45 + .../weno3/julia/02c/plotter.jl | 147 +++ .../weno3/julia/02c/reconstructor/eno.jl | 107 ++ .../weno3/julia/02c/reconstructor/weno3.jl | 65 ++ .../weno3/julia/02c/residual.jl | 65 ++ .../weno3/julia/02c/run_eno_weno.jl | 50 + .../weno3/julia/02c/solution.jl | 75 ++ .../weno3/julia/02c/solver.jl | 171 ++++ .../weno3/julia/02c/time_integration.jl | 169 ++++ .../weno3/julia/02d/boundary.jl | 129 +++ .../weno3/julia/02d/config.jl | 68 ++ .../weno3/julia/02d/domain.jl | 61 ++ .../weno3/julia/02d/flux.jl | 112 +++ .../weno3/julia/02d/initial_condition.jl | 86 ++ .../weno3/julia/02d/mesh.jl | 45 + .../weno3/julia/02d/plotter.jl | 147 +++ .../weno3/julia/02d/reconstructor/eno.jl | 107 ++ .../weno3/julia/02d/reconstructor/weno3.jl | 65 ++ .../weno3/julia/02d/residual.jl | 65 ++ .../weno3/julia/02d/run_eno_weno.jl | 50 + .../weno3/julia/02d/solution.jl | 75 ++ .../weno3/julia/02d/solver.jl | 167 ++++ .../weno3/julia/02d/time_integration.jl | 169 ++++ .../weno3/julia/02e/boundary.jl | 129 +++ .../weno3/julia/02e/config.jl | 68 ++ .../weno3/julia/02e/domain.jl | 61 ++ .../weno3/julia/02e/flux.jl | 112 +++ .../weno3/julia/02e/initial_condition.jl | 120 +++ .../weno3/julia/02e/mesh.jl | 45 + .../weno3/julia/02e/plotter.jl | 147 +++ .../weno3/julia/02e/reconstructor/eno.jl | 107 ++ .../weno3/julia/02e/reconstructor/weno3.jl | 65 ++ .../weno3/julia/02e/residual.jl | 65 ++ .../weno3/julia/02e/run_eno_weno.jl | 50 + .../weno3/julia/02e/solution.jl | 64 ++ .../weno3/julia/02e/solver.jl | 167 ++++ .../weno3/julia/02e/time_integration.jl | 169 ++++ .../weno3/julia/02f/boundary.jl | 129 +++ .../weno3/julia/02f/config.jl | 68 ++ .../weno3/julia/02f/domain.jl | 61 ++ .../weno3/julia/02f/eno_weno_comparison.png | Bin 0 -> 225919 bytes .../weno3/julia/02f/flux.jl | 112 +++ .../weno3/julia/02f/initial_condition.jl | 120 +++ .../weno3/julia/02f/mesh.jl | 45 + .../weno3/julia/02f/plotter.jl | 147 +++ .../weno3/julia/02f/reconstructor/eno.jl | 107 ++ .../weno3/julia/02f/reconstructor/factory.jl | 41 + .../weno3/julia/02f/reconstructor/weno3.jl | 65 ++ .../weno3/julia/02f/residual.jl | 65 ++ .../weno3/julia/02f/run_eno_weno.jl | 50 + .../weno3/julia/02f/solution.jl | 64 ++ .../weno3/julia/02f/solver.jl | 152 +++ .../weno3/julia/02f/time_integration.jl | 169 ++++ .../weno3/julia/02g/boundary.jl | 129 +++ .../weno3/julia/02g/config.jl | 68 ++ .../weno3/julia/02g/domain.jl | 61 ++ .../weno3/julia/02g/flux.jl | 112 +++ .../weno3/julia/02g/initial_condition.jl | 120 +++ .../weno3/julia/02g/mesh.jl | 45 + .../weno3/julia/02g/plotter.jl | 147 +++ .../weno3/julia/02g/reconstructor/eno.jl | 107 ++ .../weno3/julia/02g/reconstructor/factory.jl | 41 + .../weno3/julia/02g/reconstructor/weno3.jl | 65 ++ .../weno3/julia/02g/residual.jl | 69 ++ .../weno3/julia/02g/run_eno_weno.jl | 50 + .../weno3/julia/02g/solution.jl | 64 ++ .../weno3/julia/02g/solver.jl | 130 +++ .../weno3/julia/02g/time_integration.jl | 169 ++++ .../weno3/julia/03/examples/run_eno_weno.jl | 50 + .../weno3/julia/03/src/boundary.jl | 121 +++ .../weno3/julia/03/src/config.jl | 76 ++ .../weno3/julia/03/src/domain.jl | 57 ++ .../weno3/julia/03/src/flux.jl | 112 +++ .../weno3/julia/03/src/initial_condition.jl | 112 +++ .../weno3/julia/03/src/mesh.jl | 45 + .../weno3/julia/03/src/plotter.jl | 147 +++ .../weno3/julia/03/src/reconstructor/eno.jl | 107 ++ .../julia/03/src/reconstructor/factory.jl | 41 + .../weno3/julia/03/src/reconstructor/weno3.jl | 65 ++ .../weno3/julia/03/src/registry.jl | 164 +++ .../weno3/julia/03/src/residual.jl | 69 ++ .../weno3/julia/03/src/solution.jl | 60 ++ .../weno3/julia/03/src/solver.jl | 130 +++ .../weno3/julia/03/src/time_integration.jl | 169 ++++ .../weno3/julia/03a/examples/run_eno_weno.jl | 50 + .../weno3/julia/03a/src/boundary.jl | 121 +++ .../weno3/julia/03a/src/config.jl | 69 ++ .../weno3/julia/03a/src/domain.jl | 58 ++ .../weno3/julia/03a/src/flux.jl | 112 +++ .../weno3/julia/03a/src/initial_condition.jl | 112 +++ .../weno3/julia/03a/src/mesh.jl | 45 + .../weno3/julia/03a/src/plotter.jl | 147 +++ .../weno3/julia/03a/src/reconstructor/eno.jl | 107 ++ .../julia/03a/src/reconstructor/factory.jl | 38 + .../julia/03a/src/reconstructor/weno3.jl | 65 ++ .../weno3/julia/03a/src/registry.jl | 164 +++ .../weno3/julia/03a/src/residual.jl | 69 ++ .../weno3/julia/03a/src/solution.jl | 60 ++ .../weno3/julia/03a/src/solver.jl | 130 +++ .../weno3/julia/03a/src/time_integration.jl | 169 ++++ .../weno3/julia/03a/src/utils.jl | 9 + .../weno3/julia/03b/examples/run_eno_weno.jl | 50 + .../weno3/julia/03b/src/boundary.jl | 121 +++ .../weno3/julia/03b/src/config.jl | 69 ++ .../weno3/julia/03b/src/domain.jl | 58 ++ .../weno3/julia/03b/src/flux/base.jl | 7 + .../julia/03b/src/flux/engquist_osher.jl | 25 + .../weno3/julia/03b/src/flux/factory.jl | 26 + .../weno3/julia/03b/src/flux/flux.jl | 10 + .../weno3/julia/03b/src/flux/rusanov.jl | 28 + .../weno3/julia/03b/src/initial_condition.jl | 112 +++ .../weno3/julia/03b/src/mesh.jl | 45 + .../weno3/julia/03b/src/plotter.jl | 147 +++ .../weno3/julia/03b/src/reconstructor/eno.jl | 107 ++ .../julia/03b/src/reconstructor/factory.jl | 38 + .../julia/03b/src/reconstructor/weno3.jl | 65 ++ .../weno3/julia/03b/src/registry.jl | 164 +++ .../weno3/julia/03b/src/residual.jl | 69 ++ .../weno3/julia/03b/src/solution.jl | 60 ++ .../weno3/julia/03b/src/solver.jl | 130 +++ .../weno3/julia/03b/src/time_integration.jl | 169 ++++ .../weno3/julia/03b/src/utils.jl | 9 + .../weno3/julia/03c/examples/run_eno_weno.jl | 50 + .../weno3/julia/03c/src/boundary.jl | 121 +++ .../weno3/julia/03c/src/config.jl | 69 ++ .../weno3/julia/03c/src/domain.jl | 58 ++ .../weno3/julia/03c/src/flux/base.jl | 7 + .../julia/03c/src/flux/engquist_osher.jl | 25 + .../weno3/julia/03c/src/flux/factory.jl | 26 + .../weno3/julia/03c/src/flux/flux.jl | 10 + .../weno3/julia/03c/src/flux/rusanov.jl | 28 + .../weno3/julia/03c/src/initial_condition.jl | 112 +++ .../weno3/julia/03c/src/mesh.jl | 45 + .../weno3/julia/03c/src/plotter.jl | 147 +++ .../weno3/julia/03c/src/reconstructor/eno.jl | 107 ++ .../julia/03c/src/reconstructor/factory.jl | 38 + .../julia/03c/src/reconstructor/weno3.jl | 65 ++ .../weno3/julia/03c/src/registry.jl | 164 +++ .../weno3/julia/03c/src/residual.jl | 69 ++ .../weno3/julia/03c/src/solution.jl | 60 ++ .../weno3/julia/03c/src/solver.jl | 129 +++ .../julia/03c/src/time_integration/base.jl | 33 + .../julia/03c/src/time_integration/factory.jl | 29 + .../julia/03c/src/time_integration/rk1.jl | 19 + .../julia/03c/src/time_integration/rk2.jl | 30 + .../julia/03c/src/time_integration/rk3.jl | 44 + .../src/time_integration/time_integration.jl | 7 + .../weno3/julia/03c/src/utils.jl | 9 + .../weno3/julia/03d/examples/run_eno_weno.jl | 50 + .../weno3/julia/03d/src/boundary.jl | 121 +++ .../weno3/julia/03d/src/config.jl | 73 ++ .../weno3/julia/03d/src/domain.jl | 58 ++ .../weno3/julia/03d/src/equations/base.jl | 28 + .../julia/03d/src/equations/equations.jl | 4 + .../03d/src/equations/linear_advection.jl | 16 + .../weno3/julia/03d/src/flux/base.jl | 7 + .../julia/03d/src/flux/engquist_osher.jl | 25 + .../weno3/julia/03d/src/flux/factory.jl | 26 + .../weno3/julia/03d/src/flux/flux.jl | 10 + .../weno3/julia/03d/src/flux/rusanov.jl | 28 + .../weno3/julia/03d/src/initial_condition.jl | 112 +++ .../weno3/julia/03d/src/mesh.jl | 45 + .../weno3/julia/03d/src/plotter.jl | 147 +++ .../weno3/julia/03d/src/problems/base.jl | 22 + .../03d/src/problems/linear_advection.jl | 43 + .../weno3/julia/03d/src/problems/problems.jl | 4 + .../weno3/julia/03d/src/reconstructor/eno.jl | 107 ++ .../julia/03d/src/reconstructor/factory.jl | 38 + .../julia/03d/src/reconstructor/weno3.jl | 65 ++ .../weno3/julia/03d/src/registry.jl | 164 +++ .../weno3/julia/03d/src/residual.jl | 69 ++ .../weno3/julia/03d/src/solution.jl | 60 ++ .../weno3/julia/03d/src/solver.jl | 162 +++ .../julia/03d/src/time_integration/base.jl | 33 + .../julia/03d/src/time_integration/factory.jl | 29 + .../julia/03d/src/time_integration/rk1.jl | 19 + .../julia/03d/src/time_integration/rk2.jl | 30 + .../julia/03d/src/time_integration/rk3.jl | 44 + .../src/time_integration/time_integration.jl | 7 + .../weno3/julia/03d/src/utils.jl | 9 + .../weno3/julia/03f/examples/run_eno_weno.jl | 99 ++ .../weno3/julia/03f/src/boundary.jl | 121 +++ .../weno3/julia/03f/src/config.jl | 74 ++ .../weno3/julia/03f/src/domain.jl | 58 ++ .../weno3/julia/03f/src/equations/base.jl | 28 + .../julia/03f/src/equations/equations.jl | 4 + .../03f/src/equations/linear_advection.jl | 16 + .../weno3/julia/03f/src/flux/base.jl | 7 + .../julia/03f/src/flux/engquist_osher.jl | 26 + .../weno3/julia/03f/src/flux/factory.jl | 26 + .../weno3/julia/03f/src/flux/flux.jl | 10 + .../weno3/julia/03f/src/flux/rusanov.jl | 29 + .../weno3/julia/03f/src/initial_condition.jl | 112 +++ .../weno3/julia/03f/src/mesh.jl | 45 + .../weno3/julia/03f/src/plotter.jl | 147 +++ .../weno3/julia/03f/src/problems/base.jl | 22 + .../03f/src/problems/linear_advection.jl | 43 + .../weno3/julia/03f/src/problems/problems.jl | 4 + .../weno3/julia/03f/src/reconstructor/eno.jl | 107 ++ .../julia/03f/src/reconstructor/factory.jl | 46 + .../julia/03f/src/reconstructor/weno3.jl | 65 ++ .../julia/03f/src/reconstructor/weno5.jl | 77 ++ .../weno3/julia/03f/src/registry.jl | 164 +++ .../weno3/julia/03f/src/residual.jl | 69 ++ .../weno3/julia/03f/src/solution.jl | 60 ++ .../weno3/julia/03f/src/solver.jl | 163 +++ .../julia/03f/src/time_integration/base.jl | 33 + .../julia/03f/src/time_integration/factory.jl | 29 + .../julia/03f/src/time_integration/rk1.jl | 19 + .../julia/03f/src/time_integration/rk2.jl | 30 + .../julia/03f/src/time_integration/rk3.jl | 44 + .../src/time_integration/time_integration.jl | 7 + .../weno3/julia/03f/src/utils.jl | 9 + .../weno3/julia/03g/examples/run_eno_weno.jl | 99 ++ .../weno3/julia/03g/src/boundary.jl | 121 +++ .../weno3/julia/03g/src/config.jl | 74 ++ .../weno3/julia/03g/src/domain.jl | 58 ++ .../weno3/julia/03g/src/equations/base.jl | 28 + .../julia/03g/src/equations/equations.jl | 4 + .../03g/src/equations/linear_advection.jl | 16 + .../weno3/julia/03g/src/flux/base.jl | 7 + .../julia/03g/src/flux/engquist_osher.jl | 26 + .../weno3/julia/03g/src/flux/factory.jl | 26 + .../weno3/julia/03g/src/flux/flux.jl | 10 + .../weno3/julia/03g/src/flux/rusanov.jl | 29 + .../weno3/julia/03g/src/initial_condition.jl | 112 +++ .../weno3/julia/03g/src/mesh.jl | 45 + .../weno3/julia/03g/src/plotter.jl | 147 +++ .../weno3/julia/03g/src/problems/base.jl | 22 + .../03g/src/problems/linear_advection.jl | 43 + .../weno3/julia/03g/src/problems/problems.jl | 4 + .../weno3/julia/03g/src/reconstructor/eno.jl | 114 +++ .../julia/03g/src/reconstructor/factory.jl | 50 + .../julia/03g/src/reconstructor/weno3.jl | 68 ++ .../julia/03g/src/reconstructor/weno5.jl | 80 ++ .../weno3/julia/03g/src/registry.jl | 164 +++ .../weno3/julia/03g/src/residual.jl | 69 ++ .../weno3/julia/03g/src/solution.jl | 60 ++ .../weno3/julia/03g/src/solver.jl | 165 +++ .../julia/03g/src/time_integration/base.jl | 33 + .../julia/03g/src/time_integration/factory.jl | 29 + .../julia/03g/src/time_integration/rk1.jl | 19 + .../julia/03g/src/time_integration/rk2.jl | 30 + .../julia/03g/src/time_integration/rk3.jl | 44 + .../src/time_integration/time_integration.jl | 7 + .../weno3/julia/03g/src/utils.jl | 9 + .../weno3/python-v1/01/boundary.py | 104 ++ .../weno3/python-v1/01/cfd_registry.py | 62 ++ .../weno3/python-v1/01/config.py | 41 + .../weno3/python-v1/01/domain.py | 56 ++ .../python-v1/01/factories/base_factory.py | 49 + .../weno3/python-v1/01/flux/__init__.py | 7 + .../weno3/python-v1/01/flux/base.py | 25 + .../weno3/python-v1/01/flux/engquist_osher.py | 19 + .../weno3/python-v1/01/flux/factory.py | 25 + .../weno3/python-v1/01/flux/rusanov.py | 21 + .../weno3/python-v1/01/initial_condition.py | 107 ++ .../weno3/python-v1/01/mesh.py | 26 + .../weno3/python-v1/01/plotter.py | 109 ++ .../weno3/python-v1/01/problem.py | 38 + .../python-v1/01/problems/linear_advection.py | 27 + .../python-v1/01/reconstructor/__init__.py | 4 + .../weno3/python-v1/01/reconstructor/base.py | 7 + .../weno3/python-v1/01/reconstructor/eno.py | 90 ++ .../python-v1/01/reconstructor/factory.py | 42 + .../weno3/python-v1/01/reconstructor/weno3.py | 59 ++ .../weno3/python-v1/01/registry.py | 75 ++ .../weno3/python-v1/01/residual.py | 40 + .../weno3/python-v1/01/result.py | 21 + .../weno3/python-v1/01/run_eno_weno.py | 52 + .../weno3/python-v1/01/solution.py | 40 + .../weno3/python-v1/01/solver.py | 44 + .../weno3/python-v1/01/time_integration.py | 126 +++ .../weno3/python-v1/01a/boundary.py | 104 ++ .../weno3/python-v1/01a/cfd_registry.py | 62 ++ .../weno3/python-v1/01a/config.py | 41 + .../weno3/python-v1/01a/domain.py | 56 ++ .../python-v1/01a/factories/base_factory.py | 49 + .../weno3/python-v1/01a/flux/__init__.py | 7 + .../weno3/python-v1/01a/flux/base.py | 25 + .../python-v1/01a/flux/engquist_osher.py | 19 + .../weno3/python-v1/01a/flux/factory.py | 25 + .../weno3/python-v1/01a/flux/rusanov.py | 21 + .../weno3/python-v1/01a/initial_condition.py | 107 ++ .../weno3/python-v1/01a/mesh.py | 26 + .../weno3/python-v1/01a/plotter.py | 109 ++ .../weno3/python-v1/01a/problem.py | 38 + .../01a/problems/linear_advection.py | 27 + .../python-v1/01a/reconstructor/__init__.py | 4 + .../weno3/python-v1/01a/reconstructor/base.py | 7 + .../weno3/python-v1/01a/reconstructor/eno.py | 90 ++ .../python-v1/01a/reconstructor/factory.py | 42 + .../python-v1/01a/reconstructor/weno3.py | 59 ++ .../weno3/python-v1/01a/registry.py | 75 ++ .../weno3/python-v1/01a/residual.py | 40 + .../weno3/python-v1/01a/result.py | 21 + .../weno3/python-v1/01a/run_eno_weno.py | 52 + .../weno3/python-v1/01a/solution.py | 38 + .../weno3/python-v1/01a/solver.py | 42 + .../weno3/python-v1/01a/time_integration.py | 132 +++ .../weno3/python-v1/01b/boundary.py | 104 ++ .../weno3/python-v1/01b/cfd_registry.py | 62 ++ .../weno3/python-v1/01b/config.py | 41 + .../weno3/python-v1/01b/domain.py | 56 ++ .../python-v1/01b/factories/base_factory.py | 49 + .../weno3/python-v1/01b/flux/__init__.py | 7 + .../weno3/python-v1/01b/flux/base.py | 25 + .../python-v1/01b/flux/engquist_osher.py | 19 + .../weno3/python-v1/01b/flux/factory.py | 25 + .../weno3/python-v1/01b/flux/rusanov.py | 21 + .../weno3/python-v1/01b/initial_condition.py | 107 ++ .../weno3/python-v1/01b/mesh.py | 26 + .../weno3/python-v1/01b/plotter.py | 109 ++ .../weno3/python-v1/01b/problem.py | 38 + .../01b/problems/linear_advection.py | 27 + .../python-v1/01b/reconstructor/__init__.py | 4 + .../weno3/python-v1/01b/reconstructor/base.py | 7 + .../weno3/python-v1/01b/reconstructor/eno.py | 90 ++ .../python-v1/01b/reconstructor/factory.py | 42 + .../python-v1/01b/reconstructor/weno3.py | 59 ++ .../weno3/python-v1/01b/registry.py | 75 ++ .../weno3/python-v1/01b/residual.py | 40 + .../weno3/python-v1/01b/result.py | 21 + .../weno3/python-v1/01b/run_eno_weno.py | 52 + .../weno3/python-v1/01b/solution.py | 32 + .../weno3/python-v1/01b/solver.py | 67 ++ .../weno3/python-v1/01b/time_integration.py | 124 +++ .../weno3/python-v1/01c/boundary.py | 104 ++ .../weno3/python-v1/01c/cfd_registry.py | 62 ++ .../weno3/python-v1/01c/config.py | 41 + .../weno3/python-v1/01c/domain.py | 56 ++ .../python-v1/01c/factories/base_factory.py | 49 + .../weno3/python-v1/01c/flux/__init__.py | 7 + .../weno3/python-v1/01c/flux/base.py | 25 + .../python-v1/01c/flux/engquist_osher.py | 19 + .../weno3/python-v1/01c/flux/factory.py | 25 + .../weno3/python-v1/01c/flux/rusanov.py | 21 + .../weno3/python-v1/01c/initial_condition.py | 107 ++ .../weno3/python-v1/01c/mesh.py | 26 + .../weno3/python-v1/01c/plotter.py | 109 ++ .../weno3/python-v1/01c/problem.py | 38 + .../01c/problems/linear_advection.py | 27 + .../python-v1/01c/reconstructor/__init__.py | 4 + .../weno3/python-v1/01c/reconstructor/base.py | 7 + .../weno3/python-v1/01c/reconstructor/eno.py | 90 ++ .../python-v1/01c/reconstructor/factory.py | 42 + .../python-v1/01c/reconstructor/weno3.py | 59 ++ .../weno3/python-v1/01c/registry.py | 75 ++ .../weno3/python-v1/01c/residual.py | 40 + .../weno3/python-v1/01c/result.py | 21 + .../weno3/python-v1/01c/run_eno_weno.py | 52 + .../weno3/python-v1/01c/solution.py | 32 + .../weno3/python-v1/01c/solver.py | 67 ++ .../01c/time_integration/__init__.py | 8 + .../python-v1/01c/time_integration/base.py | 27 + .../python-v1/01c/time_integration/factory.py | 10 + .../python-v1/01c/time_integration/rk1.py | 15 + .../python-v1/01c/time_integration/rk2.py | 29 + .../python-v1/01c/time_integration/rk3.py | 43 + .../weno3/python-v1/01d/boundary.py | 104 ++ .../weno3/python-v1/01d/config.py | 41 + .../weno3/python-v1/01d/core/registry.py | 79 ++ .../weno3/python-v1/01d/domain.py | 56 ++ .../weno3/python-v1/01d/flux/__init__.py | 7 + .../weno3/python-v1/01d/flux/base.py | 25 + .../python-v1/01d/flux/engquist_osher.py | 19 + .../weno3/python-v1/01d/flux/factory.py | 25 + .../weno3/python-v1/01d/flux/rusanov.py | 21 + .../weno3/python-v1/01d/initial_condition.py | 107 ++ .../weno3/python-v1/01d/mesh.py | 26 + .../weno3/python-v1/01d/plotter.py | 109 ++ .../weno3/python-v1/01d/problem.py | 38 + .../01d/problems/linear_advection.py | 27 + .../python-v1/01d/reconstructor/__init__.py | 4 + .../weno3/python-v1/01d/reconstructor/base.py | 7 + .../weno3/python-v1/01d/reconstructor/eno.py | 90 ++ .../python-v1/01d/reconstructor/factory.py | 42 + .../python-v1/01d/reconstructor/weno3.py | 59 ++ .../weno3/python-v1/01d/residual.py | 40 + .../weno3/python-v1/01d/result.py | 21 + .../weno3/python-v1/01d/run_eno_weno.py | 52 + .../weno3/python-v1/01d/solution.py | 32 + .../weno3/python-v1/01d/solver.py | 67 ++ .../01d/time_integration/__init__.py | 8 + .../python-v1/01d/time_integration/base.py | 27 + .../python-v1/01d/time_integration/factory.py | 10 + .../python-v1/01d/time_integration/rk1.py | 15 + .../python-v1/01d/time_integration/rk2.py | 29 + .../python-v1/01d/time_integration/rk3.py | 43 + .../weno3/python-v1/01e/boundary.py | 104 ++ .../weno3/python-v1/01e/config.py | 42 + .../weno3/python-v1/01e/core/registry.py | 79 ++ .../weno3/python-v1/01e/domain.py | 56 ++ .../weno3/python-v1/01e/flux/__init__.py | 7 + .../weno3/python-v1/01e/flux/base.py | 25 + .../python-v1/01e/flux/engquist_osher.py | 19 + .../weno3/python-v1/01e/flux/factory.py | 25 + .../weno3/python-v1/01e/flux/rusanov.py | 21 + .../weno3/python-v1/01e/initial_condition.py | 90 ++ .../weno3/python-v1/01e/mesh.py | 26 + .../weno3/python-v1/01e/plotter.py | 109 ++ .../weno3/python-v1/01e/problem.py | 38 + .../01e/problems/linear_advection.py | 28 + .../python-v1/01e/reconstructor/__init__.py | 4 + .../weno3/python-v1/01e/reconstructor/base.py | 7 + .../weno3/python-v1/01e/reconstructor/eno.py | 90 ++ .../python-v1/01e/reconstructor/factory.py | 42 + .../python-v1/01e/reconstructor/weno3.py | 59 ++ .../weno3/python-v1/01e/residual.py | 40 + .../weno3/python-v1/01e/result.py | 25 + .../weno3/python-v1/01e/run_eno_weno.py | 52 + .../weno3/python-v1/01e/solution.py | 32 + .../weno3/python-v1/01e/solver.py | 67 ++ .../01e/time_integration/__init__.py | 8 + .../python-v1/01e/time_integration/base.py | 27 + .../python-v1/01e/time_integration/factory.py | 10 + .../python-v1/01e/time_integration/rk1.py | 15 + .../python-v1/01e/time_integration/rk2.py | 29 + .../python-v1/01e/time_integration/rk3.py | 43 + .../weno3/python-v1/01f/boundary.py | 104 ++ .../weno3/python-v1/01f/config.py | 42 + .../weno3/python-v1/01f/core/registry.py | 79 ++ .../weno3/python-v1/01f/domain.py | 56 ++ .../weno3/python-v1/01f/equations/base.py | 47 + .../01f/equations/linear_advection.py | 38 + .../weno3/python-v1/01f/flux/__init__.py | 7 + .../weno3/python-v1/01f/flux/base.py | 25 + .../python-v1/01f/flux/engquist_osher.py | 19 + .../weno3/python-v1/01f/flux/factory.py | 25 + .../weno3/python-v1/01f/flux/rusanov.py | 21 + .../weno3/python-v1/01f/initial_condition.py | 90 ++ .../weno3/python-v1/01f/mesh.py | 26 + .../weno3/python-v1/01f/plotter.py | 109 ++ .../weno3/python-v1/01f/problem.py | 38 + .../01f/problems/linear_advection.py | 39 + .../python-v1/01f/reconstructor/__init__.py | 4 + .../weno3/python-v1/01f/reconstructor/base.py | 7 + .../weno3/python-v1/01f/reconstructor/eno.py | 90 ++ .../python-v1/01f/reconstructor/factory.py | 42 + .../python-v1/01f/reconstructor/weno3.py | 59 ++ .../weno3/python-v1/01f/residual.py | 40 + .../weno3/python-v1/01f/result.py | 25 + .../weno3/python-v1/01f/run_eno_weno.py | 51 + .../weno3/python-v1/01f/solution.py | 32 + .../weno3/python-v1/01f/solver.py | 67 ++ .../01f/time_integration/__init__.py | 8 + .../python-v1/01f/time_integration/base.py | 27 + .../python-v1/01f/time_integration/factory.py | 10 + .../python-v1/01f/time_integration/rk1.py | 15 + .../python-v1/01f/time_integration/rk2.py | 29 + .../python-v1/01f/time_integration/rk3.py | 43 + .../weno3/python-v1/01g/boundary.py | 104 ++ .../weno3/python-v1/01g/config.py | 43 + .../weno3/python-v1/01g/core/registry.py | 79 ++ .../weno3/python-v1/01g/domain.py | 56 ++ .../weno3/python-v1/01g/equations/base.py | 56 ++ .../01g/equations/linear_advection.py | 42 + .../weno3/python-v1/01g/flux/__init__.py | 7 + .../weno3/python-v1/01g/flux/base.py | 25 + .../python-v1/01g/flux/engquist_osher.py | 20 + .../weno3/python-v1/01g/flux/factory.py | 25 + .../weno3/python-v1/01g/flux/rusanov.py | 25 + .../weno3/python-v1/01g/initial_condition.py | 90 ++ .../weno3/python-v1/01g/mesh.py | 26 + .../weno3/python-v1/01g/plotter.py | 109 ++ .../weno3/python-v1/01g/problem.py | 38 + .../01g/problems/linear_advection.py | 39 + .../python-v1/01g/reconstructor/__init__.py | 4 + .../weno3/python-v1/01g/reconstructor/base.py | 7 + .../weno3/python-v1/01g/reconstructor/eno.py | 90 ++ .../python-v1/01g/reconstructor/factory.py | 42 + .../python-v1/01g/reconstructor/weno3.py | 59 ++ .../python-v1/01g/reconstructor/weno5.py | 68 ++ .../weno3/python-v1/01g/residual.py | 40 + .../weno3/python-v1/01g/result.py | 25 + .../weno3/python-v1/01g/run_eno_weno.py | 51 + .../weno3/python-v1/01g/solution.py | 32 + .../weno3/python-v1/01g/solver.py | 67 ++ .../01g/time_integration/__init__.py | 8 + .../python-v1/01g/time_integration/base.py | 27 + .../python-v1/01g/time_integration/factory.py | 10 + .../python-v1/01g/time_integration/rk1.py | 15 + .../python-v1/01g/time_integration/rk2.py | 29 + .../python-v1/01g/time_integration/rk3.py | 43 + .../weno3/python-v1/01h/boundary.py | 104 ++ .../weno3/python-v1/01h/config.py | 45 + .../weno3/python-v1/01h/core/registry.py | 83 ++ .../weno3/python-v1/01h/domain.py | 56 ++ .../weno3/python-v1/01h/equations/base.py | 56 ++ .../01h/equations/linear_advection.py | 42 + .../weno3/python-v1/01h/flux/__init__.py | 7 + .../weno3/python-v1/01h/flux/base.py | 25 + .../python-v1/01h/flux/engquist_osher.py | 20 + .../weno3/python-v1/01h/flux/factory.py | 25 + .../weno3/python-v1/01h/flux/rusanov.py | 25 + .../weno3/python-v1/01h/initial_condition.py | 90 ++ .../weno3/python-v1/01h/mesh.py | 26 + .../weno3/python-v1/01h/plotter.py | 109 ++ .../weno3/python-v1/01h/problem.py | 38 + .../01h/problems/linear_advection.py | 39 + .../python-v1/01h/reconstructor/__init__.py | 4 + .../weno3/python-v1/01h/reconstructor/base.py | 7 + .../weno3/python-v1/01h/reconstructor/eno.py | 90 ++ .../python-v1/01h/reconstructor/factory.py | 36 + .../python-v1/01h/reconstructor/weno3.py | 59 ++ .../python-v1/01h/reconstructor/weno5.py | 68 ++ .../weno3/python-v1/01h/residual.py | 40 + .../weno3/python-v1/01h/result.py | 25 + .../weno3/python-v1/01h/run_eno_weno.py | 88 ++ .../weno3/python-v1/01h/solution.py | 32 + .../weno3/python-v1/01h/solver.py | 67 ++ .../01h/time_integration/__init__.py | 8 + .../python-v1/01h/time_integration/base.py | 27 + .../python-v1/01h/time_integration/factory.py | 10 + .../python-v1/01h/time_integration/rk1.py | 15 + .../python-v1/01h/time_integration/rk2.py | 29 + .../python-v1/01h/time_integration/rk3.py | 43 + .../weno3/python-v1/01i/boundary.py | 104 ++ .../weno3/python-v1/01i/config.py | 45 + .../weno3/python-v1/01i/core/registry.py | 83 ++ .../weno3/python-v1/01i/domain.py | 56 ++ .../weno3/python-v1/01i/equations/base.py | 56 ++ .../01i/equations/linear_advection.py | 42 + .../weno3/python-v1/01i/flux/__init__.py | 7 + .../weno3/python-v1/01i/flux/base.py | 25 + .../python-v1/01i/flux/engquist_osher.py | 20 + .../weno3/python-v1/01i/flux/factory.py | 25 + .../weno3/python-v1/01i/flux/rusanov.py | 25 + .../weno3/python-v1/01i/initial_condition.py | 90 ++ .../weno3/python-v1/01i/mesh.py | 26 + .../weno3/python-v1/01i/plotter.py | 109 ++ .../weno3/python-v1/01i/problem.py | 38 + .../01i/problems/linear_advection.py | 39 + .../python-v1/01i/reconstructor/__init__.py | 6 + .../weno3/python-v1/01i/reconstructor/base.py | 7 + .../weno3/python-v1/01i/reconstructor/eno.py | 90 ++ .../python-v1/01i/reconstructor/factory.py | 28 + .../python-v1/01i/reconstructor/weno3.py | 59 ++ .../python-v1/01i/reconstructor/weno5.py | 68 ++ .../weno3/python-v1/01i/residual.py | 40 + .../weno3/python-v1/01i/result.py | 25 + .../weno3/python-v1/01i/run_eno_weno.py | 88 ++ .../weno3/python-v1/01i/solution.py | 32 + .../weno3/python-v1/01i/solver.py | 67 ++ .../01i/time_integration/__init__.py | 8 + .../python-v1/01i/time_integration/base.py | 27 + .../python-v1/01i/time_integration/factory.py | 10 + .../python-v1/01i/time_integration/rk1.py | 15 + .../python-v1/01i/time_integration/rk2.py | 29 + .../python-v1/01i/time_integration/rk3.py | 43 + .../python-v1/02/example/run_eno_weno.py | 93 ++ .../weno3/python-v1/02/src/boundary.py | 104 ++ .../weno3/python-v1/02/src/config.py | 45 + .../weno3/python-v1/02/src/core/registry.py | 83 ++ .../weno3/python-v1/02/src/domain.py | 56 ++ .../weno3/python-v1/02/src/equations/base.py | 56 ++ .../02/src/equations/linear_advection.py | 42 + .../weno3/python-v1/02/src/flux/__init__.py | 7 + .../weno3/python-v1/02/src/flux/base.py | 25 + .../python-v1/02/src/flux/engquist_osher.py | 20 + .../weno3/python-v1/02/src/flux/factory.py | 25 + .../weno3/python-v1/02/src/flux/rusanov.py | 25 + .../python-v1/02/src/initial_condition.py | 90 ++ .../weno3/python-v1/02/src/mesh.py | 26 + .../weno3/python-v1/02/src/plotter.py | 109 ++ .../weno3/python-v1/02/src/problem.py | 38 + .../02/src/problems/linear_advection.py | 39 + .../02/src/reconstructor/__init__.py | 6 + .../python-v1/02/src/reconstructor/base.py | 7 + .../python-v1/02/src/reconstructor/eno.py | 90 ++ .../python-v1/02/src/reconstructor/factory.py | 28 + .../python-v1/02/src/reconstructor/weno3.py | 59 ++ .../python-v1/02/src/reconstructor/weno5.py | 68 ++ .../weno3/python-v1/02/src/residual.py | 40 + .../weno3/python-v1/02/src/result.py | 25 + .../weno3/python-v1/02/src/solution.py | 32 + .../weno3/python-v1/02/src/solver.py | 67 ++ .../02/src/time_integration/__init__.py | 8 + .../python-v1/02/src/time_integration/base.py | 27 + .../02/src/time_integration/factory.py | 10 + .../python-v1/02/src/time_integration/rk1.py | 15 + .../python-v1/02/src/time_integration/rk2.py | 29 + .../python-v1/02/src/time_integration/rk3.py | 43 + .../python-v1/02a/example/run_eno_weno.py | 93 ++ .../weno3/python-v1/02a/src/boundary.py | 104 ++ .../weno3/python-v1/02a/src/config.py | 45 + .../weno3/python-v1/02a/src/core/registry.py | 83 ++ .../weno3/python-v1/02a/src/domain.py | 56 ++ .../weno3/python-v1/02a/src/equations/base.py | 56 ++ .../02a/src/equations/linear_advection.py | 42 + .../weno3/python-v1/02a/src/flux/__init__.py | 7 + .../weno3/python-v1/02a/src/flux/base.py | 25 + .../python-v1/02a/src/flux/engquist_osher.py | 20 + .../weno3/python-v1/02a/src/flux/factory.py | 25 + .../weno3/python-v1/02a/src/flux/rusanov.py | 25 + .../python-v1/02a/src/initial_condition.py | 90 ++ .../weno3/python-v1/02a/src/mesh.py | 26 + .../weno3/python-v1/02a/src/plotter.py | 109 ++ .../weno3/python-v1/02a/src/problem.py | 38 + .../02a/src/problems/linear_advection.py | 39 + .../02a/src/reconstructor/__init__.py | 6 + .../python-v1/02a/src/reconstructor/base.py | 7 + .../python-v1/02a/src/reconstructor/eno.py | 96 ++ .../02a/src/reconstructor/factory.py | 14 + .../python-v1/02a/src/reconstructor/weno3.py | 62 ++ .../python-v1/02a/src/reconstructor/weno5.py | 71 ++ .../weno3/python-v1/02a/src/residual.py | 41 + .../weno3/python-v1/02a/src/result.py | 25 + .../weno3/python-v1/02a/src/solution.py | 32 + .../weno3/python-v1/02a/src/solver.py | 67 ++ .../02a/src/time_integration/__init__.py | 8 + .../02a/src/time_integration/base.py | 27 + .../02a/src/time_integration/factory.py | 10 + .../python-v1/02a/src/time_integration/rk1.py | 15 + .../python-v1/02a/src/time_integration/rk2.py | 29 + .../python-v1/02a/src/time_integration/rk3.py | 43 + .../python-v1/02b/example/run_eno_weno.py | 93 ++ .../weno3/python-v1/02b/src/boundary.py | 104 ++ .../weno3/python-v1/02b/src/config.py | 45 + .../weno3/python-v1/02b/src/core/registry.py | 83 ++ .../weno3/python-v1/02b/src/domain.py | 56 ++ .../weno3/python-v1/02b/src/equations/base.py | 56 ++ .../02b/src/equations/linear_advection.py | 42 + .../weno3/python-v1/02b/src/flux/__init__.py | 7 + .../weno3/python-v1/02b/src/flux/base.py | 25 + .../python-v1/02b/src/flux/engquist_osher.py | 20 + .../weno3/python-v1/02b/src/flux/factory.py | 25 + .../weno3/python-v1/02b/src/flux/rusanov.py | 25 + .../02b/src/initial_condition/__init__.py | 12 + .../02b/src/initial_condition/base.py | 25 + .../02b/src/initial_condition/factory.py | 24 + .../02b/src/initial_condition/gaussian.py | 16 + .../02b/src/initial_condition/sine.py | 15 + .../02b/src/initial_condition/step.py | 17 + .../weno3/python-v1/02b/src/mesh.py | 26 + .../weno3/python-v1/02b/src/plotter.py | 109 ++ .../weno3/python-v1/02b/src/problem.py | 38 + .../02b/src/problems/linear_advection.py | 39 + .../02b/src/reconstructor/__init__.py | 6 + .../python-v1/02b/src/reconstructor/base.py | 7 + .../python-v1/02b/src/reconstructor/eno.py | 96 ++ .../02b/src/reconstructor/factory.py | 14 + .../python-v1/02b/src/reconstructor/weno3.py | 62 ++ .../python-v1/02b/src/reconstructor/weno5.py | 71 ++ .../weno3/python-v1/02b/src/residual.py | 41 + .../weno3/python-v1/02b/src/result.py | 25 + .../weno3/python-v1/02b/src/solution.py | 32 + .../weno3/python-v1/02b/src/solver.py | 67 ++ .../02b/src/time_integration/__init__.py | 8 + .../02b/src/time_integration/base.py | 27 + .../02b/src/time_integration/factory.py | 10 + .../python-v1/02b/src/time_integration/rk1.py | 15 + .../python-v1/02b/src/time_integration/rk2.py | 29 + .../python-v1/02b/src/time_integration/rk3.py | 43 + .../python-v1/02c/example/run_eno_weno.py | 93 ++ .../python-v1/02c/src/boundary/__init__.py | 21 + .../weno3/python-v1/02c/src/boundary/base.py | 18 + .../python-v1/02c/src/boundary/dirichlet.py | 27 + .../python-v1/02c/src/boundary/factory.py | 24 + .../python-v1/02c/src/boundary/neumann.py | 23 + .../python-v1/02c/src/boundary/periodic.py | 19 + .../weno3/python-v1/02c/src/config.py | 45 + .../weno3/python-v1/02c/src/core/registry.py | 83 ++ .../weno3/python-v1/02c/src/domain.py | 56 ++ .../weno3/python-v1/02c/src/equations/base.py | 56 ++ .../02c/src/equations/linear_advection.py | 42 + .../weno3/python-v1/02c/src/flux/__init__.py | 7 + .../weno3/python-v1/02c/src/flux/base.py | 25 + .../python-v1/02c/src/flux/engquist_osher.py | 20 + .../weno3/python-v1/02c/src/flux/factory.py | 25 + .../weno3/python-v1/02c/src/flux/rusanov.py | 25 + .../02c/src/initial_condition/__init__.py | 12 + .../02c/src/initial_condition/base.py | 25 + .../02c/src/initial_condition/factory.py | 24 + .../02c/src/initial_condition/gaussian.py | 16 + .../02c/src/initial_condition/sine.py | 15 + .../02c/src/initial_condition/step.py | 17 + .../weno3/python-v1/02c/src/mesh.py | 26 + .../weno3/python-v1/02c/src/plotter.py | 109 ++ .../weno3/python-v1/02c/src/problem.py | 38 + .../02c/src/problems/linear_advection.py | 39 + .../02c/src/reconstructor/__init__.py | 6 + .../python-v1/02c/src/reconstructor/base.py | 7 + .../python-v1/02c/src/reconstructor/eno.py | 96 ++ .../02c/src/reconstructor/factory.py | 14 + .../python-v1/02c/src/reconstructor/weno3.py | 62 ++ .../python-v1/02c/src/reconstructor/weno5.py | 71 ++ .../weno3/python-v1/02c/src/residual.py | 41 + .../weno3/python-v1/02c/src/result.py | 25 + .../weno3/python-v1/02c/src/solution.py | 32 + .../weno3/python-v1/02c/src/solver.py | 67 ++ .../02c/src/time_integration/__init__.py | 8 + .../02c/src/time_integration/base.py | 27 + .../02c/src/time_integration/factory.py | 10 + .../python-v1/02c/src/time_integration/rk1.py | 15 + .../python-v1/02c/src/time_integration/rk2.py | 29 + .../python-v1/02c/src/time_integration/rk3.py | 43 + .../python-v1/02d/example/run_eno_weno.py | 93 ++ .../python-v1/02d/src/boundary/__init__.py | 21 + .../weno3/python-v1/02d/src/boundary/base.py | 18 + .../python-v1/02d/src/boundary/dirichlet.py | 27 + .../python-v1/02d/src/boundary/factory.py | 24 + .../python-v1/02d/src/boundary/neumann.py | 23 + .../python-v1/02d/src/boundary/periodic.py | 19 + .../weno3/python-v1/02d/src/config.py | 45 + .../weno3/python-v1/02d/src/core/registry.py | 83 ++ .../weno3/python-v1/02d/src/domain.py | 56 ++ .../weno3/python-v1/02d/src/equations/base.py | 56 ++ .../02d/src/equations/linear_advection.py | 42 + .../02d/src/initial_condition/__init__.py | 12 + .../02d/src/initial_condition/base.py | 25 + .../02d/src/initial_condition/factory.py | 24 + .../02d/src/initial_condition/gaussian.py | 16 + .../02d/src/initial_condition/sine.py | 15 + .../02d/src/initial_condition/step.py | 17 + .../weno3/python-v1/02d/src/mesh.py | 26 + .../python-v1/02d/src/numerics/__init__.py | 27 + .../02d/src/numerics/flux/__init__.py | 19 + .../python-v1/02d/src/numerics/flux/base.py | 25 + .../02d/src/numerics/flux/engquist_osher.py | 20 + .../02d/src/numerics/flux/factory.py | 26 + .../02d/src/numerics/flux/rusanov.py | 25 + .../src/numerics/reconstructor/__init__.py | 21 + .../02d/src/numerics/reconstructor/base.py | 7 + .../02d/src/numerics/reconstructor/eno.py | 96 ++ .../02d/src/numerics/reconstructor/factory.py | 20 + .../02d/src/numerics/reconstructor/weno3.py | 62 ++ .../02d/src/numerics/reconstructor/weno5.py | 71 ++ .../python-v1/02d/src/numerics/residual.py | 39 + .../src/numerics/time_integration/__init__.py | 21 + .../02d/src/numerics/time_integration/base.py | 27 + .../src/numerics/time_integration/factory.py | 15 + .../02d/src/numerics/time_integration/rk1.py | 15 + .../02d/src/numerics/time_integration/rk2.py | 29 + .../02d/src/numerics/time_integration/rk3.py | 43 + .../weno3/python-v1/02d/src/plotter.py | 109 ++ .../weno3/python-v1/02d/src/problem.py | 38 + .../02d/src/problems/linear_advection.py | 39 + .../weno3/python-v1/02d/src/result.py | 25 + .../weno3/python-v1/02d/src/solution.py | 32 + .../weno3/python-v1/02d/src/solver.py | 68 ++ .../python-v1/02e/example/run_eno_weno.py | 102 ++ .../python-v1/02e/src/boundary/__init__.py | 21 + .../weno3/python-v1/02e/src/boundary/base.py | 18 + .../python-v1/02e/src/boundary/dirichlet.py | 27 + .../python-v1/02e/src/boundary/factory.py | 24 + .../python-v1/02e/src/boundary/neumann.py | 23 + .../python-v1/02e/src/boundary/periodic.py | 19 + .../weno3/python-v1/02e/src/config.py | 45 + .../weno3/python-v1/02e/src/core/registry.py | 83 ++ .../weno3/python-v1/02e/src/domain.py | 56 ++ .../weno3/python-v1/02e/src/mesh.py | 26 + .../python-v1/02e/src/numerics/__init__.py | 27 + .../02e/src/numerics/flux/__init__.py | 19 + .../python-v1/02e/src/numerics/flux/base.py | 25 + .../02e/src/numerics/flux/engquist_osher.py | 20 + .../02e/src/numerics/flux/factory.py | 26 + .../02e/src/numerics/flux/rusanov.py | 25 + .../src/numerics/reconstructor/__init__.py | 21 + .../02e/src/numerics/reconstructor/base.py | 7 + .../02e/src/numerics/reconstructor/eno.py | 96 ++ .../02e/src/numerics/reconstructor/factory.py | 20 + .../02e/src/numerics/reconstructor/weno3.py | 62 ++ .../02e/src/numerics/reconstructor/weno5.py | 71 ++ .../python-v1/02e/src/numerics/residual.py | 39 + .../src/numerics/time_integration/__init__.py | 21 + .../02e/src/numerics/time_integration/base.py | 27 + .../src/numerics/time_integration/factory.py | 15 + .../02e/src/numerics/time_integration/rk1.py | 15 + .../02e/src/numerics/time_integration/rk2.py | 29 + .../02e/src/numerics/time_integration/rk3.py | 43 + .../python-v1/02e/src/physics/__init__.py | 0 .../02e/src/physics/equations/__init__.py | 15 + .../02e/src/physics/equations/base.py | 60 ++ .../02e/src/physics/equations/euler.py | 23 + .../src/physics/equations/linear_advection.py | 42 + .../physics/initial_conditions/__init__.py | 16 + .../src/physics/initial_conditions/base.py | 25 + .../src/physics/initial_conditions/factory.py | 24 + .../physics/initial_conditions/gaussian.py | 16 + .../src/physics/initial_conditions/sine.py | 15 + .../src/physics/initial_conditions/step.py | 17 + .../02e/src/physics/problems/__init__.py | 14 + .../02e/src/physics/problems/base.py | 42 + .../02e/src/physics/problems/factory.py | 28 + .../src/physics/problems/linear_advection.py | 36 + .../02e/src/physics/systems/__init__.py | 15 + .../python-v1/02e/src/physics/systems/base.py | 33 + .../02e/src/physics/systems/factory.py | 28 + .../systems/linear_advection_system.py | 36 + .../weno3/python-v1/02e/src/plotter.py | 109 ++ .../weno3/python-v1/02e/src/problem.py | 38 + .../weno3/python-v1/02e/src/result.py | 25 + .../weno3/python-v1/02e/src/solution.py | 32 + .../weno3/python-v1/02e/src/solver.py | 68 ++ .../python-v1/02f/example/run_eno_weno.py | 102 ++ .../weno3/python-v1/02f/src/config.py | 45 + .../weno3/python-v1/02f/src/core/registry.py | 83 ++ .../02f/src/infrastructure/__init__.py | 18 + .../src/infrastructure/boundary/__init__.py | 21 + .../02f/src/infrastructure/boundary/base.py | 18 + .../src/infrastructure/boundary/dirichlet.py | 27 + .../src/infrastructure/boundary/factory.py | 24 + .../src/infrastructure/boundary/neumann.py | 23 + .../src/infrastructure/boundary/periodic.py | 19 + .../02f/src/infrastructure/domain.py | 56 ++ .../python-v1/02f/src/infrastructure/mesh.py | 26 + .../02f/src/infrastructure/solution.py | 32 + .../python-v1/02f/src/numerics/__init__.py | 27 + .../02f/src/numerics/flux/__init__.py | 19 + .../python-v1/02f/src/numerics/flux/base.py | 25 + .../02f/src/numerics/flux/engquist_osher.py | 20 + .../02f/src/numerics/flux/factory.py | 26 + .../02f/src/numerics/flux/rusanov.py | 25 + .../src/numerics/reconstructor/__init__.py | 21 + .../02f/src/numerics/reconstructor/base.py | 7 + .../02f/src/numerics/reconstructor/eno.py | 96 ++ .../02f/src/numerics/reconstructor/factory.py | 20 + .../02f/src/numerics/reconstructor/weno3.py | 62 ++ .../02f/src/numerics/reconstructor/weno5.py | 71 ++ .../python-v1/02f/src/numerics/residual.py | 39 + .../src/numerics/time_integration/__init__.py | 21 + .../02f/src/numerics/time_integration/base.py | 27 + .../src/numerics/time_integration/factory.py | 15 + .../02f/src/numerics/time_integration/rk1.py | 15 + .../02f/src/numerics/time_integration/rk2.py | 29 + .../02f/src/numerics/time_integration/rk3.py | 43 + .../python-v1/02f/src/physics/__init__.py | 0 .../02f/src/physics/equations/__init__.py | 15 + .../02f/src/physics/equations/base.py | 60 ++ .../02f/src/physics/equations/euler.py | 23 + .../src/physics/equations/linear_advection.py | 42 + .../physics/initial_conditions/__init__.py | 16 + .../src/physics/initial_conditions/base.py | 25 + .../src/physics/initial_conditions/factory.py | 24 + .../physics/initial_conditions/gaussian.py | 16 + .../src/physics/initial_conditions/sine.py | 15 + .../src/physics/initial_conditions/step.py | 17 + .../02f/src/physics/problems/__init__.py | 14 + .../02f/src/physics/problems/base.py | 42 + .../02f/src/physics/problems/factory.py | 28 + .../src/physics/problems/linear_advection.py | 36 + .../02f/src/physics/systems/__init__.py | 15 + .../python-v1/02f/src/physics/systems/base.py | 33 + .../02f/src/physics/systems/factory.py | 28 + .../systems/linear_advection_system.py | 36 + .../weno3/python-v1/02f/src/plotter.py | 109 ++ .../weno3/python-v1/02f/src/result.py | 25 + .../weno3/python-v1/02f/src/solver.py | 74 ++ .../python-v1/02g/example/run_eno_weno.py | 92 ++ .../weno3/python-v1/02g/src/core/registry.py | 83 ++ .../weno3/python-v1/02g/src/core/solver.py | 67 ++ .../02g/src/infrastructure/__init__.py | 18 + .../src/infrastructure/boundary/__init__.py | 21 + .../02g/src/infrastructure/boundary/base.py | 18 + .../src/infrastructure/boundary/dirichlet.py | 27 + .../src/infrastructure/boundary/factory.py | 24 + .../src/infrastructure/boundary/neumann.py | 23 + .../src/infrastructure/boundary/periodic.py | 19 + .../02g/src/infrastructure/config.py | 45 + .../02g/src/infrastructure/domain.py | 56 ++ .../python-v1/02g/src/infrastructure/mesh.py | 26 + .../02g/src/infrastructure/result.py | 25 + .../02g/src/infrastructure/solution.py | 32 + .../python-v1/02g/src/numerics/__init__.py | 27 + .../02g/src/numerics/flux/__init__.py | 19 + .../python-v1/02g/src/numerics/flux/base.py | 25 + .../02g/src/numerics/flux/engquist_osher.py | 20 + .../02g/src/numerics/flux/factory.py | 26 + .../02g/src/numerics/flux/rusanov.py | 25 + .../src/numerics/reconstructor/__init__.py | 21 + .../02g/src/numerics/reconstructor/base.py | 7 + .../02g/src/numerics/reconstructor/eno.py | 96 ++ .../02g/src/numerics/reconstructor/factory.py | 20 + .../02g/src/numerics/reconstructor/weno3.py | 62 ++ .../02g/src/numerics/reconstructor/weno5.py | 71 ++ .../python-v1/02g/src/numerics/residual.py | 39 + .../src/numerics/time_integration/__init__.py | 21 + .../02g/src/numerics/time_integration/base.py | 27 + .../src/numerics/time_integration/factory.py | 15 + .../02g/src/numerics/time_integration/rk1.py | 15 + .../02g/src/numerics/time_integration/rk2.py | 29 + .../02g/src/numerics/time_integration/rk3.py | 43 + .../python-v1/02g/src/physics/__init__.py | 0 .../02g/src/physics/equations/__init__.py | 15 + .../02g/src/physics/equations/base.py | 60 ++ .../02g/src/physics/equations/euler.py | 23 + .../src/physics/equations/linear_advection.py | 42 + .../physics/initial_conditions/__init__.py | 16 + .../src/physics/initial_conditions/base.py | 25 + .../src/physics/initial_conditions/factory.py | 24 + .../physics/initial_conditions/gaussian.py | 16 + .../src/physics/initial_conditions/sine.py | 15 + .../src/physics/initial_conditions/step.py | 17 + .../02g/src/physics/problems/__init__.py | 14 + .../02g/src/physics/problems/base.py | 42 + .../02g/src/physics/problems/factory.py | 28 + .../src/physics/problems/linear_advection.py | 36 + .../02g/src/physics/systems/__init__.py | 15 + .../python-v1/02g/src/physics/systems/base.py | 33 + .../02g/src/physics/systems/factory.py | 28 + .../systems/linear_advection_system.py | 36 + .../02g/src/visualization/plotter.py | 109 ++ .../example/linear_advection/run_eno_weno.py | 103 ++ .../weno3/python-v1/02h/src/core/registry.py | 83 ++ .../weno3/python-v1/02h/src/core/solver.py | 67 ++ .../02h/src/infrastructure/__init__.py | 18 + .../src/infrastructure/boundary/__init__.py | 21 + .../02h/src/infrastructure/boundary/base.py | 18 + .../src/infrastructure/boundary/dirichlet.py | 27 + .../src/infrastructure/boundary/factory.py | 24 + .../src/infrastructure/boundary/neumann.py | 23 + .../src/infrastructure/boundary/periodic.py | 19 + .../02h/src/infrastructure/config.py | 45 + .../02h/src/infrastructure/domain.py | 56 ++ .../python-v1/02h/src/infrastructure/mesh.py | 26 + .../02h/src/infrastructure/result.py | 25 + .../02h/src/infrastructure/solution.py | 32 + .../python-v1/02h/src/numerics/__init__.py | 27 + .../02h/src/numerics/flux/__init__.py | 19 + .../python-v1/02h/src/numerics/flux/base.py | 25 + .../02h/src/numerics/flux/engquist_osher.py | 20 + .../02h/src/numerics/flux/factory.py | 26 + .../02h/src/numerics/flux/rusanov.py | 25 + .../src/numerics/reconstructor/__init__.py | 21 + .../02h/src/numerics/reconstructor/base.py | 7 + .../02h/src/numerics/reconstructor/eno.py | 96 ++ .../02h/src/numerics/reconstructor/factory.py | 20 + .../02h/src/numerics/reconstructor/weno3.py | 62 ++ .../02h/src/numerics/reconstructor/weno5.py | 71 ++ .../python-v1/02h/src/numerics/residual.py | 39 + .../src/numerics/time_integration/__init__.py | 21 + .../02h/src/numerics/time_integration/base.py | 27 + .../src/numerics/time_integration/factory.py | 15 + .../02h/src/numerics/time_integration/rk1.py | 15 + .../02h/src/numerics/time_integration/rk2.py | 29 + .../02h/src/numerics/time_integration/rk3.py | 43 + .../python-v1/02h/src/physics/__init__.py | 0 .../02h/src/physics/equations/__init__.py | 15 + .../02h/src/physics/equations/base.py | 60 ++ .../02h/src/physics/equations/euler.py | 23 + .../src/physics/equations/linear_advection.py | 42 + .../physics/initial_conditions/__init__.py | 16 + .../src/physics/initial_conditions/base.py | 25 + .../src/physics/initial_conditions/factory.py | 24 + .../physics/initial_conditions/gaussian.py | 16 + .../src/physics/initial_conditions/sine.py | 15 + .../src/physics/initial_conditions/step.py | 17 + .../02h/src/physics/problems/__init__.py | 14 + .../02h/src/physics/problems/base.py | 42 + .../02h/src/physics/problems/factory.py | 28 + .../src/physics/problems/linear_advection.py | 36 + .../02h/src/physics/systems/__init__.py | 15 + .../python-v1/02h/src/physics/systems/base.py | 33 + .../02h/src/physics/systems/factory.py | 28 + .../systems/linear_advection_system.py | 36 + .../02h/src/visualization/plotter.py | 109 ++ .../weno3/python/05/boundary.py | 83 ++ .../weno3/python/05/config.py | 41 + .../weno3/python/05/domain.py | 56 ++ .../weno3/python/05/eno_weno_comparison.png | Bin 0 -> 224091 bytes .../weno3/python/05/flux.py | 61 ++ .../weno3/python/05/initial_condition.py | 81 ++ .../weno3/python/05/mesh.py | 26 + .../weno3/python/05/plotter.py | 107 ++ .../weno3/python/05/reconstructor/__init__.py | 4 + .../weno3/python/05/reconstructor/base.py | 7 + .../weno3/python/05/reconstructor/eno.py | 88 ++ .../weno3/python/05/reconstructor/factory.py | 35 + .../weno3/python/05/reconstructor/weno3.py | 56 ++ .../weno3/python/05/residual.py | 40 + .../weno3/python/05/run_eno_weno.py | 52 + .../weno3/python/05/solution.py | 40 + .../weno3/python/05/solver.py | 90 ++ .../weno3/python/05/time_integration.py | 111 +++ .../weno3/python/05a/boundary.py | 136 +++ .../weno3/python/05a/config.py | 41 + .../weno3/python/05a/domain.py | 56 ++ .../weno3/python/05a/flux.py | 61 ++ .../weno3/python/05a/initial_condition.py | 81 ++ .../weno3/python/05a/mesh.py | 26 + .../weno3/python/05a/plotter.py | 107 ++ .../python/05a/reconstructor/__init__.py | 4 + .../weno3/python/05a/reconstructor/base.py | 7 + .../weno3/python/05a/reconstructor/eno.py | 88 ++ .../weno3/python/05a/reconstructor/factory.py | 35 + .../weno3/python/05a/reconstructor/weno3.py | 86 ++ .../weno3/python/05a/registry.py | 56 ++ .../weno3/python/05a/residual.py | 40 + .../weno3/python/05a/run_eno_weno.py | 52 + .../weno3/python/05a/solution.py | 40 + .../weno3/python/05a/solver.py | 90 ++ .../weno3/python/05a/time_integration.py | 111 +++ .../weno3/python/05b/boundary.py | 136 +++ .../weno3/python/05b/config.py | 41 + .../weno3/python/05b/domain.py | 56 ++ .../weno3/python/05b/flux.py | 113 +++ .../weno3/python/05b/initial_condition.py | 81 ++ .../weno3/python/05b/mesh.py | 26 + .../weno3/python/05b/plotter.py | 107 ++ .../python/05b/reconstructor/__init__.py | 4 + .../weno3/python/05b/reconstructor/base.py | 7 + .../weno3/python/05b/reconstructor/eno.py | 88 ++ .../weno3/python/05b/reconstructor/factory.py | 35 + .../weno3/python/05b/reconstructor/weno3.py | 86 ++ .../weno3/python/05b/registry.py | 56 ++ .../weno3/python/05b/residual.py | 40 + .../weno3/python/05b/run_eno_weno.py | 52 + .../weno3/python/05b/solution.py | 40 + .../weno3/python/05b/solver.py | 90 ++ .../weno3/python/05b/time_integration.py | 111 +++ .../weno3/python/05c/boundary.py | 136 +++ .../weno3/python/05c/config.py | 41 + .../weno3/python/05c/domain.py | 56 ++ .../weno3/python/05c/flux.py | 113 +++ .../weno3/python/05c/initial_condition.py | 146 +++ .../weno3/python/05c/mesh.py | 26 + .../weno3/python/05c/plotter.py | 107 ++ .../python/05c/reconstructor/__init__.py | 4 + .../weno3/python/05c/reconstructor/base.py | 7 + .../weno3/python/05c/reconstructor/eno.py | 88 ++ .../weno3/python/05c/reconstructor/factory.py | 35 + .../weno3/python/05c/reconstructor/weno3.py | 86 ++ .../weno3/python/05c/registry.py | 56 ++ .../weno3/python/05c/residual.py | 40 + .../weno3/python/05c/run_eno_weno.py | 52 + .../weno3/python/05c/solution.py | 40 + .../weno3/python/05c/solver.py | 90 ++ .../weno3/python/05c/time_integration.py | 111 +++ .../weno3/python/05d/boundary.py | 136 +++ .../weno3/python/05d/config.py | 41 + .../weno3/python/05d/domain.py | 56 ++ .../weno3/python/05d/flux.py | 113 +++ .../weno3/python/05d/initial_condition.py | 146 +++ .../weno3/python/05d/mesh.py | 26 + .../weno3/python/05d/plotter.py | 107 ++ .../python/05d/reconstructor/__init__.py | 4 + .../weno3/python/05d/reconstructor/base.py | 7 + .../weno3/python/05d/reconstructor/eno.py | 88 ++ .../weno3/python/05d/reconstructor/factory.py | 35 + .../weno3/python/05d/reconstructor/weno3.py | 86 ++ .../weno3/python/05d/registry.py | 56 ++ .../weno3/python/05d/residual.py | 40 + .../weno3/python/05d/run_eno_weno.py | 52 + .../weno3/python/05d/solution.py | 40 + .../weno3/python/05d/solver.py | 90 ++ .../weno3/python/05d/time_integration.py | 173 ++++ .../weno3/python/05e/boundary.py | 136 +++ .../weno3/python/05e/config.py | 41 + .../weno3/python/05e/domain.py | 56 ++ .../weno3/python/05e/flux.py | 113 +++ .../weno3/python/05e/initial_condition.py | 146 +++ .../weno3/python/05e/mesh.py | 26 + .../weno3/python/05e/plotter.py | 107 ++ .../python/05e/reconstructor/__init__.py | 4 + .../weno3/python/05e/reconstructor/base.py | 7 + .../weno3/python/05e/reconstructor/eno.py | 108 ++ .../weno3/python/05e/reconstructor/factory.py | 66 ++ .../weno3/python/05e/reconstructor/weno3.py | 105 ++ .../weno3/python/05e/registry.py | 56 ++ .../weno3/python/05e/residual.py | 40 + .../weno3/python/05e/run_eno_weno.py | 52 + .../weno3/python/05e/solution.py | 40 + .../weno3/python/05e/solver.py | 90 ++ .../weno3/python/05e/time_integration.py | 173 ++++ .../weno3/python/05f/boundary.py | 151 +++ .../weno3/python/05f/config.py | 41 + .../weno3/python/05f/domain.py | 56 ++ .../weno3/python/05f/flux.py | 113 +++ .../weno3/python/05f/initial_condition.py | 146 +++ .../weno3/python/05f/mesh.py | 26 + .../weno3/python/05f/plotter.py | 107 ++ .../python/05f/reconstructor/__init__.py | 4 + .../weno3/python/05f/reconstructor/base.py | 7 + .../weno3/python/05f/reconstructor/eno.py | 108 ++ .../weno3/python/05f/reconstructor/factory.py | 66 ++ .../weno3/python/05f/reconstructor/weno3.py | 105 ++ .../weno3/python/05f/registry.py | 56 ++ .../weno3/python/05f/residual.py | 40 + .../weno3/python/05f/run_eno_weno.py | 52 + .../weno3/python/05f/solution.py | 40 + .../weno3/python/05f/solver.py | 90 ++ .../weno3/python/05f/test_all_boundaries.py | 68 ++ .../python/05f/test_registry_complete.py | 75 ++ .../weno3/python/05f/time_integration.py | 173 ++++ .../weno3/python/06/boundary.py | 103 ++ .../weno3/python/06/cfd_registry.py | 62 ++ .../weno3/python/06/config.py | 41 + .../weno3/python/06/domain.py | 56 ++ .../weno3/python/06/factories/base_factory.py | 49 + .../weno3/python/06/flux.py | 73 ++ .../weno3/python/06/initial_condition.py | 90 ++ .../weno3/python/06/mesh.py | 26 + .../weno3/python/06/plotter.py | 107 ++ .../weno3/python/06/reconstructor/__init__.py | 4 + .../weno3/python/06/reconstructor/base.py | 7 + .../weno3/python/06/reconstructor/eno.py | 90 ++ .../weno3/python/06/reconstructor/factory.py | 42 + .../weno3/python/06/reconstructor/weno3.py | 88 ++ .../weno3/python/06/registry.py | 75 ++ .../weno3/python/06/residual.py | 40 + .../weno3/python/06/run_eno_weno.py | 52 + .../weno3/python/06/solution.py | 40 + .../weno3/python/06/solver.py | 86 ++ .../python/06/test_simplified_imports.py | 45 + .../weno3/python/06/time_integration.py | 125 +++ .../weno3/python/06a/Project.toml | 2 + .../weno3/python/06a/boundary.py | 103 ++ .../weno3/python/06a/cfd_registry.py | 62 ++ .../weno3/python/06a/config.py | 41 + .../weno3/python/06a/domain.py | 56 ++ .../python/06a/factories/base_factory.py | 49 + .../weno3/python/06a/flux.py | 73 ++ .../weno3/python/06a/initial_condition.py | 90 ++ .../weno3/python/06a/mesh.py | 26 + .../weno3/python/06a/plotter.py | 107 ++ .../python/06a/reconstructor/__init__.py | 4 + .../weno3/python/06a/reconstructor/base.py | 7 + .../weno3/python/06a/reconstructor/eno.py | 90 ++ .../weno3/python/06a/reconstructor/factory.py | 42 + .../weno3/python/06a/reconstructor/weno3.py | 88 ++ .../weno3/python/06a/registry.py | 75 ++ .../weno3/python/06a/residual.py | 40 + .../weno3/python/06a/run_eno_weno.py | 52 + .../weno3/python/06a/solution.py | 40 + .../weno3/python/06a/solver.py | 86 ++ .../weno3/python/06a/time_integration.py | 125 +++ .../weno3/python/06b/boundary.py | 104 ++ .../weno3/python/06b/cfd_registry.py | 62 ++ .../weno3/python/06b/config.py | 41 + .../weno3/python/06b/domain.py | 56 ++ .../python/06b/factories/base_factory.py | 49 + .../weno3/python/06b/flux/__init__.py | 7 + .../weno3/python/06b/flux/base.py | 25 + .../weno3/python/06b/flux/engquist_osher.py | 19 + .../weno3/python/06b/flux/factory.py | 25 + .../weno3/python/06b/flux/rusanov.py | 21 + .../weno3/python/06b/initial_condition.py | 91 ++ .../weno3/python/06b/mesh.py | 26 + .../weno3/python/06b/plotter.py | 109 ++ .../python/06b/reconstructor/__init__.py | 4 + .../weno3/python/06b/reconstructor/base.py | 7 + .../weno3/python/06b/reconstructor/eno.py | 90 ++ .../weno3/python/06b/reconstructor/factory.py | 42 + .../weno3/python/06b/reconstructor/weno3.py | 59 ++ .../weno3/python/06b/registry.py | 75 ++ .../weno3/python/06b/residual.py | 39 + .../weno3/python/06b/run_eno_weno.py | 54 + .../weno3/python/06b/solution.py | 40 + .../weno3/python/06b/solver.py | 79 ++ .../weno3/python/06b/time_integration.py | 125 +++ .../weno3/python/06c/boundary.py | 104 ++ .../weno3/python/06c/cfd_registry.py | 62 ++ .../weno3/python/06c/config.py | 41 + .../weno3/python/06c/domain.py | 56 ++ .../python/06c/factories/base_factory.py | 49 + .../weno3/python/06c/flux/__init__.py | 7 + .../weno3/python/06c/flux/base.py | 25 + .../weno3/python/06c/flux/engquist_osher.py | 19 + .../weno3/python/06c/flux/factory.py | 25 + .../weno3/python/06c/flux/rusanov.py | 21 + .../weno3/python/06c/initial_condition.py | 91 ++ .../weno3/python/06c/mesh.py | 26 + .../weno3/python/06c/plotter.py | 109 ++ .../python/06c/reconstructor/__init__.py | 4 + .../weno3/python/06c/reconstructor/base.py | 7 + .../weno3/python/06c/reconstructor/eno.py | 90 ++ .../weno3/python/06c/reconstructor/factory.py | 42 + .../weno3/python/06c/reconstructor/weno3.py | 59 ++ .../weno3/python/06c/registry.py | 75 ++ .../weno3/python/06c/residual.py | 39 + .../weno3/python/06c/run_eno_weno.py | 54 + .../weno3/python/06c/solution.py | 40 + .../weno3/python/06c/solver.py | 79 ++ .../weno3/python/06c/time_integration.py | 125 +++ .../weno3/python/06d/boundary.py | 104 ++ .../weno3/python/06d/cfd_registry.py | 62 ++ .../weno3/python/06d/config.py | 41 + .../weno3/python/06d/domain.py | 56 ++ .../python/06d/factories/base_factory.py | 49 + .../weno3/python/06d/flux/__init__.py | 7 + .../weno3/python/06d/flux/base.py | 25 + .../weno3/python/06d/flux/engquist_osher.py | 19 + .../weno3/python/06d/flux/factory.py | 25 + .../weno3/python/06d/flux/rusanov.py | 21 + .../weno3/python/06d/initial_condition.py | 91 ++ .../weno3/python/06d/mesh.py | 26 + .../weno3/python/06d/plotter.py | 109 ++ .../python/06d/reconstructor/__init__.py | 4 + .../weno3/python/06d/reconstructor/base.py | 7 + .../weno3/python/06d/reconstructor/eno.py | 90 ++ .../weno3/python/06d/reconstructor/factory.py | 42 + .../weno3/python/06d/reconstructor/weno3.py | 59 ++ .../weno3/python/06d/registry.py | 75 ++ .../weno3/python/06d/residual.py | 39 + .../weno3/python/06d/run_eno_weno.py | 54 + .../weno3/python/06d/solution.py | 40 + .../weno3/python/06d/solver.py | 79 ++ .../weno3/python/06d/time_integration.py | 125 +++ .../weno3/python/06e/boundary.py | 104 ++ .../weno3/python/06e/cfd_registry.py | 62 ++ .../weno3/python/06e/config.py | 41 + .../weno3/python/06e/domain.py | 56 ++ .../python/06e/factories/base_factory.py | 49 + .../weno3/python/06e/flux/__init__.py | 7 + .../weno3/python/06e/flux/base.py | 25 + .../weno3/python/06e/flux/engquist_osher.py | 19 + .../weno3/python/06e/flux/factory.py | 25 + .../weno3/python/06e/flux/rusanov.py | 21 + .../weno3/python/06e/initial_condition.py | 91 ++ .../weno3/python/06e/mesh.py | 26 + .../weno3/python/06e/plotter.py | 109 ++ .../python/06e/reconstructor/__init__.py | 4 + .../weno3/python/06e/reconstructor/base.py | 7 + .../weno3/python/06e/reconstructor/eno.py | 90 ++ .../weno3/python/06e/reconstructor/factory.py | 42 + .../weno3/python/06e/reconstructor/weno3.py | 59 ++ .../weno3/python/06e/registry.py | 75 ++ .../weno3/python/06e/residual.py | 40 + .../weno3/python/06e/run_eno_weno.py | 54 + .../weno3/python/06e/solution.py | 40 + .../weno3/python/06e/solver.py | 67 ++ .../weno3/python/06e/time_integration.py | 126 +++ .../weno3/python/06f/boundary.py | 104 ++ .../weno3/python/06f/cfd_registry.py | 62 ++ .../weno3/python/06f/config.py | 41 + .../weno3/python/06f/domain.py | 56 ++ .../python/06f/factories/base_factory.py | 49 + .../weno3/python/06f/flux/__init__.py | 7 + .../weno3/python/06f/flux/base.py | 25 + .../weno3/python/06f/flux/engquist_osher.py | 19 + .../weno3/python/06f/flux/factory.py | 25 + .../weno3/python/06f/flux/rusanov.py | 21 + .../weno3/python/06f/initial_condition.py | 103 ++ .../weno3/python/06f/mesh.py | 26 + .../weno3/python/06f/plotter.py | 109 ++ .../python/06f/reconstructor/__init__.py | 4 + .../weno3/python/06f/reconstructor/base.py | 7 + .../weno3/python/06f/reconstructor/eno.py | 90 ++ .../weno3/python/06f/reconstructor/factory.py | 42 + .../weno3/python/06f/reconstructor/weno3.py | 59 ++ .../weno3/python/06f/registry.py | 75 ++ .../weno3/python/06f/residual.py | 40 + .../weno3/python/06f/run_eno_weno.py | 54 + .../weno3/python/06f/solution.py | 40 + .../weno3/python/06f/solver.py | 57 ++ .../weno3/python/06f/time_integration.py | 126 +++ .../weno3/python/06g/boundary.py | 104 ++ .../weno3/python/06g/cfd_registry.py | 62 ++ .../weno3/python/06g/config.py | 41 + .../weno3/python/06g/domain.py | 56 ++ .../python/06g/factories/base_factory.py | 49 + .../weno3/python/06g/flux/__init__.py | 7 + .../weno3/python/06g/flux/base.py | 25 + .../weno3/python/06g/flux/engquist_osher.py | 19 + .../weno3/python/06g/flux/factory.py | 25 + .../weno3/python/06g/flux/rusanov.py | 21 + .../weno3/python/06g/initial_condition.py | 107 ++ .../weno3/python/06g/mesh.py | 26 + .../weno3/python/06g/plotter.py | 109 ++ .../python/06g/reconstructor/__init__.py | 4 + .../weno3/python/06g/reconstructor/base.py | 7 + .../weno3/python/06g/reconstructor/eno.py | 90 ++ .../weno3/python/06g/reconstructor/factory.py | 42 + .../weno3/python/06g/reconstructor/weno3.py | 59 ++ .../weno3/python/06g/registry.py | 75 ++ .../weno3/python/06g/residual.py | 40 + .../weno3/python/06g/result.py | 21 + .../weno3/python/06g/run_eno_weno.py | 54 + .../weno3/python/06g/solution.py | 40 + .../weno3/python/06g/solver.py | 42 + .../weno3/python/06g/time_integration.py | 126 +++ .../01/eno_weno_coefficients.yaml | 75 ++ .../01/generate_weno_coef.py | 179 ++++ .../01/weno_reconstructors.py | 92 ++ modern-cfd/src/cgns/include/Cgns_t.h | 2 +- modern-cfd/src/cgns/include/cgnstest.h | 2 +- modern-cfd/src/cgns/src/Cgns_t.cpp | 2 +- modern-cfd/src/cgns/src/cgnstest.cpp | 2 +- modern-cfd/src/geometry/include/Geom.h | 2 +- modern-cfd/src/geometry/include/Grid.h | 2 +- modern-cfd/src/geometry/src/Geom.cpp | 2 +- modern-cfd/src/geometry/src/Grid.cpp | 2 +- modern-cfd/src/main/include/Simu.h | 2 +- modern-cfd/src/main/src/Simu.cpp | 2 +- modern-cfd/src/main/src/main.cpp | 2 +- modern-cfd/src/parallel/include/Cmpi.h | 2 +- modern-cfd/src/parallel/src/Cmpi.cpp | 2 +- modern-cfd/src/project/include/Project.h | 2 +- modern-cfd/src/project/src/Project.cpp | 2 +- modern-cfd/src/solver/include/CfdPara.h | 2 +- modern-cfd/src/solver/include/Solver.h | 2 +- modern-cfd/src/solver/include/SolverDetail.h | 2 +- .../src/solver/include/SolverDetailCpu.h | 2 +- .../src/solver/include/SolverDetailCuda.h | 2 +- modern-cfd/src/solver/src/CfdPara.cpp | 2 +- modern-cfd/src/solver/src/Solver.cpp | 2 +- modern-cfd/src/solver/src/SolverDetail.cpp | 2 +- modern-cfd/src/solver/src/SolverDetailCpu.cpp | 2 +- modern-cfd/src/tools/include/tools.h | 2 +- modern-cfd/src/visualize/include/Visual.h | 2 +- modern-cfd/src/visualize/src/Visual.cpp | 2 +- modern-cfd/ui/MyDataBase.cpp | 2 +- modern-cfd/ui/MyDataBase.h | 2 +- modern-cfd/ui/main.cpp | 2 +- modern-cfd/ui/mainwindow.cpp | 4 +- test/test.py | 2 +- 3916 files changed, 208884 insertions(+), 760 deletions(-) create mode 100644 example/1d-linear-convection/weno3/fortran/FortranLibTest/01/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/FortranLibTest/01/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/FortranLibTest/01/src/mymath.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/FortranLibTest/01/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/FortranLibTest/01/tests/test_mymath.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/FortranLibTest/01a/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/FortranLibTest/01a/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/FortranLibTest/01a/src/mymath.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/FortranLibTest/01a/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/FortranLibTest/01a/tests/test_mymath.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/cfd/01/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/cfd/01/cfd_solver.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/cfd/01/postprocess.py create mode 100644 example/1d-linear-convection/weno3/fortran/cfd/01a/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/cfd/01a/cfd_solver.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/cfd/01a/postprocess.py create mode 100644 example/1d-linear-convection/weno3/fortran/cfd/01b/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/cfd/01b/cfd_solver.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/cfd/01b/postprocess.py create mode 100644 example/1d-linear-convection/weno3/fortran/cfd/01c/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/cfd/01c/cfd_solver.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/cfd/01c/postprocess.py create mode 100644 example/1d-linear-convection/weno3/fortran/cfd/01d/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/cfd/01d/cfd_solver.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/cfd/01d/postprocess.py create mode 100644 example/1d-linear-convection/weno3/fortran/cfd/01e/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/cfd/01e/cfd_solver.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/cfd/01e/postprocess.py create mode 100644 example/1d-linear-convection/weno3/fortran/cfd/01f/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/cfd/01f/README.md create mode 100644 example/1d-linear-convection/weno3/fortran/cfd/01f/cfd_solver.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/cfd/01f/postprocess.py create mode 100644 example/1d-linear-convection/weno3/fortran/cfd/01f/run_all.py create mode 100644 example/1d-linear-convection/weno3/fortran/cfd/01g/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/cfd/01g/README.md create mode 100644 example/1d-linear-convection/weno3/fortran/cfd/01g/cfd_solver.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/cfd/01g/postprocess.py create mode 100644 example/1d-linear-convection/weno3/fortran/cfd/01g/run_all.py create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/01/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/01/main.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/01a/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/01a/main.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/01b/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/01b/main.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/01b/sub1.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/01b/sub2.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/01c/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/01c/main.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/01d/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/01d/main.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/01d/mymod1.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/01d/mymod2.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/01e/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/01e/main.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/01e/src/mymod1.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/01e/src/mymod2.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/01f/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/01f/main.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/01f/src/mymod1.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/01f/src/mymod2.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02/main.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02/src/sub1.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02/src/sub2.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02a/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02a/src/sub1.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02a/src/sub2.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02a/test/main.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02b/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02b/src/sub1.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02b/src/sub2.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02b/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02b/tests/main.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02c/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02c/src/sub1.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02c/src/sub2.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02c/test1/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02c/test1/main1.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02c/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02c/tests/main.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02d/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02d/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02d/src/sub1.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02d/src/sub2.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02d/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02d/tests/main.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02e/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02e/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02e/src/sub1.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02e/src/sub2.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02e/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02e/tests/main.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02f/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02f/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02f/src/sub1.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02f/src/sub2.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02f/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02f/tests/main.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02g/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02g/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02g/src/sub1.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02g/src/sub2.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02g/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02g/tests/main.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02h/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02h/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02h/src/sub1.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02h/src/sub2.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02h/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/02h/tests/main.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03/main.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03a/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03a/main.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03a/mymod1.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03a/mymod2.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03b/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03b/main.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03b/src/mymod1.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03b/src/mymod2.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03c/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03c/main.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03c/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03c/src/mymod1.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03c/src/mymod2.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03d/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03d/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03d/src/mymod1.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03d/src/mymod2.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03d/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03d/tests/main.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03e/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03e/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03e/src/mymath.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03e/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03e/tests/main.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03f/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03f/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03f/src/mymath.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03f/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/grammar/03f/tests/main.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01/tests/test_registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01a/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01a/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01a/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01a/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01a/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01a/tests/test_registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01b/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01b/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01b/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01b/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01b/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01b/src/core/registry_advanced.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01b/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01b/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01b/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/flux/factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/flux/rusanov.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/reconstructor/eno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/reconstructor/eno_type.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/reconstructor/factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/reconstructor/weno3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/reconstructor/weno5.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01b/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01b/tests/test_factory_system.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01b/tests/test_minimal.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01b/tests/test_registry_basic.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01c/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01c/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01c/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01c/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01c/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01c/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01c/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01c/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01c/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01c/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01c/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01c/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01c/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01c/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01c/tests/test_minimal.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01d/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01d/build.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01d/build.ps1 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01d/build.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01d/build_config.yaml create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01d/build_with_config.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01d/clean_build.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01d/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01d/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01d/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01d/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01d/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01d/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01d/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/flux/rusanov.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/reconstructor/eno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/reconstructor/weno3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01d/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01d/tests/test_factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01d/tests/test_minimal.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01d/tests/test_minimal_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01e/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01e/README.md create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01e/scripts/build.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01e/scripts/build.ps1 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01e/scripts/build.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01e/scripts/requirements.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01e/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01e/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01e/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01e/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01e/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01e/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01e/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/flux/rusanov.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/reconstructor/eno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/reconstructor/weno3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01e/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01e/tests/test_factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01e/tests/test_minimal.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01e/tests/test_minimal_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01f/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01f/README.md create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01f/scripts/build.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01f/scripts/build.ps1 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01f/scripts/build.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01f/scripts/requirements.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01f/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01f/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01f/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01f/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01f/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01f/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01f/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/flux/rusanov.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/reconstructor/eno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/reconstructor/weno3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01f/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01f/tests/test_factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01f/tests/test_minimal.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01f/tests/test_minimal_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01g/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01g/README.md create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01g/scripts/build.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01g/scripts/build.ps1 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01g/scripts/build.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01g/scripts/requirements.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01g/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01g/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01g/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01g/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01g/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01g/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01g/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/flux/rusanov.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/reconstructor/eno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/reconstructor/weno3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01g/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01g/tests/test_factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01g/tests/test_factory_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01g/tests/test_minimal.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01g/tests/test_minimal_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01g/tests/test_minimal_simpleBAK.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01h/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01h/README.md create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01h/scripts/build.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01h/scripts/build.ps1 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01h/scripts/build.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01h/scripts/requirements.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01h/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01h/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01h/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01h/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01h/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01h/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01h/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/flux/rusanov.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/reconstructor/eno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/reconstructor/weno3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01h/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01h/tests/test_factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01h/tests/test_factory_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01h/tests/test_minimal.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01h/tests/test_minimal_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01h/tests/test_minimal_simpleBAK.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01i/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01i/README.md create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01i/scripts/build.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01i/scripts/build.ps1 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01i/scripts/build.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01i/scripts/requirements.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01i/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01i/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01i/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01i/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01i/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01i/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01i/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/flux/rusanov.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/reconstructor/eno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/reconstructor/weno3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01i/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01i/tests/test_factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01i/tests/test_factory_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01i/tests/test_factory_simpleBAK.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01i/tests/test_minimal.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01i/tests/test_minimal_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01i/tests/test_minimal_simpleBAK.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/README.md create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/scripts/build.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/scripts/build.ps1 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/scripts/build.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/scripts/requirements.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/flux/rusanov.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/reconstructor/eno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/reconstructor/weno3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/tests/test_factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/tests/test_factory_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/tests/test_factory_simpleBAK.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/tests/test_minimal.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/tests/test_minimal_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/tests/test_minimal_simpleBAK.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01j/tests/test_simple_link.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01k/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01k/README.md create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01k/scripts/build.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01k/scripts/build.ps1 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01k/scripts/build.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01k/scripts/requirements.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01k/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01k/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01k/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01k/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01k/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01k/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01k/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/flux/rusanov.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/reconstructor/eno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/reconstructor/weno3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01k/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01k/tests/test_factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01k/tests/test_factory_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01k/tests/test_minimal.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01k/tests/test_minimal_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01k/tests/test_simple_link.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01l/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01l/README.md create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01l/scripts/build.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01l/scripts/build.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01l/scripts/requirements.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01l/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01l/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01l/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01l/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01l/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01l/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01l/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/flux/rusanov.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/reconstructor/eno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/reconstructor/weno3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01l/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01l/tests/test_factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01l/tests/test_factory_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01l/tests/test_minimal.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01l/tests/test_minimal_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01l/tests/test_simple_link.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01m/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01m/README.md create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01m/scripts/build.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01m/scripts/build.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01m/scripts/requirements.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01m/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01m/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01m/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01m/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01m/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01m/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01m/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/flux/rusanov.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/reconstructor/eno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/reconstructor/weno3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01m/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01m/tests/test_factory_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01m/tests/test_minimal_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/01m/tests/test_simple_link.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02/README.md create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02/scripts/build.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02/scripts/build.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02/scripts/requirements.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/flux/rusanov.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/reconstructor/eno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/reconstructor/weno3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02/tests/test_factory_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02/tests/test_minimal_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02/tests/test_simple_link.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02a/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02a/README.md create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02a/scripts/build.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02a/scripts/build.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02a/scripts/requirements.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02a/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02a/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02a/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02a/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02a/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02a/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02a/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/flux/rusanov.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/reconstructor/eno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/reconstructor/weno3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02a/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02a/tests/test_factory_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02a/tests/test_minimal_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02a/tests/test_simple_link.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02b/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02b/README.md create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02b/scripts/build.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02b/scripts/build.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02b/scripts/requirements.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02b/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02b/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02b/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02b/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02b/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02b/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02b/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02b/src/manager/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02b/src/manager/component_factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02b/src/manager/component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/flux/rusanov.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/reconstructor/eno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/reconstructor/weno3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02b/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02b/tests/test_component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02b/tests/test_factory_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02b/tests/test_minimal_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02b/tests/test_simple_link.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/README.md create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/scripts/build.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/scripts/build.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/scripts/requirements.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/src/manager/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/src/manager/component_factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/src/manager/component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/flux/rusanov.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/reconstructor/eno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/reconstructor/weno3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/tests/test_architecture.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/tests/test_component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/tests/test_factory_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/tests/test_minimal_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02c/tests/test_simple_link.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/README.md create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/scripts/build.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/scripts/build.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/scripts/requirements.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/src/base/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/src/base/modules.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/src/core/factory_base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/src/infrastructure/domain.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/src/infrastructure/solution.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/src/manager/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/src/manager/component_factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/src/manager/component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/flux/rusanov.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/reconstructor/eno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/reconstructor/weno3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_architecture.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_basic_only.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_cfd_architecture.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_factory_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_minimal_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_simple_link.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_solver_framework.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/README.md create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/scripts/build.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/scripts/build.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/scripts/requirements.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/src/base/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/src/base/modules.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/src/core/factory_base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/src/infrastructure/domain.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/src/infrastructure/solution.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/src/manager/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/src/manager/component_factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/src/manager/component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/flux/rusanov.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/reconstructor/eno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/reconstructor/weno3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_architecture.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_basic_only.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_cfd_architecture.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_factory_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_minimal_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_simple_link.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_solver_framework.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/README.md create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/scripts/build.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/scripts/build.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/scripts/requirements.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/src/base/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/src/base/modules.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/src/core/factory_base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/src/infrastructure/domain.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/src/infrastructure/solution.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/src/manager/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/src/manager/component_factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/src/manager/component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/flux/rusanov.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/reconstructor/eno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/reconstructor/weno3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_architecture.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_basic_only.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_cfd_architecture.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_domain_solution.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_factory_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_minimal_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_physics_equations.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_simple_link.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/README.md create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/scripts/build.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/scripts/build.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/scripts/requirements.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/scripts/run_step1.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/scripts/run_step1.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/src/base/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/src/base/modules.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/src/base/precision.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/src/core/factory_base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/src/infrastructure/domain.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/src/infrastructure/solution.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/src/manager/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/src/manager/component_factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/src/manager/component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/flux/rusanov.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/reconstructor/eno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/reconstructor/weno3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/src/physics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/src/physics/equations/linear_convection.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/src/physics/physics_interface.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/src/physics/problems/linear_convection_problem.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_architecture.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_basic_only.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_cfd_architecture.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_domain_solution.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_factory_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_minimal_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_physics_minimal.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_simple_link.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_solver_framework.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/README.md create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/scripts/build.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/scripts/build.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/scripts/requirements.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/scripts/run_all_steps.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/scripts/run_step1.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/scripts/run_step1.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/scripts/run_step2.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/scripts/run_step2.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/src/base/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/src/base/modules.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/src/base/precision.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/src/core/factory_base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/src/infrastructure/domain.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/src/infrastructure/solution.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/src/manager/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/src/manager/component_factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/src/manager/component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/flux/rusanov.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/reconstructor/eno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/reconstructor/weno3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/src/physics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/src/physics/equations/linear_convection.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/src/physics/physics_interface.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/src/physics/problems/linear_convection_problem.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_architecture.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_basic_only.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_cfd_architecture.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_config_physics.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_domain_solution.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_factory_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_minimal_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_physics_minimal.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_simple_link.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_solver_framework.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/README.md create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/scripts/build.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/scripts/build.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/scripts/requirements.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/scripts/run_all_steps.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/scripts/run_step1.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/scripts/run_step1.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/scripts/run_step2.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/scripts/run_step2.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/base/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/base/modules.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/base/precision.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/core/factory_base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/infrastructure/domain.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/infrastructure/solution.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/manager/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/manager/component_factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/manager/component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/flux/rusanov.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/reconstructor/eno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/reconstructor/weno3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/physics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/physics/equations/linear_convection.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/physics/physics_interface.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/physics/problems/linear_convection_problem.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/solver/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/src/solver/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/tests/test_architecture.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/tests/test_basic_only.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/tests/test_cfd_architecture.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/tests/test_component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/tests/test_config_physics.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/tests/test_domain_solution.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/tests/test_factory_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/tests/test_minimal_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/tests/test_physics_minimal.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/tests/test_simple_link.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/tests/test_solver_base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03/tests/test_solver_framework.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/README.md create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/scripts/build.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/scripts/build.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/scripts/requirements.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/scripts/run_all_steps.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/scripts/run_step1.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/scripts/run_step1.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/scripts/run_step2.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/scripts/run_step2.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/base/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/base/modules.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/base/precision.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/core/factory_base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/infrastructure/domain.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/infrastructure/solution.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/manager/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/manager/component_factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/manager/component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/flux/rusanov.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/reconstructor/eno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/reconstructor/weno3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/physics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/physics/equations/linear_convection.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/physics/physics_interface.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/physics/problems/linear_convection_problem.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/solver/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/src/solver/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_architecture.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_basic_only.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_cfd_architecture.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_component_manager_physics.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_config_physics.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_domain_solution.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_factory_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_minimal_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_physics_minimal.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_simple_link.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_solver_base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_solver_framework.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/README.md create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/examples/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/examples/run_eno_weno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/scripts/build.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/scripts/build.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/scripts/requirements.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/scripts/run_all_steps.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/scripts/run_example.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/scripts/run_step1.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/scripts/run_step1.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/scripts/run_step2.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/scripts/run_step2.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/base/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/base/modules.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/base/precision.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/core/factory_base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/core/registry_initializer.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/infrastructure/domain.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/infrastructure/solution.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/manager/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/manager/component_factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/manager/component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/flux/rusanov.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/reconstructor/eno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/reconstructor/weno3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/reconstructor/weno5.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/physics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/physics/equations/linear_convection.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/physics/physics_interface.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/physics/problems/linear_convection_problem.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/run_eno_weno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/solver/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/solver/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/src/solver/physics_solver.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_architecture.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_cfd_architecture.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_component_manager_physics.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_config_physics.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_domain_solution.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_factory_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_infrastructure.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_physics.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_physics_solver.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_simple_link.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_solver_base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_solver_framework.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/README.md create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/examples/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/examples/run_eno_weno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/scripts/build.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/scripts/build.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/scripts/requirements.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/scripts/run_all_steps.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/scripts/run_example.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/scripts/run_step1.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/scripts/run_step1.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/scripts/run_step2.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/scripts/run_step2.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/base/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/base/modules.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/base/precision.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/core/factory_base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/core/registry_initializer.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/infrastructure/domain.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/infrastructure/solution.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/manager/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/manager/component_factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/manager/component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/flux/rusanov.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/reconstructor/eno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/reconstructor/weno3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/reconstructor/weno5.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/physics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/physics/equations/linear_convection.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/physics/physics_interface.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/physics/problems/linear_convection_problem.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/run_eno_weno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/solver/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/solver/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/src/solver/physics_solver.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/test_physics_solver_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_architecture.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_cfd_architecture.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_component_manager_physics.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_config_physics.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_domain_solution.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_factory_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_infrastructure.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_physics.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_physics_solver.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_physics_solver_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_simple_link.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_solver_base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_solver_framework.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/README.md create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/examples/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/examples/run_eno_weno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/scripts/build.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/scripts/build.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/scripts/requirements.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/scripts/run_all_steps.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/scripts/run_example.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/scripts/run_step1.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/scripts/run_step1.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/scripts/run_step2.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/scripts/run_step2.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/base/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/base/modules.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/base/precision.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/core/factory_base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/core/registry_initializer.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/infrastructure/domain.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/infrastructure/solution.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/manager/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/manager/component_factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/manager/component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/flux/rusanov.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/reconstructor/eno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/reconstructor/weno3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/reconstructor/weno5.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/physics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/physics/equations/linear_convection.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/physics/physics_interface.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/physics/problems/linear_convection_problem.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/solver/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/solver/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/src/solver/physics_solver.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_architecture.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_cfd_architecture.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_component_manager_physics.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_config_physics.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_domain_solution.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_factory_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_infrastructure.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_physics.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_physics_solver.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_physics_solver_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_simple_link.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_solver_base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_solver_framework.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/README.md create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/examples/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/examples/run_eno_weno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/python/plot_results.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/scripts/build.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/scripts/build.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/scripts/requirements.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/scripts/run_all_steps.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/scripts/run_example.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/scripts/run_step1.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/scripts/run_step1.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/scripts/run_step2.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/scripts/run_step2.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/base/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/base/modules.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/base/precision.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/core/factory_base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/core/registry_initializer.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/infrastructure/domain.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/infrastructure/solution.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/manager/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/manager/component_factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/manager/component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/flux/rusanov.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/reconstructor/eno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/reconstructor/weno3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/reconstructor/weno5.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/physics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/physics/equations/linear_convection.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/physics/physics_interface.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/physics/problems/linear_convection_problem.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/results.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/solver/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/solver/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/src/solver/physics_solver.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_architecture.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_cfd_architecture.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_component_manager_physics.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_config_physics.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_domain_solution.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_factory_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_infrastructure.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_physics.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_physics_solver.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_physics_solver_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_simple_link.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_solver_base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_solver_framework.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/README.md create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/examples/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/examples/run_eno_weno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/examples/run_eno_weno_integrated.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/python/plot_results.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/scripts/build.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/scripts/build.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/scripts/requirements.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/scripts/run_all_steps.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/scripts/run_example.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/scripts/run_step1.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/scripts/run_step1.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/scripts/run_step2.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/scripts/run_step2.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/scripts/test_integrated.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/base/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/base/modules.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/base/precision.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/boundary/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/boundary/boundary_base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/boundary/dirichlet.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/boundary/factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/boundary/neumann.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/boundary/periodic.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/core/factory_base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/core/factory_integrated.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/core/physics_solver_integrated.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/core/registry_initializer.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/infrastructure/domain.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/infrastructure/solution.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/initial_condition/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/initial_condition/factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/initial_condition/gaussian.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/initial_condition/ic_base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/initial_condition/sine.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/initial_condition/step.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/manager/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/manager/component_factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/manager/component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/flux/engquist_osher.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/flux/rusanov.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/reconstructor/eno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/reconstructor/weno3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/reconstructor/weno5.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/physics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/physics/equations/linear_convection.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/physics/physics_interface.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/physics/problems/linear_advection_problem.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/physics/problems/linear_convection_problem.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/results.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/solver/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/solver/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/solver/physics_solver.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/solver/residual.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/src/solver/solver_integrated.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_architecture.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_cfd_architecture.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_component_manager_physics.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_config_physics.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_domain_solution.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_factory_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_infrastructure.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_initial_condition.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_physics.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_physics_solver.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_physics_solver_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_simple_link.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_solver_base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_solver_framework.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/README.md create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/examples/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/examples/run_eno_weno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/examples/run_eno_weno_integrated.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/python/plot_results.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/scripts/build.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/scripts/build.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/scripts/requirements.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/scripts/run_all_steps.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/scripts/run_example.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/scripts/run_step1.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/scripts/run_step1.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/scripts/run_step2.bat create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/scripts/run_step2.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/scripts/test_integrated.py create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/base/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/base/modules.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/base/precision.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/boundary/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/boundary/boundary_base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/boundary/dirichlet.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/boundary/factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/boundary/neumann.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/boundary/periodic.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/core/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/core/factory_base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/core/factory_integrated.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/core/factory_interfaces.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/core/physics_solver_integrated.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/core/registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/core/registry_initializer.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/infrastructure/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/infrastructure/config.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/infrastructure/domain.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/infrastructure/mesh.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/infrastructure/solution.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/initial_condition/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/initial_condition/factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/initial_condition/gaussian.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/initial_condition/ic_base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/initial_condition/sine.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/initial_condition/step.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/manager/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/manager/component_factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/manager/component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/flux/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/flux/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/flux/engquist_osher.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/flux/rusanov.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/reconstructor/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/reconstructor/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/reconstructor/eno.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/reconstructor/weno3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/reconstructor/weno5.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/time_integration/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/time_integration/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/time_integration/base_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/time_integration/factory.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/time_integration/rk1.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/time_integration/rk2.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/time_integration/rk3.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/physics/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/physics/equations/linear_convection.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/physics/physics_interface.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/physics/problems/linear_advection_problem.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/physics/problems/linear_convection_problem.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/results.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/solver/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/solver/base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/solver/physics_solver.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/solver/residual.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/solver/residual_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/src/solver/solver_integrated.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/tests/CMakeLists.txt create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_architecture.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_cfd_architecture.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_component_manager.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_component_manager_physics.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_config_physics.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_domain_solution.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_factory_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_infrastructure.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_initial_condition.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_physics.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_physics_solver.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_physics_solver_simple.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_registry.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_simple_link.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_solver_base.f90 create mode 100644 example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_solver_framework.f90 create mode 100644 example/1d-linear-convection/weno3/julia/01/julia/boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/01/julia/test/test_boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/01/python/boundary.py create mode 100644 example/1d-linear-convection/weno3/julia/01/python/cfd_registry.py create mode 100644 example/1d-linear-convection/weno3/julia/01/python/config.py create mode 100644 example/1d-linear-convection/weno3/julia/01/python/domain.py create mode 100644 example/1d-linear-convection/weno3/julia/01/python/factories/base_factory.py create mode 100644 example/1d-linear-convection/weno3/julia/01/python/flux.py create mode 100644 example/1d-linear-convection/weno3/julia/01/python/gen_boundary_test_data.py create mode 100644 example/1d-linear-convection/weno3/julia/01/python/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/julia/01/python/mesh.py create mode 100644 example/1d-linear-convection/weno3/julia/01/python/plotter.py create mode 100644 example/1d-linear-convection/weno3/julia/01/python/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/julia/01/python/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/julia/01/python/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/julia/01/python/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/julia/01/python/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/julia/01/python/registry.py create mode 100644 example/1d-linear-convection/weno3/julia/01/python/residual.py create mode 100644 example/1d-linear-convection/weno3/julia/01/python/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/julia/01/python/solution.py create mode 100644 example/1d-linear-convection/weno3/julia/01/python/solver.py create mode 100644 example/1d-linear-convection/weno3/julia/01/python/time_integration.py create mode 100644 example/1d-linear-convection/weno3/julia/01/python/u_dirichlet_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01/python/u_input.npy create mode 100644 example/1d-linear-convection/weno3/julia/01/python/u_neumann_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01/python/u_periodic_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01a/julia/boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/01a/julia/initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/01a/julia/test/test_boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/01a/julia/test/test_initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/boundary.py create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/cfd_registry.py create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/config.py create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/domain.py create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/factories/base_factory.py create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/flux.py create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/gen_boundary_test_data.py create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/gen_ic_test_data.py create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/mesh.py create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/plotter.py create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/registry.py create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/residual.py create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/solution.py create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/solver.py create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/time_integration.py create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/u_dirichlet_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/u_gaussian_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/u_gaussian_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/u_input.npy create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/u_neumann_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/u_periodic_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/u_sin_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/u_sin_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/u_step_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01a/python/u_step_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01b/julia/boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/01b/julia/initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/01b/julia/mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/01b/julia/test/test_boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/01b/julia/test/test_initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/01b/julia/test/test_mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/boundary.py create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/cfd_registry.py create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/config.py create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/domain.py create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/factories/base_factory.py create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/flux.py create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/gen_boundary_test_data.py create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/gen_ic_test_data.py create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/mesh.py create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/plotter.py create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/registry.py create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/residual.py create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/solution.py create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/solver.py create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/time_integration.py create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/u_dirichlet_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/u_gaussian_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/u_gaussian_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/u_input.npy create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/u_neumann_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/u_periodic_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/u_sin_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/u_sin_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/u_step_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01b/python/u_step_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01c/julia/boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/01c/julia/domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/01c/julia/initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/01c/julia/mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/01c/julia/test/test_boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/01c/julia/test/test_domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/01c/julia/test/test_initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/01c/julia/test/test_mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/boundary.py create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/cfd_registry.py create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/config.py create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/domain.py create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/factories/base_factory.py create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/flux.py create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/gen_boundary_test_data.py create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/gen_ic_test_data.py create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/mesh.py create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/plotter.py create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/registry.py create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/residual.py create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/solution.py create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/solver.py create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/time_integration.py create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/u_dirichlet_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/u_gaussian_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/u_gaussian_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/u_input.npy create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/u_neumann_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/u_periodic_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/u_sin_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/u_sin_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/u_step_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01c/python/u_step_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01d/julia/boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/01d/julia/domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/01d/julia/initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/01d/julia/mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/01d/julia/solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/01d/julia/test/test_boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/01d/julia/test/test_domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/01d/julia/test/test_initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/01d/julia/test/test_mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/01d/julia/test/test_solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/boundary.py create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/cfd_registry.py create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/config.py create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/domain.py create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/factories/base_factory.py create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/flux.py create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/gen_boundary_test_data.py create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/gen_ic_test_data.py create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/mesh.py create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/plotter.py create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/registry.py create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/residual.py create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/solution.py create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/solver.py create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/time_integration.py create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/u_dirichlet_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/u_gaussian_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/u_gaussian_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/u_input.npy create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/u_neumann_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/u_periodic_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/u_sin_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/u_sin_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/u_step_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01d/python/u_step_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01e/julia/boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/01e/julia/domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/01e/julia/flux.jl create mode 100644 example/1d-linear-convection/weno3/julia/01e/julia/initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/01e/julia/mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/01e/julia/solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/01e/julia/test/test_boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/01e/julia/test/test_domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/01e/julia/test/test_flux.jl create mode 100644 example/1d-linear-convection/weno3/julia/01e/julia/test/test_initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/01e/julia/test/test_mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/01e/julia/test/test_solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/boundary.py create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/cfd_registry.py create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/config.py create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/domain.py create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/factories/base_factory.py create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/flux.py create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/gen_boundary_test_data.py create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/gen_ic_test_data.py create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/mesh.py create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/plotter.py create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/registry.py create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/residual.py create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/solution.py create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/solver.py create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/time_integration.py create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/u_dirichlet_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/u_gaussian_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/u_gaussian_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/u_input.npy create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/u_neumann_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/u_periodic_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/u_sin_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/u_sin_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/u_step_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01e/python/u_step_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01f/julia/boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/01f/julia/domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/01f/julia/flux.jl create mode 100644 example/1d-linear-convection/weno3/julia/01f/julia/initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/01f/julia/mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/01f/julia/residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/01f/julia/solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/01f/julia/test/test_boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/01f/julia/test/test_domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/01f/julia/test/test_flux.jl create mode 100644 example/1d-linear-convection/weno3/julia/01f/julia/test/test_initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/01f/julia/test/test_mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/01f/julia/test/test_residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/01f/julia/test/test_solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/01f/julia/test_residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/boundary.py create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/cfd_registry.py create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/config.py create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/domain.py create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/factories/base_factory.py create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/flux.py create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/gen_boundary_test_data.py create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/gen_ic_test_data.py create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/mesh.py create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/plotter.py create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/registry.py create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/residual.py create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/solution.py create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/solver.py create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/time_integration.py create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/u_dirichlet_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/u_gaussian_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/u_gaussian_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/u_input.npy create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/u_neumann_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/u_periodic_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/u_sin_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/u_sin_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/u_step_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01f/python/u_step_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01g/julia/boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/01g/julia/domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/01g/julia/flux.jl create mode 100644 example/1d-linear-convection/weno3/julia/01g/julia/initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/01g/julia/mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/01g/julia/residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/01g/julia/solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/01g/julia/test/test_boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/01g/julia/test/test_domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/01g/julia/test/test_flux.jl create mode 100644 example/1d-linear-convection/weno3/julia/01g/julia/test/test_initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/01g/julia/test/test_mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/01g/julia/test/test_residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/01g/julia/test/test_solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/01g/julia/test/test_time_integration.jl create mode 100644 example/1d-linear-convection/weno3/julia/01g/julia/test_residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/01g/julia/time_integration.jl create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/boundary.py create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/cfd_registry.py create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/config.py create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/domain.py create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/factories/base_factory.py create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/flux.py create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/gen_boundary_test_data.py create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/gen_ic_test_data.py create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/mesh.py create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/plotter.py create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/registry.py create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/residual.py create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/solution.py create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/solver.py create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/time_integration.py create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/u_dirichlet_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/u_gaussian_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/u_gaussian_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/u_input.npy create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/u_neumann_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/u_periodic_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/u_sin_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/u_sin_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/u_step_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01g/python/u_step_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01h/julia/boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/01h/julia/domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/01h/julia/flux.jl create mode 100644 example/1d-linear-convection/weno3/julia/01h/julia/initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/01h/julia/mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/01h/julia/reconstructor/weno3.jl create mode 100644 example/1d-linear-convection/weno3/julia/01h/julia/residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/01h/julia/solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/01h/julia/test/test_boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/01h/julia/test/test_domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/01h/julia/test/test_flux.jl create mode 100644 example/1d-linear-convection/weno3/julia/01h/julia/test/test_initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/01h/julia/test/test_mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/01h/julia/test/test_residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/01h/julia/test/test_solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/01h/julia/test/test_time_integration.jl create mode 100644 example/1d-linear-convection/weno3/julia/01h/julia/test/test_weno3.jl create mode 100644 example/1d-linear-convection/weno3/julia/01h/julia/test_residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/01h/julia/time_integration.jl create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/boundary.py create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/cfd_registry.py create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/config.py create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/domain.py create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/factories/base_factory.py create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/flux.py create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/gen_boundary_test_data.py create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/gen_ic_test_data.py create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/mesh.py create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/plotter.py create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/registry.py create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/residual.py create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/solution.py create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/solver.py create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/time_integration.py create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/u_dirichlet_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/u_gaussian_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/u_gaussian_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/u_input.npy create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/u_neumann_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/u_periodic_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/u_sin_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/u_sin_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/u_step_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01h/python/u_step_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01i/julia/boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/01i/julia/domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/01i/julia/flux.jl create mode 100644 example/1d-linear-convection/weno3/julia/01i/julia/initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/01i/julia/mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/01i/julia/reconstructor/eno.jl create mode 100644 example/1d-linear-convection/weno3/julia/01i/julia/reconstructor/weno3.jl create mode 100644 example/1d-linear-convection/weno3/julia/01i/julia/residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/01i/julia/solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/01i/julia/test/test_boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/01i/julia/test/test_domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/01i/julia/test/test_eno.jl create mode 100644 example/1d-linear-convection/weno3/julia/01i/julia/test/test_flux.jl create mode 100644 example/1d-linear-convection/weno3/julia/01i/julia/test/test_initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/01i/julia/test/test_mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/01i/julia/test/test_residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/01i/julia/test/test_solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/01i/julia/test/test_time_integration.jl create mode 100644 example/1d-linear-convection/weno3/julia/01i/julia/test/test_weno3.jl create mode 100644 example/1d-linear-convection/weno3/julia/01i/julia/test_residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/01i/julia/time_integration.jl create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/boundary.py create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/cfd_registry.py create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/config.py create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/domain.py create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/eno_weno_comparison.png create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/factories/base_factory.py create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/flux.py create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/gen_boundary_test_data.py create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/gen_ic_test_data.py create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/mesh.py create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/plotter.py create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/registry.py create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/residual.py create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/solution.py create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/solver.py create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/time_integration.py create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/u_dirichlet_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/u_gaussian_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/u_gaussian_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/u_input.npy create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/u_neumann_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/u_periodic_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/u_sin_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/u_sin_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/u_step_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01i/python/u_step_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01j/julia/boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/01j/julia/config.jl create mode 100644 example/1d-linear-convection/weno3/julia/01j/julia/domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/01j/julia/flux.jl create mode 100644 example/1d-linear-convection/weno3/julia/01j/julia/initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/01j/julia/mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/01j/julia/plotter.jl create mode 100644 example/1d-linear-convection/weno3/julia/01j/julia/reconstructor/eno.jl create mode 100644 example/1d-linear-convection/weno3/julia/01j/julia/reconstructor/weno3.jl create mode 100644 example/1d-linear-convection/weno3/julia/01j/julia/residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/01j/julia/run_eno_weno.jl create mode 100644 example/1d-linear-convection/weno3/julia/01j/julia/solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/01j/julia/solver.jl create mode 100644 example/1d-linear-convection/weno3/julia/01j/julia/test/test_boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/01j/julia/test/test_domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/01j/julia/test/test_eno.jl create mode 100644 example/1d-linear-convection/weno3/julia/01j/julia/test/test_flux.jl create mode 100644 example/1d-linear-convection/weno3/julia/01j/julia/test/test_initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/01j/julia/test/test_mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/01j/julia/test/test_residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/01j/julia/test/test_solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/01j/julia/test/test_time_integration.jl create mode 100644 example/1d-linear-convection/weno3/julia/01j/julia/test/test_weno3.jl create mode 100644 example/1d-linear-convection/weno3/julia/01j/julia/test_residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/01j/julia/time_integration.jl create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/boundary.py create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/cfd_registry.py create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/config.py create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/domain.py create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/eno_weno_comparison.png create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/factories/base_factory.py create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/flux.py create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/gen_boundary_test_data.py create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/gen_ic_test_data.py create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/mesh.py create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/plotter.py create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/registry.py create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/residual.py create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/solution.py create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/solver.py create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/time_integration.py create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/u_dirichlet_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/u_gaussian_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/u_gaussian_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/u_input.npy create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/u_neumann_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/u_periodic_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/u_sin_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/u_sin_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/u_step_full_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/01j/python/u_step_interior_py.npy create mode 100644 example/1d-linear-convection/weno3/julia/02/julia/boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/02/julia/config.jl create mode 100644 example/1d-linear-convection/weno3/julia/02/julia/domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/02/julia/flux.jl create mode 100644 example/1d-linear-convection/weno3/julia/02/julia/initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/02/julia/mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/02/julia/plotter.jl create mode 100644 example/1d-linear-convection/weno3/julia/02/julia/reconstructor/eno.jl create mode 100644 example/1d-linear-convection/weno3/julia/02/julia/reconstructor/weno3.jl create mode 100644 example/1d-linear-convection/weno3/julia/02/julia/residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/02/julia/run_eno_weno.jl create mode 100644 example/1d-linear-convection/weno3/julia/02/julia/solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/02/julia/solver.jl create mode 100644 example/1d-linear-convection/weno3/julia/02/julia/time_integration.jl create mode 100644 example/1d-linear-convection/weno3/julia/02/python/boundary.py create mode 100644 example/1d-linear-convection/weno3/julia/02/python/cfd_registry.py create mode 100644 example/1d-linear-convection/weno3/julia/02/python/config.py create mode 100644 example/1d-linear-convection/weno3/julia/02/python/domain.py create mode 100644 example/1d-linear-convection/weno3/julia/02/python/factories/base_factory.py create mode 100644 example/1d-linear-convection/weno3/julia/02/python/flux.py create mode 100644 example/1d-linear-convection/weno3/julia/02/python/gen_boundary_test_data.py create mode 100644 example/1d-linear-convection/weno3/julia/02/python/gen_ic_test_data.py create mode 100644 example/1d-linear-convection/weno3/julia/02/python/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/julia/02/python/mesh.py create mode 100644 example/1d-linear-convection/weno3/julia/02/python/plotter.py create mode 100644 example/1d-linear-convection/weno3/julia/02/python/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/julia/02/python/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/julia/02/python/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/julia/02/python/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/julia/02/python/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/julia/02/python/registry.py create mode 100644 example/1d-linear-convection/weno3/julia/02/python/residual.py create mode 100644 example/1d-linear-convection/weno3/julia/02/python/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/julia/02/python/solution.py create mode 100644 example/1d-linear-convection/weno3/julia/02/python/solver.py create mode 100644 example/1d-linear-convection/weno3/julia/02/python/time_integration.py create mode 100644 example/1d-linear-convection/weno3/julia/02a/julia/boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/02a/julia/config.jl create mode 100644 example/1d-linear-convection/weno3/julia/02a/julia/domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/02a/julia/flux.jl create mode 100644 example/1d-linear-convection/weno3/julia/02a/julia/initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/02a/julia/mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/02a/julia/plotter.jl create mode 100644 example/1d-linear-convection/weno3/julia/02a/julia/reconstructor/eno.jl create mode 100644 example/1d-linear-convection/weno3/julia/02a/julia/reconstructor/weno3.jl create mode 100644 example/1d-linear-convection/weno3/julia/02a/julia/residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/02a/julia/run_eno_weno.jl create mode 100644 example/1d-linear-convection/weno3/julia/02a/julia/solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/02a/julia/solver.jl create mode 100644 example/1d-linear-convection/weno3/julia/02a/julia/time_integration.jl create mode 100644 example/1d-linear-convection/weno3/julia/02a/python/boundary.py create mode 100644 example/1d-linear-convection/weno3/julia/02a/python/cfd_registry.py create mode 100644 example/1d-linear-convection/weno3/julia/02a/python/config.py create mode 100644 example/1d-linear-convection/weno3/julia/02a/python/domain.py create mode 100644 example/1d-linear-convection/weno3/julia/02a/python/factories/base_factory.py create mode 100644 example/1d-linear-convection/weno3/julia/02a/python/flux.py create mode 100644 example/1d-linear-convection/weno3/julia/02a/python/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/julia/02a/python/mesh.py create mode 100644 example/1d-linear-convection/weno3/julia/02a/python/plotter.py create mode 100644 example/1d-linear-convection/weno3/julia/02a/python/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/julia/02a/python/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/julia/02a/python/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/julia/02a/python/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/julia/02a/python/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/julia/02a/python/registry.py create mode 100644 example/1d-linear-convection/weno3/julia/02a/python/residual.py create mode 100644 example/1d-linear-convection/weno3/julia/02a/python/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/julia/02a/python/solution.py create mode 100644 example/1d-linear-convection/weno3/julia/02a/python/solver.py create mode 100644 example/1d-linear-convection/weno3/julia/02a/python/time_integration.py create mode 100644 example/1d-linear-convection/weno3/julia/02b/boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/02b/config.jl create mode 100644 example/1d-linear-convection/weno3/julia/02b/domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/02b/flux.jl create mode 100644 example/1d-linear-convection/weno3/julia/02b/initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/02b/mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/02b/plotter.jl create mode 100644 example/1d-linear-convection/weno3/julia/02b/reconstructor/eno.jl create mode 100644 example/1d-linear-convection/weno3/julia/02b/reconstructor/weno3.jl create mode 100644 example/1d-linear-convection/weno3/julia/02b/residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/02b/run_eno_weno.jl create mode 100644 example/1d-linear-convection/weno3/julia/02b/solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/02b/solver.jl create mode 100644 example/1d-linear-convection/weno3/julia/02b/time_integration.jl create mode 100644 example/1d-linear-convection/weno3/julia/02c/boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/02c/config.jl create mode 100644 example/1d-linear-convection/weno3/julia/02c/domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/02c/flux.jl create mode 100644 example/1d-linear-convection/weno3/julia/02c/initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/02c/mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/02c/plotter.jl create mode 100644 example/1d-linear-convection/weno3/julia/02c/reconstructor/eno.jl create mode 100644 example/1d-linear-convection/weno3/julia/02c/reconstructor/weno3.jl create mode 100644 example/1d-linear-convection/weno3/julia/02c/residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/02c/run_eno_weno.jl create mode 100644 example/1d-linear-convection/weno3/julia/02c/solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/02c/solver.jl create mode 100644 example/1d-linear-convection/weno3/julia/02c/time_integration.jl create mode 100644 example/1d-linear-convection/weno3/julia/02d/boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/02d/config.jl create mode 100644 example/1d-linear-convection/weno3/julia/02d/domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/02d/flux.jl create mode 100644 example/1d-linear-convection/weno3/julia/02d/initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/02d/mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/02d/plotter.jl create mode 100644 example/1d-linear-convection/weno3/julia/02d/reconstructor/eno.jl create mode 100644 example/1d-linear-convection/weno3/julia/02d/reconstructor/weno3.jl create mode 100644 example/1d-linear-convection/weno3/julia/02d/residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/02d/run_eno_weno.jl create mode 100644 example/1d-linear-convection/weno3/julia/02d/solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/02d/solver.jl create mode 100644 example/1d-linear-convection/weno3/julia/02d/time_integration.jl create mode 100644 example/1d-linear-convection/weno3/julia/02e/boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/02e/config.jl create mode 100644 example/1d-linear-convection/weno3/julia/02e/domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/02e/flux.jl create mode 100644 example/1d-linear-convection/weno3/julia/02e/initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/02e/mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/02e/plotter.jl create mode 100644 example/1d-linear-convection/weno3/julia/02e/reconstructor/eno.jl create mode 100644 example/1d-linear-convection/weno3/julia/02e/reconstructor/weno3.jl create mode 100644 example/1d-linear-convection/weno3/julia/02e/residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/02e/run_eno_weno.jl create mode 100644 example/1d-linear-convection/weno3/julia/02e/solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/02e/solver.jl create mode 100644 example/1d-linear-convection/weno3/julia/02e/time_integration.jl create mode 100644 example/1d-linear-convection/weno3/julia/02f/boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/02f/config.jl create mode 100644 example/1d-linear-convection/weno3/julia/02f/domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/02f/eno_weno_comparison.png create mode 100644 example/1d-linear-convection/weno3/julia/02f/flux.jl create mode 100644 example/1d-linear-convection/weno3/julia/02f/initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/02f/mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/02f/plotter.jl create mode 100644 example/1d-linear-convection/weno3/julia/02f/reconstructor/eno.jl create mode 100644 example/1d-linear-convection/weno3/julia/02f/reconstructor/factory.jl create mode 100644 example/1d-linear-convection/weno3/julia/02f/reconstructor/weno3.jl create mode 100644 example/1d-linear-convection/weno3/julia/02f/residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/02f/run_eno_weno.jl create mode 100644 example/1d-linear-convection/weno3/julia/02f/solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/02f/solver.jl create mode 100644 example/1d-linear-convection/weno3/julia/02f/time_integration.jl create mode 100644 example/1d-linear-convection/weno3/julia/02g/boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/02g/config.jl create mode 100644 example/1d-linear-convection/weno3/julia/02g/domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/02g/flux.jl create mode 100644 example/1d-linear-convection/weno3/julia/02g/initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/02g/mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/02g/plotter.jl create mode 100644 example/1d-linear-convection/weno3/julia/02g/reconstructor/eno.jl create mode 100644 example/1d-linear-convection/weno3/julia/02g/reconstructor/factory.jl create mode 100644 example/1d-linear-convection/weno3/julia/02g/reconstructor/weno3.jl create mode 100644 example/1d-linear-convection/weno3/julia/02g/residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/02g/run_eno_weno.jl create mode 100644 example/1d-linear-convection/weno3/julia/02g/solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/02g/solver.jl create mode 100644 example/1d-linear-convection/weno3/julia/02g/time_integration.jl create mode 100644 example/1d-linear-convection/weno3/julia/03/examples/run_eno_weno.jl create mode 100644 example/1d-linear-convection/weno3/julia/03/src/boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/03/src/config.jl create mode 100644 example/1d-linear-convection/weno3/julia/03/src/domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/03/src/flux.jl create mode 100644 example/1d-linear-convection/weno3/julia/03/src/initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/03/src/mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/03/src/plotter.jl create mode 100644 example/1d-linear-convection/weno3/julia/03/src/reconstructor/eno.jl create mode 100644 example/1d-linear-convection/weno3/julia/03/src/reconstructor/factory.jl create mode 100644 example/1d-linear-convection/weno3/julia/03/src/reconstructor/weno3.jl create mode 100644 example/1d-linear-convection/weno3/julia/03/src/registry.jl create mode 100644 example/1d-linear-convection/weno3/julia/03/src/residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/03/src/solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/03/src/solver.jl create mode 100644 example/1d-linear-convection/weno3/julia/03/src/time_integration.jl create mode 100644 example/1d-linear-convection/weno3/julia/03a/examples/run_eno_weno.jl create mode 100644 example/1d-linear-convection/weno3/julia/03a/src/boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/03a/src/config.jl create mode 100644 example/1d-linear-convection/weno3/julia/03a/src/domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/03a/src/flux.jl create mode 100644 example/1d-linear-convection/weno3/julia/03a/src/initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/03a/src/mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/03a/src/plotter.jl create mode 100644 example/1d-linear-convection/weno3/julia/03a/src/reconstructor/eno.jl create mode 100644 example/1d-linear-convection/weno3/julia/03a/src/reconstructor/factory.jl create mode 100644 example/1d-linear-convection/weno3/julia/03a/src/reconstructor/weno3.jl create mode 100644 example/1d-linear-convection/weno3/julia/03a/src/registry.jl create mode 100644 example/1d-linear-convection/weno3/julia/03a/src/residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/03a/src/solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/03a/src/solver.jl create mode 100644 example/1d-linear-convection/weno3/julia/03a/src/time_integration.jl create mode 100644 example/1d-linear-convection/weno3/julia/03a/src/utils.jl create mode 100644 example/1d-linear-convection/weno3/julia/03b/examples/run_eno_weno.jl create mode 100644 example/1d-linear-convection/weno3/julia/03b/src/boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/03b/src/config.jl create mode 100644 example/1d-linear-convection/weno3/julia/03b/src/domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/03b/src/flux/base.jl create mode 100644 example/1d-linear-convection/weno3/julia/03b/src/flux/engquist_osher.jl create mode 100644 example/1d-linear-convection/weno3/julia/03b/src/flux/factory.jl create mode 100644 example/1d-linear-convection/weno3/julia/03b/src/flux/flux.jl create mode 100644 example/1d-linear-convection/weno3/julia/03b/src/flux/rusanov.jl create mode 100644 example/1d-linear-convection/weno3/julia/03b/src/initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/03b/src/mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/03b/src/plotter.jl create mode 100644 example/1d-linear-convection/weno3/julia/03b/src/reconstructor/eno.jl create mode 100644 example/1d-linear-convection/weno3/julia/03b/src/reconstructor/factory.jl create mode 100644 example/1d-linear-convection/weno3/julia/03b/src/reconstructor/weno3.jl create mode 100644 example/1d-linear-convection/weno3/julia/03b/src/registry.jl create mode 100644 example/1d-linear-convection/weno3/julia/03b/src/residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/03b/src/solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/03b/src/solver.jl create mode 100644 example/1d-linear-convection/weno3/julia/03b/src/time_integration.jl create mode 100644 example/1d-linear-convection/weno3/julia/03b/src/utils.jl create mode 100644 example/1d-linear-convection/weno3/julia/03c/examples/run_eno_weno.jl create mode 100644 example/1d-linear-convection/weno3/julia/03c/src/boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/03c/src/config.jl create mode 100644 example/1d-linear-convection/weno3/julia/03c/src/domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/03c/src/flux/base.jl create mode 100644 example/1d-linear-convection/weno3/julia/03c/src/flux/engquist_osher.jl create mode 100644 example/1d-linear-convection/weno3/julia/03c/src/flux/factory.jl create mode 100644 example/1d-linear-convection/weno3/julia/03c/src/flux/flux.jl create mode 100644 example/1d-linear-convection/weno3/julia/03c/src/flux/rusanov.jl create mode 100644 example/1d-linear-convection/weno3/julia/03c/src/initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/03c/src/mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/03c/src/plotter.jl create mode 100644 example/1d-linear-convection/weno3/julia/03c/src/reconstructor/eno.jl create mode 100644 example/1d-linear-convection/weno3/julia/03c/src/reconstructor/factory.jl create mode 100644 example/1d-linear-convection/weno3/julia/03c/src/reconstructor/weno3.jl create mode 100644 example/1d-linear-convection/weno3/julia/03c/src/registry.jl create mode 100644 example/1d-linear-convection/weno3/julia/03c/src/residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/03c/src/solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/03c/src/solver.jl create mode 100644 example/1d-linear-convection/weno3/julia/03c/src/time_integration/base.jl create mode 100644 example/1d-linear-convection/weno3/julia/03c/src/time_integration/factory.jl create mode 100644 example/1d-linear-convection/weno3/julia/03c/src/time_integration/rk1.jl create mode 100644 example/1d-linear-convection/weno3/julia/03c/src/time_integration/rk2.jl create mode 100644 example/1d-linear-convection/weno3/julia/03c/src/time_integration/rk3.jl create mode 100644 example/1d-linear-convection/weno3/julia/03c/src/time_integration/time_integration.jl create mode 100644 example/1d-linear-convection/weno3/julia/03c/src/utils.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/examples/run_eno_weno.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/config.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/equations/base.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/equations/equations.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/equations/linear_advection.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/flux/base.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/flux/engquist_osher.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/flux/factory.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/flux/flux.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/flux/rusanov.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/plotter.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/problems/base.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/problems/linear_advection.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/problems/problems.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/reconstructor/eno.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/reconstructor/factory.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/reconstructor/weno3.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/registry.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/solver.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/time_integration/base.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/time_integration/factory.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/time_integration/rk1.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/time_integration/rk2.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/time_integration/rk3.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/time_integration/time_integration.jl create mode 100644 example/1d-linear-convection/weno3/julia/03d/src/utils.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/examples/run_eno_weno.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/config.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/equations/base.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/equations/equations.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/equations/linear_advection.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/flux/base.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/flux/engquist_osher.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/flux/factory.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/flux/flux.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/flux/rusanov.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/plotter.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/problems/base.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/problems/linear_advection.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/problems/problems.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/reconstructor/eno.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/reconstructor/factory.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/reconstructor/weno3.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/reconstructor/weno5.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/registry.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/solver.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/time_integration/base.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/time_integration/factory.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/time_integration/rk1.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/time_integration/rk2.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/time_integration/rk3.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/time_integration/time_integration.jl create mode 100644 example/1d-linear-convection/weno3/julia/03f/src/utils.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/examples/run_eno_weno.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/boundary.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/config.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/domain.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/equations/base.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/equations/equations.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/equations/linear_advection.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/flux/base.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/flux/engquist_osher.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/flux/factory.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/flux/flux.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/flux/rusanov.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/initial_condition.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/mesh.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/plotter.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/problems/base.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/problems/linear_advection.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/problems/problems.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/reconstructor/eno.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/reconstructor/factory.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/reconstructor/weno3.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/reconstructor/weno5.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/registry.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/residual.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/solution.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/solver.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/time_integration/base.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/time_integration/factory.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/time_integration/rk1.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/time_integration/rk2.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/time_integration/rk3.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/time_integration/time_integration.jl create mode 100644 example/1d-linear-convection/weno3/julia/03g/src/utils.jl create mode 100644 example/1d-linear-convection/weno3/python-v1/01/boundary.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01/cfd_registry.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01/config.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01/domain.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01/factories/base_factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01/flux/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01/flux/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01/flux/engquist_osher.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01/flux/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01/flux/rusanov.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01/mesh.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01/plotter.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01/problem.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01/problems/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01/registry.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01/residual.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01/result.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01/solution.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01/solver.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01/time_integration.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01a/boundary.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01a/cfd_registry.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01a/config.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01a/domain.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01a/factories/base_factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01a/flux/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01a/flux/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01a/flux/engquist_osher.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01a/flux/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01a/flux/rusanov.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01a/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01a/mesh.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01a/plotter.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01a/problem.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01a/problems/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01a/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01a/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01a/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01a/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01a/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01a/registry.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01a/residual.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01a/result.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01a/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01a/solution.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01a/solver.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01a/time_integration.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01b/boundary.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01b/cfd_registry.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01b/config.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01b/domain.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01b/factories/base_factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01b/flux/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01b/flux/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01b/flux/engquist_osher.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01b/flux/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01b/flux/rusanov.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01b/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01b/mesh.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01b/plotter.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01b/problem.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01b/problems/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01b/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01b/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01b/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01b/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01b/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01b/registry.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01b/residual.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01b/result.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01b/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01b/solution.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01b/solver.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01b/time_integration.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/boundary.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/cfd_registry.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/config.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/domain.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/factories/base_factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/flux/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/flux/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/flux/engquist_osher.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/flux/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/flux/rusanov.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/mesh.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/plotter.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/problem.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/problems/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/registry.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/residual.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/result.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/solution.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/solver.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/time_integration/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/time_integration/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/time_integration/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/time_integration/rk1.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/time_integration/rk2.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01c/time_integration/rk3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/boundary.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/config.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/core/registry.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/domain.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/flux/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/flux/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/flux/engquist_osher.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/flux/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/flux/rusanov.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/mesh.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/plotter.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/problem.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/problems/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/residual.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/result.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/solution.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/solver.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/time_integration/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/time_integration/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/time_integration/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/time_integration/rk1.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/time_integration/rk2.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01d/time_integration/rk3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/boundary.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/config.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/core/registry.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/domain.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/flux/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/flux/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/flux/engquist_osher.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/flux/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/flux/rusanov.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/mesh.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/plotter.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/problem.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/problems/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/residual.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/result.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/solution.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/solver.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/time_integration/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/time_integration/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/time_integration/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/time_integration/rk1.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/time_integration/rk2.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01e/time_integration/rk3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/boundary.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/config.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/core/registry.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/domain.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/equations/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/equations/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/flux/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/flux/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/flux/engquist_osher.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/flux/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/flux/rusanov.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/mesh.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/plotter.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/problem.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/problems/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/residual.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/result.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/solution.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/solver.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/time_integration/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/time_integration/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/time_integration/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/time_integration/rk1.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/time_integration/rk2.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01f/time_integration/rk3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/boundary.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/config.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/core/registry.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/domain.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/equations/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/equations/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/flux/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/flux/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/flux/engquist_osher.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/flux/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/flux/rusanov.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/mesh.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/plotter.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/problem.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/problems/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/reconstructor/weno5.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/residual.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/result.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/solution.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/solver.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/time_integration/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/time_integration/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/time_integration/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/time_integration/rk1.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/time_integration/rk2.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01g/time_integration/rk3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/boundary.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/config.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/core/registry.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/domain.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/equations/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/equations/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/flux/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/flux/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/flux/engquist_osher.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/flux/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/flux/rusanov.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/mesh.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/plotter.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/problem.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/problems/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/reconstructor/weno5.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/residual.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/result.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/solution.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/solver.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/time_integration/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/time_integration/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/time_integration/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/time_integration/rk1.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/time_integration/rk2.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01h/time_integration/rk3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/boundary.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/config.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/core/registry.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/domain.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/equations/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/equations/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/flux/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/flux/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/flux/engquist_osher.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/flux/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/flux/rusanov.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/mesh.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/plotter.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/problem.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/problems/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/reconstructor/weno5.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/residual.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/result.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/solution.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/solver.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/time_integration/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/time_integration/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/time_integration/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/time_integration/rk1.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/time_integration/rk2.py create mode 100644 example/1d-linear-convection/weno3/python-v1/01i/time_integration/rk3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/example/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/boundary.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/config.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/core/registry.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/domain.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/equations/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/equations/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/flux/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/flux/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/flux/engquist_osher.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/flux/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/flux/rusanov.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/mesh.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/plotter.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/problem.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/problems/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/reconstructor/weno5.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/residual.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/result.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/solution.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/solver.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/time_integration/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/time_integration/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/time_integration/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/time_integration/rk1.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/time_integration/rk2.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02/src/time_integration/rk3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/example/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/boundary.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/config.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/core/registry.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/domain.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/equations/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/equations/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/flux/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/flux/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/flux/engquist_osher.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/flux/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/flux/rusanov.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/mesh.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/plotter.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/problem.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/problems/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/reconstructor/weno5.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/residual.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/result.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/solution.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/solver.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/time_integration/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/time_integration/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/time_integration/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/time_integration/rk1.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/time_integration/rk2.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02a/src/time_integration/rk3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/example/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/boundary.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/config.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/core/registry.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/domain.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/equations/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/equations/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/flux/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/flux/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/flux/engquist_osher.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/flux/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/flux/rusanov.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/initial_condition/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/initial_condition/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/initial_condition/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/initial_condition/gaussian.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/initial_condition/sine.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/initial_condition/step.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/mesh.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/plotter.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/problem.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/problems/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/reconstructor/weno5.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/residual.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/result.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/solution.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/solver.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/time_integration/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/time_integration/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/time_integration/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/time_integration/rk1.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/time_integration/rk2.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02b/src/time_integration/rk3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/example/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/boundary/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/boundary/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/boundary/dirichlet.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/boundary/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/boundary/neumann.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/boundary/periodic.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/config.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/core/registry.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/domain.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/equations/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/equations/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/flux/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/flux/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/flux/engquist_osher.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/flux/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/flux/rusanov.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/initial_condition/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/initial_condition/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/initial_condition/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/initial_condition/gaussian.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/initial_condition/sine.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/initial_condition/step.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/mesh.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/plotter.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/problem.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/problems/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/reconstructor/weno5.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/residual.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/result.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/solution.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/solver.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/time_integration/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/time_integration/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/time_integration/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/time_integration/rk1.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/time_integration/rk2.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02c/src/time_integration/rk3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/example/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/boundary/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/boundary/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/boundary/dirichlet.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/boundary/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/boundary/neumann.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/boundary/periodic.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/config.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/core/registry.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/domain.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/equations/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/equations/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/initial_condition/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/initial_condition/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/initial_condition/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/initial_condition/gaussian.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/initial_condition/sine.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/initial_condition/step.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/mesh.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/numerics/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/numerics/flux/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/numerics/flux/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/numerics/flux/engquist_osher.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/numerics/flux/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/numerics/flux/rusanov.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/numerics/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/numerics/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/numerics/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/numerics/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/numerics/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/numerics/reconstructor/weno5.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/numerics/residual.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/numerics/time_integration/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/numerics/time_integration/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/numerics/time_integration/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/numerics/time_integration/rk1.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/numerics/time_integration/rk2.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/numerics/time_integration/rk3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/plotter.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/problem.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/problems/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/result.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/solution.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02d/src/solver.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/example/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/boundary/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/boundary/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/boundary/dirichlet.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/boundary/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/boundary/neumann.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/boundary/periodic.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/config.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/core/registry.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/domain.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/mesh.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/numerics/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/numerics/flux/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/numerics/flux/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/numerics/flux/engquist_osher.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/numerics/flux/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/numerics/flux/rusanov.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/numerics/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/numerics/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/numerics/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/numerics/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/numerics/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/numerics/reconstructor/weno5.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/numerics/residual.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/numerics/time_integration/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/numerics/time_integration/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/numerics/time_integration/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/numerics/time_integration/rk1.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/numerics/time_integration/rk2.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/numerics/time_integration/rk3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/physics/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/physics/equations/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/physics/equations/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/physics/equations/euler.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/physics/equations/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/physics/initial_conditions/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/physics/initial_conditions/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/physics/initial_conditions/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/physics/initial_conditions/gaussian.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/physics/initial_conditions/sine.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/physics/initial_conditions/step.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/physics/problems/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/physics/problems/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/physics/problems/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/physics/problems/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/physics/systems/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/physics/systems/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/physics/systems/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/physics/systems/linear_advection_system.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/plotter.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/problem.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/result.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/solution.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02e/src/solver.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/example/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/config.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/core/registry.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/boundary/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/boundary/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/boundary/dirichlet.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/boundary/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/boundary/neumann.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/boundary/periodic.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/domain.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/mesh.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/solution.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/numerics/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/numerics/flux/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/numerics/flux/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/numerics/flux/engquist_osher.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/numerics/flux/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/numerics/flux/rusanov.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/numerics/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/numerics/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/numerics/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/numerics/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/numerics/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/numerics/reconstructor/weno5.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/numerics/residual.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/numerics/time_integration/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/numerics/time_integration/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/numerics/time_integration/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/numerics/time_integration/rk1.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/numerics/time_integration/rk2.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/numerics/time_integration/rk3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/physics/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/physics/equations/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/physics/equations/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/physics/equations/euler.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/physics/equations/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/physics/initial_conditions/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/physics/initial_conditions/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/physics/initial_conditions/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/physics/initial_conditions/gaussian.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/physics/initial_conditions/sine.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/physics/initial_conditions/step.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/physics/problems/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/physics/problems/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/physics/problems/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/physics/problems/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/physics/systems/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/physics/systems/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/physics/systems/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/physics/systems/linear_advection_system.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/plotter.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/result.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02f/src/solver.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/example/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/core/registry.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/core/solver.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/boundary/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/boundary/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/boundary/dirichlet.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/boundary/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/boundary/neumann.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/boundary/periodic.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/config.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/domain.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/mesh.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/result.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/solution.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/numerics/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/numerics/flux/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/numerics/flux/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/numerics/flux/engquist_osher.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/numerics/flux/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/numerics/flux/rusanov.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/numerics/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/numerics/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/numerics/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/numerics/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/numerics/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/numerics/reconstructor/weno5.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/numerics/residual.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/numerics/time_integration/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/numerics/time_integration/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/numerics/time_integration/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/numerics/time_integration/rk1.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/numerics/time_integration/rk2.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/numerics/time_integration/rk3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/physics/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/physics/equations/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/physics/equations/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/physics/equations/euler.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/physics/equations/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/physics/initial_conditions/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/physics/initial_conditions/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/physics/initial_conditions/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/physics/initial_conditions/gaussian.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/physics/initial_conditions/sine.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/physics/initial_conditions/step.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/physics/problems/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/physics/problems/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/physics/problems/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/physics/problems/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/physics/systems/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/physics/systems/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/physics/systems/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/physics/systems/linear_advection_system.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02g/src/visualization/plotter.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/example/linear_advection/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/core/registry.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/core/solver.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/boundary/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/boundary/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/boundary/dirichlet.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/boundary/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/boundary/neumann.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/boundary/periodic.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/config.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/domain.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/mesh.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/result.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/solution.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/numerics/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/numerics/flux/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/numerics/flux/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/numerics/flux/engquist_osher.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/numerics/flux/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/numerics/flux/rusanov.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/numerics/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/numerics/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/numerics/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/numerics/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/numerics/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/numerics/reconstructor/weno5.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/numerics/residual.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/numerics/time_integration/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/numerics/time_integration/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/numerics/time_integration/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/numerics/time_integration/rk1.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/numerics/time_integration/rk2.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/numerics/time_integration/rk3.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/physics/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/physics/equations/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/physics/equations/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/physics/equations/euler.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/physics/equations/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/physics/initial_conditions/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/physics/initial_conditions/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/physics/initial_conditions/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/physics/initial_conditions/gaussian.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/physics/initial_conditions/sine.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/physics/initial_conditions/step.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/physics/problems/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/physics/problems/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/physics/problems/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/physics/problems/linear_advection.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/physics/systems/__init__.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/physics/systems/base.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/physics/systems/factory.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/physics/systems/linear_advection_system.py create mode 100644 example/1d-linear-convection/weno3/python-v1/02h/src/visualization/plotter.py create mode 100644 example/1d-linear-convection/weno3/python/05/boundary.py create mode 100644 example/1d-linear-convection/weno3/python/05/config.py create mode 100644 example/1d-linear-convection/weno3/python/05/domain.py create mode 100644 example/1d-linear-convection/weno3/python/05/eno_weno_comparison.png create mode 100644 example/1d-linear-convection/weno3/python/05/flux.py create mode 100644 example/1d-linear-convection/weno3/python/05/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/python/05/mesh.py create mode 100644 example/1d-linear-convection/weno3/python/05/plotter.py create mode 100644 example/1d-linear-convection/weno3/python/05/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python/05/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python/05/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python/05/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python/05/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python/05/residual.py create mode 100644 example/1d-linear-convection/weno3/python/05/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python/05/solution.py create mode 100644 example/1d-linear-convection/weno3/python/05/solver.py create mode 100644 example/1d-linear-convection/weno3/python/05/time_integration.py create mode 100644 example/1d-linear-convection/weno3/python/05a/boundary.py create mode 100644 example/1d-linear-convection/weno3/python/05a/config.py create mode 100644 example/1d-linear-convection/weno3/python/05a/domain.py create mode 100644 example/1d-linear-convection/weno3/python/05a/flux.py create mode 100644 example/1d-linear-convection/weno3/python/05a/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/python/05a/mesh.py create mode 100644 example/1d-linear-convection/weno3/python/05a/plotter.py create mode 100644 example/1d-linear-convection/weno3/python/05a/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python/05a/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python/05a/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python/05a/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python/05a/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python/05a/registry.py create mode 100644 example/1d-linear-convection/weno3/python/05a/residual.py create mode 100644 example/1d-linear-convection/weno3/python/05a/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python/05a/solution.py create mode 100644 example/1d-linear-convection/weno3/python/05a/solver.py create mode 100644 example/1d-linear-convection/weno3/python/05a/time_integration.py create mode 100644 example/1d-linear-convection/weno3/python/05b/boundary.py create mode 100644 example/1d-linear-convection/weno3/python/05b/config.py create mode 100644 example/1d-linear-convection/weno3/python/05b/domain.py create mode 100644 example/1d-linear-convection/weno3/python/05b/flux.py create mode 100644 example/1d-linear-convection/weno3/python/05b/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/python/05b/mesh.py create mode 100644 example/1d-linear-convection/weno3/python/05b/plotter.py create mode 100644 example/1d-linear-convection/weno3/python/05b/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python/05b/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python/05b/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python/05b/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python/05b/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python/05b/registry.py create mode 100644 example/1d-linear-convection/weno3/python/05b/residual.py create mode 100644 example/1d-linear-convection/weno3/python/05b/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python/05b/solution.py create mode 100644 example/1d-linear-convection/weno3/python/05b/solver.py create mode 100644 example/1d-linear-convection/weno3/python/05b/time_integration.py create mode 100644 example/1d-linear-convection/weno3/python/05c/boundary.py create mode 100644 example/1d-linear-convection/weno3/python/05c/config.py create mode 100644 example/1d-linear-convection/weno3/python/05c/domain.py create mode 100644 example/1d-linear-convection/weno3/python/05c/flux.py create mode 100644 example/1d-linear-convection/weno3/python/05c/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/python/05c/mesh.py create mode 100644 example/1d-linear-convection/weno3/python/05c/plotter.py create mode 100644 example/1d-linear-convection/weno3/python/05c/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python/05c/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python/05c/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python/05c/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python/05c/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python/05c/registry.py create mode 100644 example/1d-linear-convection/weno3/python/05c/residual.py create mode 100644 example/1d-linear-convection/weno3/python/05c/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python/05c/solution.py create mode 100644 example/1d-linear-convection/weno3/python/05c/solver.py create mode 100644 example/1d-linear-convection/weno3/python/05c/time_integration.py create mode 100644 example/1d-linear-convection/weno3/python/05d/boundary.py create mode 100644 example/1d-linear-convection/weno3/python/05d/config.py create mode 100644 example/1d-linear-convection/weno3/python/05d/domain.py create mode 100644 example/1d-linear-convection/weno3/python/05d/flux.py create mode 100644 example/1d-linear-convection/weno3/python/05d/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/python/05d/mesh.py create mode 100644 example/1d-linear-convection/weno3/python/05d/plotter.py create mode 100644 example/1d-linear-convection/weno3/python/05d/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python/05d/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python/05d/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python/05d/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python/05d/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python/05d/registry.py create mode 100644 example/1d-linear-convection/weno3/python/05d/residual.py create mode 100644 example/1d-linear-convection/weno3/python/05d/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python/05d/solution.py create mode 100644 example/1d-linear-convection/weno3/python/05d/solver.py create mode 100644 example/1d-linear-convection/weno3/python/05d/time_integration.py create mode 100644 example/1d-linear-convection/weno3/python/05e/boundary.py create mode 100644 example/1d-linear-convection/weno3/python/05e/config.py create mode 100644 example/1d-linear-convection/weno3/python/05e/domain.py create mode 100644 example/1d-linear-convection/weno3/python/05e/flux.py create mode 100644 example/1d-linear-convection/weno3/python/05e/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/python/05e/mesh.py create mode 100644 example/1d-linear-convection/weno3/python/05e/plotter.py create mode 100644 example/1d-linear-convection/weno3/python/05e/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python/05e/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python/05e/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python/05e/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python/05e/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python/05e/registry.py create mode 100644 example/1d-linear-convection/weno3/python/05e/residual.py create mode 100644 example/1d-linear-convection/weno3/python/05e/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python/05e/solution.py create mode 100644 example/1d-linear-convection/weno3/python/05e/solver.py create mode 100644 example/1d-linear-convection/weno3/python/05e/time_integration.py create mode 100644 example/1d-linear-convection/weno3/python/05f/boundary.py create mode 100644 example/1d-linear-convection/weno3/python/05f/config.py create mode 100644 example/1d-linear-convection/weno3/python/05f/domain.py create mode 100644 example/1d-linear-convection/weno3/python/05f/flux.py create mode 100644 example/1d-linear-convection/weno3/python/05f/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/python/05f/mesh.py create mode 100644 example/1d-linear-convection/weno3/python/05f/plotter.py create mode 100644 example/1d-linear-convection/weno3/python/05f/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python/05f/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python/05f/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python/05f/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python/05f/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python/05f/registry.py create mode 100644 example/1d-linear-convection/weno3/python/05f/residual.py create mode 100644 example/1d-linear-convection/weno3/python/05f/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python/05f/solution.py create mode 100644 example/1d-linear-convection/weno3/python/05f/solver.py create mode 100644 example/1d-linear-convection/weno3/python/05f/test_all_boundaries.py create mode 100644 example/1d-linear-convection/weno3/python/05f/test_registry_complete.py create mode 100644 example/1d-linear-convection/weno3/python/05f/time_integration.py create mode 100644 example/1d-linear-convection/weno3/python/06/boundary.py create mode 100644 example/1d-linear-convection/weno3/python/06/cfd_registry.py create mode 100644 example/1d-linear-convection/weno3/python/06/config.py create mode 100644 example/1d-linear-convection/weno3/python/06/domain.py create mode 100644 example/1d-linear-convection/weno3/python/06/factories/base_factory.py create mode 100644 example/1d-linear-convection/weno3/python/06/flux.py create mode 100644 example/1d-linear-convection/weno3/python/06/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/python/06/mesh.py create mode 100644 example/1d-linear-convection/weno3/python/06/plotter.py create mode 100644 example/1d-linear-convection/weno3/python/06/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python/06/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python/06/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python/06/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python/06/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python/06/registry.py create mode 100644 example/1d-linear-convection/weno3/python/06/residual.py create mode 100644 example/1d-linear-convection/weno3/python/06/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python/06/solution.py create mode 100644 example/1d-linear-convection/weno3/python/06/solver.py create mode 100644 example/1d-linear-convection/weno3/python/06/test_simplified_imports.py create mode 100644 example/1d-linear-convection/weno3/python/06/time_integration.py create mode 100644 example/1d-linear-convection/weno3/python/06a/Project.toml create mode 100644 example/1d-linear-convection/weno3/python/06a/boundary.py create mode 100644 example/1d-linear-convection/weno3/python/06a/cfd_registry.py create mode 100644 example/1d-linear-convection/weno3/python/06a/config.py create mode 100644 example/1d-linear-convection/weno3/python/06a/domain.py create mode 100644 example/1d-linear-convection/weno3/python/06a/factories/base_factory.py create mode 100644 example/1d-linear-convection/weno3/python/06a/flux.py create mode 100644 example/1d-linear-convection/weno3/python/06a/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/python/06a/mesh.py create mode 100644 example/1d-linear-convection/weno3/python/06a/plotter.py create mode 100644 example/1d-linear-convection/weno3/python/06a/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python/06a/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python/06a/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python/06a/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python/06a/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python/06a/registry.py create mode 100644 example/1d-linear-convection/weno3/python/06a/residual.py create mode 100644 example/1d-linear-convection/weno3/python/06a/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python/06a/solution.py create mode 100644 example/1d-linear-convection/weno3/python/06a/solver.py create mode 100644 example/1d-linear-convection/weno3/python/06a/time_integration.py create mode 100644 example/1d-linear-convection/weno3/python/06b/boundary.py create mode 100644 example/1d-linear-convection/weno3/python/06b/cfd_registry.py create mode 100644 example/1d-linear-convection/weno3/python/06b/config.py create mode 100644 example/1d-linear-convection/weno3/python/06b/domain.py create mode 100644 example/1d-linear-convection/weno3/python/06b/factories/base_factory.py create mode 100644 example/1d-linear-convection/weno3/python/06b/flux/__init__.py create mode 100644 example/1d-linear-convection/weno3/python/06b/flux/base.py create mode 100644 example/1d-linear-convection/weno3/python/06b/flux/engquist_osher.py create mode 100644 example/1d-linear-convection/weno3/python/06b/flux/factory.py create mode 100644 example/1d-linear-convection/weno3/python/06b/flux/rusanov.py create mode 100644 example/1d-linear-convection/weno3/python/06b/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/python/06b/mesh.py create mode 100644 example/1d-linear-convection/weno3/python/06b/plotter.py create mode 100644 example/1d-linear-convection/weno3/python/06b/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python/06b/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python/06b/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python/06b/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python/06b/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python/06b/registry.py create mode 100644 example/1d-linear-convection/weno3/python/06b/residual.py create mode 100644 example/1d-linear-convection/weno3/python/06b/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python/06b/solution.py create mode 100644 example/1d-linear-convection/weno3/python/06b/solver.py create mode 100644 example/1d-linear-convection/weno3/python/06b/time_integration.py create mode 100644 example/1d-linear-convection/weno3/python/06c/boundary.py create mode 100644 example/1d-linear-convection/weno3/python/06c/cfd_registry.py create mode 100644 example/1d-linear-convection/weno3/python/06c/config.py create mode 100644 example/1d-linear-convection/weno3/python/06c/domain.py create mode 100644 example/1d-linear-convection/weno3/python/06c/factories/base_factory.py create mode 100644 example/1d-linear-convection/weno3/python/06c/flux/__init__.py create mode 100644 example/1d-linear-convection/weno3/python/06c/flux/base.py create mode 100644 example/1d-linear-convection/weno3/python/06c/flux/engquist_osher.py create mode 100644 example/1d-linear-convection/weno3/python/06c/flux/factory.py create mode 100644 example/1d-linear-convection/weno3/python/06c/flux/rusanov.py create mode 100644 example/1d-linear-convection/weno3/python/06c/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/python/06c/mesh.py create mode 100644 example/1d-linear-convection/weno3/python/06c/plotter.py create mode 100644 example/1d-linear-convection/weno3/python/06c/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python/06c/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python/06c/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python/06c/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python/06c/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python/06c/registry.py create mode 100644 example/1d-linear-convection/weno3/python/06c/residual.py create mode 100644 example/1d-linear-convection/weno3/python/06c/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python/06c/solution.py create mode 100644 example/1d-linear-convection/weno3/python/06c/solver.py create mode 100644 example/1d-linear-convection/weno3/python/06c/time_integration.py create mode 100644 example/1d-linear-convection/weno3/python/06d/boundary.py create mode 100644 example/1d-linear-convection/weno3/python/06d/cfd_registry.py create mode 100644 example/1d-linear-convection/weno3/python/06d/config.py create mode 100644 example/1d-linear-convection/weno3/python/06d/domain.py create mode 100644 example/1d-linear-convection/weno3/python/06d/factories/base_factory.py create mode 100644 example/1d-linear-convection/weno3/python/06d/flux/__init__.py create mode 100644 example/1d-linear-convection/weno3/python/06d/flux/base.py create mode 100644 example/1d-linear-convection/weno3/python/06d/flux/engquist_osher.py create mode 100644 example/1d-linear-convection/weno3/python/06d/flux/factory.py create mode 100644 example/1d-linear-convection/weno3/python/06d/flux/rusanov.py create mode 100644 example/1d-linear-convection/weno3/python/06d/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/python/06d/mesh.py create mode 100644 example/1d-linear-convection/weno3/python/06d/plotter.py create mode 100644 example/1d-linear-convection/weno3/python/06d/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python/06d/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python/06d/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python/06d/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python/06d/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python/06d/registry.py create mode 100644 example/1d-linear-convection/weno3/python/06d/residual.py create mode 100644 example/1d-linear-convection/weno3/python/06d/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python/06d/solution.py create mode 100644 example/1d-linear-convection/weno3/python/06d/solver.py create mode 100644 example/1d-linear-convection/weno3/python/06d/time_integration.py create mode 100644 example/1d-linear-convection/weno3/python/06e/boundary.py create mode 100644 example/1d-linear-convection/weno3/python/06e/cfd_registry.py create mode 100644 example/1d-linear-convection/weno3/python/06e/config.py create mode 100644 example/1d-linear-convection/weno3/python/06e/domain.py create mode 100644 example/1d-linear-convection/weno3/python/06e/factories/base_factory.py create mode 100644 example/1d-linear-convection/weno3/python/06e/flux/__init__.py create mode 100644 example/1d-linear-convection/weno3/python/06e/flux/base.py create mode 100644 example/1d-linear-convection/weno3/python/06e/flux/engquist_osher.py create mode 100644 example/1d-linear-convection/weno3/python/06e/flux/factory.py create mode 100644 example/1d-linear-convection/weno3/python/06e/flux/rusanov.py create mode 100644 example/1d-linear-convection/weno3/python/06e/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/python/06e/mesh.py create mode 100644 example/1d-linear-convection/weno3/python/06e/plotter.py create mode 100644 example/1d-linear-convection/weno3/python/06e/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python/06e/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python/06e/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python/06e/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python/06e/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python/06e/registry.py create mode 100644 example/1d-linear-convection/weno3/python/06e/residual.py create mode 100644 example/1d-linear-convection/weno3/python/06e/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python/06e/solution.py create mode 100644 example/1d-linear-convection/weno3/python/06e/solver.py create mode 100644 example/1d-linear-convection/weno3/python/06e/time_integration.py create mode 100644 example/1d-linear-convection/weno3/python/06f/boundary.py create mode 100644 example/1d-linear-convection/weno3/python/06f/cfd_registry.py create mode 100644 example/1d-linear-convection/weno3/python/06f/config.py create mode 100644 example/1d-linear-convection/weno3/python/06f/domain.py create mode 100644 example/1d-linear-convection/weno3/python/06f/factories/base_factory.py create mode 100644 example/1d-linear-convection/weno3/python/06f/flux/__init__.py create mode 100644 example/1d-linear-convection/weno3/python/06f/flux/base.py create mode 100644 example/1d-linear-convection/weno3/python/06f/flux/engquist_osher.py create mode 100644 example/1d-linear-convection/weno3/python/06f/flux/factory.py create mode 100644 example/1d-linear-convection/weno3/python/06f/flux/rusanov.py create mode 100644 example/1d-linear-convection/weno3/python/06f/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/python/06f/mesh.py create mode 100644 example/1d-linear-convection/weno3/python/06f/plotter.py create mode 100644 example/1d-linear-convection/weno3/python/06f/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python/06f/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python/06f/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python/06f/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python/06f/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python/06f/registry.py create mode 100644 example/1d-linear-convection/weno3/python/06f/residual.py create mode 100644 example/1d-linear-convection/weno3/python/06f/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python/06f/solution.py create mode 100644 example/1d-linear-convection/weno3/python/06f/solver.py create mode 100644 example/1d-linear-convection/weno3/python/06f/time_integration.py create mode 100644 example/1d-linear-convection/weno3/python/06g/boundary.py create mode 100644 example/1d-linear-convection/weno3/python/06g/cfd_registry.py create mode 100644 example/1d-linear-convection/weno3/python/06g/config.py create mode 100644 example/1d-linear-convection/weno3/python/06g/domain.py create mode 100644 example/1d-linear-convection/weno3/python/06g/factories/base_factory.py create mode 100644 example/1d-linear-convection/weno3/python/06g/flux/__init__.py create mode 100644 example/1d-linear-convection/weno3/python/06g/flux/base.py create mode 100644 example/1d-linear-convection/weno3/python/06g/flux/engquist_osher.py create mode 100644 example/1d-linear-convection/weno3/python/06g/flux/factory.py create mode 100644 example/1d-linear-convection/weno3/python/06g/flux/rusanov.py create mode 100644 example/1d-linear-convection/weno3/python/06g/initial_condition.py create mode 100644 example/1d-linear-convection/weno3/python/06g/mesh.py create mode 100644 example/1d-linear-convection/weno3/python/06g/plotter.py create mode 100644 example/1d-linear-convection/weno3/python/06g/reconstructor/__init__.py create mode 100644 example/1d-linear-convection/weno3/python/06g/reconstructor/base.py create mode 100644 example/1d-linear-convection/weno3/python/06g/reconstructor/eno.py create mode 100644 example/1d-linear-convection/weno3/python/06g/reconstructor/factory.py create mode 100644 example/1d-linear-convection/weno3/python/06g/reconstructor/weno3.py create mode 100644 example/1d-linear-convection/weno3/python/06g/registry.py create mode 100644 example/1d-linear-convection/weno3/python/06g/residual.py create mode 100644 example/1d-linear-convection/weno3/python/06g/result.py create mode 100644 example/1d-linear-convection/weno3/python/06g/run_eno_weno.py create mode 100644 example/1d-linear-convection/weno3/python/06g/solution.py create mode 100644 example/1d-linear-convection/weno3/python/06g/solver.py create mode 100644 example/1d-linear-convection/weno3/python/06g/time_integration.py create mode 100644 example/codegen/weno_reconstructor/01/eno_weno_coefficients.yaml create mode 100644 example/codegen/weno_reconstructor/01/generate_weno_coef.py create mode 100644 example/codegen/weno_reconstructor/01/weno_reconstructors.py diff --git a/README.md b/README.md index 44ffaba35..080aba484 100644 --- a/README.md +++ b/README.md @@ -65,4 +65,4 @@ Email: If you have any problem in building or running the code, please do not hesitate to contact. -Copyright 2017-2025, He Xin, and the OneFLOW contributors. +Copyright 2017-2026, He Xin, and the OneFLOW contributors. diff --git a/README_zh_CN.md b/README_zh_CN.md index 77a9115ab..281d4839e 100644 --- a/README_zh_CN.md +++ b/README_zh_CN.md @@ -106,4 +106,4 @@ The current OneFLOW release has been coordinated by the OneFLOW International De 如果在编译和运行代码中遇到问题,可随时通过邮件联系。 -Copyright 2017-2025, He Xin, and the OneFLOW contributors. +Copyright 2017-2026, He Xin, and the OneFLOW contributors. diff --git a/codes/adt/include/AdtTree.h b/codes/adt/include/AdtTree.h index 16167ba9d..fe480d0d8 100644 --- a/codes/adt/include/AdtTree.h +++ b/codes/adt/include/AdtTree.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/array/include/HXArray.h b/codes/array/include/HXArray.h index c4706dceb..328f2dcce 100644 --- a/codes/array/include/HXArray.h +++ b/codes/array/include/HXArray.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/array/include/Marray.h b/codes/array/include/Marray.h index c47b72e22..a64150e0d 100644 --- a/codes/array/include/Marray.h +++ b/codes/array/include/Marray.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/array/src/Marray.cpp b/codes/array/src/Marray.cpp index 48a6b430d..a3ebd5170 100644 --- a/codes/array/src/Marray.cpp +++ b/codes/array/src/Marray.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/basic/include/Constant.h b/codes/basic/include/Constant.h index 7c5d11ab3..5ace34889 100644 --- a/codes/basic/include/Constant.h +++ b/codes/basic/include/Constant.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/basic/include/HXDefine.h b/codes/basic/include/HXDefine.h index e1058aab8..0bf9e8dc1 100644 --- a/codes/basic/include/HXDefine.h +++ b/codes/basic/include/HXDefine.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/basic/include/HXMid.h b/codes/basic/include/HXMid.h index 0efa7704b..a69ac2cc8 100644 --- a/codes/basic/include/HXMid.h +++ b/codes/basic/include/HXMid.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/basic/include/HXPointer.h b/codes/basic/include/HXPointer.h index b54f97c89..7ff81f4ff 100644 --- a/codes/basic/include/HXPointer.h +++ b/codes/basic/include/HXPointer.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/basic/include/HXSort.h b/codes/basic/include/HXSort.h index ddf25e4df..521180373 100644 --- a/codes/basic/include/HXSort.h +++ b/codes/basic/include/HXSort.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/basic/include/HXStd.h b/codes/basic/include/HXStd.h index 1d15c062d..7db88eed3 100644 --- a/codes/basic/include/HXStd.h +++ b/codes/basic/include/HXStd.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/basic/include/HXType.h b/codes/basic/include/HXType.h index a73cfcc3c..d41292760 100644 --- a/codes/basic/include/HXType.h +++ b/codes/basic/include/HXType.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/basic/include/HXTypeBasic.h b/codes/basic/include/HXTypeBasic.h index 9523e904f..10c8e6e84 100644 --- a/codes/basic/include/HXTypeBasic.h +++ b/codes/basic/include/HXTypeBasic.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/basic/include/HXVector.h b/codes/basic/include/HXVector.h index dad9f8f12..f9243108e 100644 --- a/codes/basic/include/HXVector.h +++ b/codes/basic/include/HXVector.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/basic/src/HXDefine.cpp b/codes/basic/src/HXDefine.cpp index 138e934eb..bee347c63 100644 --- a/codes/basic/src/HXDefine.cpp +++ b/codes/basic/src/HXDefine.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/basic/src/HXMid.cpp b/codes/basic/src/HXMid.cpp index ca9761b85..92a5518dc 100644 --- a/codes/basic/src/HXMid.cpp +++ b/codes/basic/src/HXMid.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/include/CgnsBase.h b/codes/cgns/include/CgnsBase.h index 107e2c142..0a70bb599 100644 --- a/codes/cgns/include/CgnsBase.h +++ b/codes/cgns/include/CgnsBase.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/include/CgnsBaseUtil.h b/codes/cgns/include/CgnsBaseUtil.h index e2a5f6c85..a9f680839 100644 --- a/codes/cgns/include/CgnsBaseUtil.h +++ b/codes/cgns/include/CgnsBaseUtil.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/include/CgnsBc1to1.h b/codes/cgns/include/CgnsBc1to1.h index 8702bd864..c455a5d5e 100644 --- a/codes/cgns/include/CgnsBc1to1.h +++ b/codes/cgns/include/CgnsBc1to1.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/include/CgnsBcBoco.h b/codes/cgns/include/CgnsBcBoco.h index e38b293ac..ab2a58f75 100644 --- a/codes/cgns/include/CgnsBcBoco.h +++ b/codes/cgns/include/CgnsBcBoco.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/include/CgnsBcConn.h b/codes/cgns/include/CgnsBcConn.h index 8ce5048c5..9b3e45ea9 100644 --- a/codes/cgns/include/CgnsBcConn.h +++ b/codes/cgns/include/CgnsBcConn.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/include/CgnsBcLink.h b/codes/cgns/include/CgnsBcLink.h index f88384fe0..d0b973c00 100644 --- a/codes/cgns/include/CgnsBcLink.h +++ b/codes/cgns/include/CgnsBcLink.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/include/CgnsCoor.h b/codes/cgns/include/CgnsCoor.h index f440858ca..bf3d2730a 100644 --- a/codes/cgns/include/CgnsCoor.h +++ b/codes/cgns/include/CgnsCoor.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/include/CgnsFactory.h b/codes/cgns/include/CgnsFactory.h index 420238243..b4ceac99b 100644 --- a/codes/cgns/include/CgnsFactory.h +++ b/codes/cgns/include/CgnsFactory.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/include/CgnsFamilyBc.h b/codes/cgns/include/CgnsFamilyBc.h index 4b9b267dc..0bc845ef7 100644 --- a/codes/cgns/include/CgnsFamilyBc.h +++ b/codes/cgns/include/CgnsFamilyBc.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/include/CgnsFile.h b/codes/cgns/include/CgnsFile.h index 77a425c72..adba145ff 100644 --- a/codes/cgns/include/CgnsFile.h +++ b/codes/cgns/include/CgnsFile.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/include/CgnsGlobal.h b/codes/cgns/include/CgnsGlobal.h index 9ec54e4f3..206eafe16 100644 --- a/codes/cgns/include/CgnsGlobal.h +++ b/codes/cgns/include/CgnsGlobal.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/include/CgnsPeriod.h b/codes/cgns/include/CgnsPeriod.h index 2bdf89fb0..ffed4f775 100644 --- a/codes/cgns/include/CgnsPeriod.h +++ b/codes/cgns/include/CgnsPeriod.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/include/CgnsSection.h b/codes/cgns/include/CgnsSection.h index bc9c43522..39df28c0b 100644 --- a/codes/cgns/include/CgnsSection.h +++ b/codes/cgns/include/CgnsSection.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/include/CgnsVariable.h b/codes/cgns/include/CgnsVariable.h index 6ffee78e4..653696345 100644 --- a/codes/cgns/include/CgnsVariable.h +++ b/codes/cgns/include/CgnsVariable.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/include/CgnsZbase.h b/codes/cgns/include/CgnsZbase.h index e283e31bb..bbc122694 100644 --- a/codes/cgns/include/CgnsZbase.h +++ b/codes/cgns/include/CgnsZbase.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/include/CgnsZbaseUtil.h b/codes/cgns/include/CgnsZbaseUtil.h index 0355e8800..9cf02037a 100644 --- a/codes/cgns/include/CgnsZbaseUtil.h +++ b/codes/cgns/include/CgnsZbaseUtil.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/include/CgnsZbc.h b/codes/cgns/include/CgnsZbc.h index 880604b6f..b2508fb0f 100644 --- a/codes/cgns/include/CgnsZbc.h +++ b/codes/cgns/include/CgnsZbc.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/include/CgnsZbc1to1.h b/codes/cgns/include/CgnsZbc1to1.h index e13319893..c2502991b 100644 --- a/codes/cgns/include/CgnsZbc1to1.h +++ b/codes/cgns/include/CgnsZbc1to1.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/include/CgnsZbcBoco.h b/codes/cgns/include/CgnsZbcBoco.h index 30ec9c40f..eafa4c8cc 100644 --- a/codes/cgns/include/CgnsZbcBoco.h +++ b/codes/cgns/include/CgnsZbcBoco.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/include/CgnsZbcConn.h b/codes/cgns/include/CgnsZbcConn.h index 7dd9e8a28..7134d5630 100644 --- a/codes/cgns/include/CgnsZbcConn.h +++ b/codes/cgns/include/CgnsZbcConn.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/include/CgnsZone.h b/codes/cgns/include/CgnsZone.h index f658aeaae..8b6ff4a83 100644 --- a/codes/cgns/include/CgnsZone.h +++ b/codes/cgns/include/CgnsZone.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/include/CgnsZoneUtil.h b/codes/cgns/include/CgnsZoneUtil.h index 41236b358..78b03d35c 100644 --- a/codes/cgns/include/CgnsZoneUtil.h +++ b/codes/cgns/include/CgnsZoneUtil.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/include/CgnsZsection.h b/codes/cgns/include/CgnsZsection.h index 1d855504b..bc6e39fd1 100644 --- a/codes/cgns/include/CgnsZsection.h +++ b/codes/cgns/include/CgnsZsection.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/include/HXCgns.h b/codes/cgns/include/HXCgns.h index ca4d74777..54017e27d 100644 --- a/codes/cgns/include/HXCgns.h +++ b/codes/cgns/include/HXCgns.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/src/CgnsBase.cpp b/codes/cgns/src/CgnsBase.cpp index c4620fc41..9c3f1a8dc 100644 --- a/codes/cgns/src/CgnsBase.cpp +++ b/codes/cgns/src/CgnsBase.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/src/CgnsBaseUtil.cpp b/codes/cgns/src/CgnsBaseUtil.cpp index 80fe76675..cb0cabfdc 100644 --- a/codes/cgns/src/CgnsBaseUtil.cpp +++ b/codes/cgns/src/CgnsBaseUtil.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/src/CgnsBc1to1.cpp b/codes/cgns/src/CgnsBc1to1.cpp index 1ba91573c..51df5c900 100644 --- a/codes/cgns/src/CgnsBc1to1.cpp +++ b/codes/cgns/src/CgnsBc1to1.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/src/CgnsBcBoco.cpp b/codes/cgns/src/CgnsBcBoco.cpp index 6405e2943..0839b341b 100644 --- a/codes/cgns/src/CgnsBcBoco.cpp +++ b/codes/cgns/src/CgnsBcBoco.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/src/CgnsBcConn.cpp b/codes/cgns/src/CgnsBcConn.cpp index 6928905e3..ed11e7745 100644 --- a/codes/cgns/src/CgnsBcConn.cpp +++ b/codes/cgns/src/CgnsBcConn.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/src/CgnsBcLink.cpp b/codes/cgns/src/CgnsBcLink.cpp index d84245d76..5c1eccc8a 100644 --- a/codes/cgns/src/CgnsBcLink.cpp +++ b/codes/cgns/src/CgnsBcLink.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/src/CgnsCoor.cpp b/codes/cgns/src/CgnsCoor.cpp index 2d448b969..4c092dbbd 100644 --- a/codes/cgns/src/CgnsCoor.cpp +++ b/codes/cgns/src/CgnsCoor.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/src/CgnsFactory.cpp b/codes/cgns/src/CgnsFactory.cpp index 6927feca3..aa0a6e058 100644 --- a/codes/cgns/src/CgnsFactory.cpp +++ b/codes/cgns/src/CgnsFactory.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/src/CgnsFamilyBc.cpp b/codes/cgns/src/CgnsFamilyBc.cpp index 975a49b35..85c8b365b 100644 --- a/codes/cgns/src/CgnsFamilyBc.cpp +++ b/codes/cgns/src/CgnsFamilyBc.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/src/CgnsFile.cpp b/codes/cgns/src/CgnsFile.cpp index 4643e26f6..667d74cdc 100644 --- a/codes/cgns/src/CgnsFile.cpp +++ b/codes/cgns/src/CgnsFile.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/src/CgnsGlobal.cpp b/codes/cgns/src/CgnsGlobal.cpp index 89e26ddc8..8fe422fe1 100644 --- a/codes/cgns/src/CgnsGlobal.cpp +++ b/codes/cgns/src/CgnsGlobal.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/src/CgnsPeriod.cpp b/codes/cgns/src/CgnsPeriod.cpp index 23603a79f..70815c9e0 100644 --- a/codes/cgns/src/CgnsPeriod.cpp +++ b/codes/cgns/src/CgnsPeriod.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/src/CgnsSection.cpp b/codes/cgns/src/CgnsSection.cpp index 9b8e7705e..4e003b9fa 100644 --- a/codes/cgns/src/CgnsSection.cpp +++ b/codes/cgns/src/CgnsSection.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/src/CgnsVariable.cpp b/codes/cgns/src/CgnsVariable.cpp index c57119460..deb778208 100644 --- a/codes/cgns/src/CgnsVariable.cpp +++ b/codes/cgns/src/CgnsVariable.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/src/CgnsZbase.cpp b/codes/cgns/src/CgnsZbase.cpp index e8c7fbded..8bd8036d8 100644 --- a/codes/cgns/src/CgnsZbase.cpp +++ b/codes/cgns/src/CgnsZbase.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/src/CgnsZbaseUtil.cpp b/codes/cgns/src/CgnsZbaseUtil.cpp index fe771ce23..afad4eabc 100644 --- a/codes/cgns/src/CgnsZbaseUtil.cpp +++ b/codes/cgns/src/CgnsZbaseUtil.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/src/CgnsZbc.cpp b/codes/cgns/src/CgnsZbc.cpp index 1f57aae77..4162ac08b 100644 --- a/codes/cgns/src/CgnsZbc.cpp +++ b/codes/cgns/src/CgnsZbc.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/src/CgnsZbc1to1.cpp b/codes/cgns/src/CgnsZbc1to1.cpp index 9d8ebbf8e..0d58feb6e 100644 --- a/codes/cgns/src/CgnsZbc1to1.cpp +++ b/codes/cgns/src/CgnsZbc1to1.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/src/CgnsZbcBoco.cpp b/codes/cgns/src/CgnsZbcBoco.cpp index 83f7b183d..1c9b070b6 100644 --- a/codes/cgns/src/CgnsZbcBoco.cpp +++ b/codes/cgns/src/CgnsZbcBoco.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/src/CgnsZbcConn.cpp b/codes/cgns/src/CgnsZbcConn.cpp index 061b08ebf..34891b354 100644 --- a/codes/cgns/src/CgnsZbcConn.cpp +++ b/codes/cgns/src/CgnsZbcConn.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/src/CgnsZone.cpp b/codes/cgns/src/CgnsZone.cpp index e1c5ed7ae..3f752f4f1 100644 --- a/codes/cgns/src/CgnsZone.cpp +++ b/codes/cgns/src/CgnsZone.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/src/CgnsZoneUtil.cpp b/codes/cgns/src/CgnsZoneUtil.cpp index a85b75cb4..7d3c04c67 100644 --- a/codes/cgns/src/CgnsZoneUtil.cpp +++ b/codes/cgns/src/CgnsZoneUtil.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/src/CgnsZsection.cpp b/codes/cgns/src/CgnsZsection.cpp index f60e22812..e13986397 100644 --- a/codes/cgns/src/CgnsZsection.cpp +++ b/codes/cgns/src/CgnsZsection.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cgns/src/HXCgns.cpp b/codes/cgns/src/HXCgns.cpp index d06aa76f8..3074878ac 100644 --- a/codes/cgns/src/HXCgns.cpp +++ b/codes/cgns/src/HXCgns.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/chemical/include/BlotterCurve.h b/codes/chemical/include/BlotterCurve.h index d1f9dbe6a..73564b528 100644 --- a/codes/chemical/include/BlotterCurve.h +++ b/codes/chemical/include/BlotterCurve.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/chemical/include/Chemical.h b/codes/chemical/include/Chemical.h index 8a736847c..7d9114f09 100644 --- a/codes/chemical/include/Chemical.h +++ b/codes/chemical/include/Chemical.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/chemical/include/MolecularProperty.h b/codes/chemical/include/MolecularProperty.h index 042163a6d..9fdaaaa2e 100644 --- a/codes/chemical/include/MolecularProperty.h +++ b/codes/chemical/include/MolecularProperty.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/chemical/include/ReactionRate.h b/codes/chemical/include/ReactionRate.h index c557c2d09..c00ffb0fa 100644 --- a/codes/chemical/include/ReactionRate.h +++ b/codes/chemical/include/ReactionRate.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/chemical/include/SchmidtNumber.h b/codes/chemical/include/SchmidtNumber.h index 3e7c4dbe8..d173550aa 100644 --- a/codes/chemical/include/SchmidtNumber.h +++ b/codes/chemical/include/SchmidtNumber.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/chemical/include/Stoichiometric.h b/codes/chemical/include/Stoichiometric.h index 53d39052c..c3402cd89 100644 --- a/codes/chemical/include/Stoichiometric.h +++ b/codes/chemical/include/Stoichiometric.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/chemical/include/Thermodynamic.h b/codes/chemical/include/Thermodynamic.h index 3716f3c4d..c964c0ded 100644 --- a/codes/chemical/include/Thermodynamic.h +++ b/codes/chemical/include/Thermodynamic.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/chemical/src/BlotterCurve.cpp b/codes/chemical/src/BlotterCurve.cpp index ee74f99a9..81feeb190 100644 --- a/codes/chemical/src/BlotterCurve.cpp +++ b/codes/chemical/src/BlotterCurve.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/chemical/src/Chemical.cpp b/codes/chemical/src/Chemical.cpp index 9b20eacee..ae1713e25 100644 --- a/codes/chemical/src/Chemical.cpp +++ b/codes/chemical/src/Chemical.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/chemical/src/MolecularProperty.cpp b/codes/chemical/src/MolecularProperty.cpp index c58bf4eff..51e1f3b25 100644 --- a/codes/chemical/src/MolecularProperty.cpp +++ b/codes/chemical/src/MolecularProperty.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/chemical/src/ReactionRate.cpp b/codes/chemical/src/ReactionRate.cpp index 65ea14acc..25d7c37a5 100644 --- a/codes/chemical/src/ReactionRate.cpp +++ b/codes/chemical/src/ReactionRate.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/chemical/src/SchmidtNumber.cpp b/codes/chemical/src/SchmidtNumber.cpp index 6b5a35cb0..a1b1f5f02 100644 --- a/codes/chemical/src/SchmidtNumber.cpp +++ b/codes/chemical/src/SchmidtNumber.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/chemical/src/Stoichiometric.cpp b/codes/chemical/src/Stoichiometric.cpp index 72a43c6a1..571cc6d94 100644 --- a/codes/chemical/src/Stoichiometric.cpp +++ b/codes/chemical/src/Stoichiometric.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/chemical/src/Thermodynamic.cpp b/codes/chemical/src/Thermodynamic.cpp index 581996e15..a3fcc3706 100644 --- a/codes/chemical/src/Thermodynamic.cpp +++ b/codes/chemical/src/Thermodynamic.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cuda/include/HybridParallel.h b/codes/cuda/include/HybridParallel.h index 828073b15..42b4228de 100644 --- a/codes/cuda/include/HybridParallel.h +++ b/codes/cuda/include/HybridParallel.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/cuda/src/HybridParallel.cpp b/codes/cuda/src/HybridParallel.cpp index 6931dfcfa..2211eacc0 100644 --- a/codes/cuda/src/HybridParallel.cpp +++ b/codes/cuda/src/HybridParallel.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/database/include/DataBase.h b/codes/database/include/DataBase.h index f1522b89e..5e1aa3c5d 100644 --- a/codes/database/include/DataBase.h +++ b/codes/database/include/DataBase.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/database/include/DataBaseIO.h b/codes/database/include/DataBaseIO.h index e622952ad..9a577afb9 100644 --- a/codes/database/include/DataBaseIO.h +++ b/codes/database/include/DataBaseIO.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/database/include/DataBaseType.h b/codes/database/include/DataBaseType.h index 812514826..54a9df2bd 100644 --- a/codes/database/include/DataBaseType.h +++ b/codes/database/include/DataBaseType.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/database/include/DataBook.h b/codes/database/include/DataBook.h index 87d0bdce4..8186efd6a 100644 --- a/codes/database/include/DataBook.h +++ b/codes/database/include/DataBook.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/database/include/DataField.h b/codes/database/include/DataField.h index 51d3826d4..919e21aa2 100644 --- a/codes/database/include/DataField.h +++ b/codes/database/include/DataField.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/database/include/DataObject.h b/codes/database/include/DataObject.h index 587143949..4340b5dd8 100644 --- a/codes/database/include/DataObject.h +++ b/codes/database/include/DataObject.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/database/include/DataPage.h b/codes/database/include/DataPage.h index 2d93fdd56..62fd2b2f4 100644 --- a/codes/database/include/DataPage.h +++ b/codes/database/include/DataPage.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/database/include/DataPara.h b/codes/database/include/DataPara.h index e62b37b06..722e5df16 100644 --- a/codes/database/include/DataPara.h +++ b/codes/database/include/DataPara.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/database/include/DataPointer.h b/codes/database/include/DataPointer.h index 41a1a7d91..b9abb9ed9 100644 --- a/codes/database/include/DataPointer.h +++ b/codes/database/include/DataPointer.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/database/include/DataStorage.h b/codes/database/include/DataStorage.h index 77e381f3f..2713c0b09 100644 --- a/codes/database/include/DataStorage.h +++ b/codes/database/include/DataStorage.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/database/src/DataBase.cpp b/codes/database/src/DataBase.cpp index 5274b2b38..a7eed3cca 100644 --- a/codes/database/src/DataBase.cpp +++ b/codes/database/src/DataBase.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/database/src/DataBaseIO.cpp b/codes/database/src/DataBaseIO.cpp index a78051c8d..bac00dafe 100644 --- a/codes/database/src/DataBaseIO.cpp +++ b/codes/database/src/DataBaseIO.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/database/src/DataBaseType.cpp b/codes/database/src/DataBaseType.cpp index f13397d49..5d8a0ebf6 100644 --- a/codes/database/src/DataBaseType.cpp +++ b/codes/database/src/DataBaseType.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/database/src/DataBook.cpp b/codes/database/src/DataBook.cpp index 16ed3e99e..b00f1a86c 100644 --- a/codes/database/src/DataBook.cpp +++ b/codes/database/src/DataBook.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/database/src/DataField.cpp b/codes/database/src/DataField.cpp index f3d687ab0..013621af2 100644 --- a/codes/database/src/DataField.cpp +++ b/codes/database/src/DataField.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/database/src/DataPage.cpp b/codes/database/src/DataPage.cpp index bc0c3e591..2e7403637 100644 --- a/codes/database/src/DataPage.cpp +++ b/codes/database/src/DataPage.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/database/src/DataPara.cpp b/codes/database/src/DataPara.cpp index 09b8e30b8..908bdb824 100644 --- a/codes/database/src/DataPara.cpp +++ b/codes/database/src/DataPara.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/database/src/DataStorage.cpp b/codes/database/src/DataStorage.cpp index 18e65b8ad..e6fd06465 100644 --- a/codes/database/src/DataStorage.cpp +++ b/codes/database/src/DataStorage.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/force/include/AeroForce.h b/codes/force/include/AeroForce.h index 84d8584d3..0ae2004d4 100644 --- a/codes/force/include/AeroForce.h +++ b/codes/force/include/AeroForce.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/force/include/AeroForceTask.h b/codes/force/include/AeroForceTask.h index 7544165b5..d62a60f2c 100644 --- a/codes/force/include/AeroForceTask.h +++ b/codes/force/include/AeroForceTask.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/force/include/AeroForceTaskReg.h b/codes/force/include/AeroForceTaskReg.h index 396082f85..9fa9652f9 100644 --- a/codes/force/include/AeroForceTaskReg.h +++ b/codes/force/include/AeroForceTaskReg.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/force/include/Force.h b/codes/force/include/Force.h index a5a9ae4d4..875da7da6 100644 --- a/codes/force/include/Force.h +++ b/codes/force/include/Force.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/force/src/AeroForce.cpp b/codes/force/src/AeroForce.cpp index a0f3e06d7..b3636a1ab 100644 --- a/codes/force/src/AeroForce.cpp +++ b/codes/force/src/AeroForce.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/force/src/AeroForceTask.cpp b/codes/force/src/AeroForceTask.cpp index a9cd3d7d0..eab8d82b7 100644 --- a/codes/force/src/AeroForceTask.cpp +++ b/codes/force/src/AeroForceTask.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/force/src/AeroForceTaskReg.cpp b/codes/force/src/AeroForceTaskReg.cpp index 3dbaf6d06..c72b7b92f 100644 --- a/codes/force/src/AeroForceTaskReg.cpp +++ b/codes/force/src/AeroForceTaskReg.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/force/src/Force.cpp b/codes/force/src/Force.cpp index 3c2f2bd93..66dc47b22 100644 --- a/codes/force/src/Force.cpp +++ b/codes/force/src/Force.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/BcRecord.h b/codes/geometry/include/BcRecord.h index 47b057714..cbd97a5ed 100644 --- a/codes/geometry/include/BcRecord.h +++ b/codes/geometry/include/BcRecord.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/Boundary.h b/codes/geometry/include/Boundary.h index 259e437f0..01abc8bdf 100644 --- a/codes/geometry/include/Boundary.h +++ b/codes/geometry/include/Boundary.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/CalcGrid.h b/codes/geometry/include/CalcGrid.h index 17195f0a2..2397c45b5 100644 --- a/codes/geometry/include/CalcGrid.h +++ b/codes/geometry/include/CalcGrid.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/CellMesh.h b/codes/geometry/include/CellMesh.h index 405b6fa90..13b667580 100644 --- a/codes/geometry/include/CellMesh.h +++ b/codes/geometry/include/CellMesh.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/CellTopo.h b/codes/geometry/include/CellTopo.h index a9fb501d1..c47575106 100644 --- a/codes/geometry/include/CellTopo.h +++ b/codes/geometry/include/CellTopo.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/DomainInp.h b/codes/geometry/include/DomainInp.h index a4fceb010..55ac84278 100644 --- a/codes/geometry/include/DomainInp.h +++ b/codes/geometry/include/DomainInp.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/ElemFeature.h b/codes/geometry/include/ElemFeature.h index 894594d9b..b537f1fa0 100644 --- a/codes/geometry/include/ElemFeature.h +++ b/codes/geometry/include/ElemFeature.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/ElementHome.h b/codes/geometry/include/ElementHome.h index 006f0d75a..80ec09681 100644 --- a/codes/geometry/include/ElementHome.h +++ b/codes/geometry/include/ElementHome.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/FaceMesh.h b/codes/geometry/include/FaceMesh.h index d486a73b4..8102a4457 100644 --- a/codes/geometry/include/FaceMesh.h +++ b/codes/geometry/include/FaceMesh.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/FaceSearch.h b/codes/geometry/include/FaceSearch.h index b8c0b147e..177b852b7 100644 --- a/codes/geometry/include/FaceSearch.h +++ b/codes/geometry/include/FaceSearch.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/FaceSolver.h b/codes/geometry/include/FaceSolver.h index 1d5aa3991..dc12f596b 100644 --- a/codes/geometry/include/FaceSolver.h +++ b/codes/geometry/include/FaceSolver.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/FaceTopo.h b/codes/geometry/include/FaceTopo.h index 0d46200ca..df32f9e31 100644 --- a/codes/geometry/include/FaceTopo.h +++ b/codes/geometry/include/FaceTopo.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/Grid.h b/codes/geometry/include/Grid.h index a5da1e012..4e757fdb8 100644 --- a/codes/geometry/include/Grid.h +++ b/codes/geometry/include/Grid.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/GridDef.h b/codes/geometry/include/GridDef.h index 9547ef44e..af8f9b04e 100644 --- a/codes/geometry/include/GridDef.h +++ b/codes/geometry/include/GridDef.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/GridElem.h b/codes/geometry/include/GridElem.h index 7594e8dc0..58190cea6 100644 --- a/codes/geometry/include/GridElem.h +++ b/codes/geometry/include/GridElem.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/GridFactory.h b/codes/geometry/include/GridFactory.h index 74f1e04e2..b7b354052 100644 --- a/codes/geometry/include/GridFactory.h +++ b/codes/geometry/include/GridFactory.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/GridMediator.h b/codes/geometry/include/GridMediator.h index ff91d57f3..66a760501 100644 --- a/codes/geometry/include/GridMediator.h +++ b/codes/geometry/include/GridMediator.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/GridPara.h b/codes/geometry/include/GridPara.h index c6cb745d9..b66b27847 100644 --- a/codes/geometry/include/GridPara.h +++ b/codes/geometry/include/GridPara.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/GridTask.h b/codes/geometry/include/GridTask.h index 7a3f61da4..adb4145b1 100644 --- a/codes/geometry/include/GridTask.h +++ b/codes/geometry/include/GridTask.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/IFaceLink.h b/codes/geometry/include/IFaceLink.h index aeeb1d74d..723ab211d 100644 --- a/codes/geometry/include/IFaceLink.h +++ b/codes/geometry/include/IFaceLink.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/InterFace.h b/codes/geometry/include/InterFace.h index e35a6db1e..888a334be 100644 --- a/codes/geometry/include/InterFace.h +++ b/codes/geometry/include/InterFace.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/NodeMesh.h b/codes/geometry/include/NodeMesh.h index d32a810be..e4d10dd2c 100644 --- a/codes/geometry/include/NodeMesh.h +++ b/codes/geometry/include/NodeMesh.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/Plot3D.h b/codes/geometry/include/Plot3D.h index cf4b5599c..21233c9a5 100644 --- a/codes/geometry/include/Plot3D.h +++ b/codes/geometry/include/Plot3D.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/Point.h b/codes/geometry/include/Point.h index 42824b2fb..9fb6ad65c 100644 --- a/codes/geometry/include/Point.h +++ b/codes/geometry/include/Point.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/PointFactory.h b/codes/geometry/include/PointFactory.h index f4be43db9..335da9e7b 100644 --- a/codes/geometry/include/PointFactory.h +++ b/codes/geometry/include/PointFactory.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/PointSearch.h b/codes/geometry/include/PointSearch.h index 40b7eb5d9..59fc23854 100644 --- a/codes/geometry/include/PointSearch.h +++ b/codes/geometry/include/PointSearch.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/SlipFace.h b/codes/geometry/include/SlipFace.h index 73aca811d..06c16d028 100644 --- a/codes/geometry/include/SlipFace.h +++ b/codes/geometry/include/SlipFace.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/StrGrid.h b/codes/geometry/include/StrGrid.h index 03e2e53c9..b47d69874 100644 --- a/codes/geometry/include/StrGrid.h +++ b/codes/geometry/include/StrGrid.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/StrRegion.h b/codes/geometry/include/StrRegion.h index 947a693c5..73770c200 100644 --- a/codes/geometry/include/StrRegion.h +++ b/codes/geometry/include/StrRegion.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/Su2Grid.h b/codes/geometry/include/Su2Grid.h index 2e15c81be..1840d8c42 100644 --- a/codes/geometry/include/Su2Grid.h +++ b/codes/geometry/include/Su2Grid.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/UnitElement.h b/codes/geometry/include/UnitElement.h index d06f4d0ae..ce4489bb9 100644 --- a/codes/geometry/include/UnitElement.h +++ b/codes/geometry/include/UnitElement.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/UnsGrid.h b/codes/geometry/include/UnsGrid.h index a2105419a..648299703 100644 --- a/codes/geometry/include/UnsGrid.h +++ b/codes/geometry/include/UnsGrid.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/Visual.h b/codes/geometry/include/Visual.h index 0b68841c4..059c79e9b 100644 --- a/codes/geometry/include/Visual.h +++ b/codes/geometry/include/Visual.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/include/WallDist.h b/codes/geometry/include/WallDist.h index 3a19c9c8c..bed15ed0e 100644 --- a/codes/geometry/include/WallDist.h +++ b/codes/geometry/include/WallDist.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/BcRecord.cpp b/codes/geometry/src/BcRecord.cpp index 27baaa576..09cf1e387 100644 --- a/codes/geometry/src/BcRecord.cpp +++ b/codes/geometry/src/BcRecord.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/Boundary.cpp b/codes/geometry/src/Boundary.cpp index a293eded7..968196c5c 100644 --- a/codes/geometry/src/Boundary.cpp +++ b/codes/geometry/src/Boundary.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/CalcGrid.cpp b/codes/geometry/src/CalcGrid.cpp index 4cd97144a..3fb73aa51 100644 --- a/codes/geometry/src/CalcGrid.cpp +++ b/codes/geometry/src/CalcGrid.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/CellMesh.cpp b/codes/geometry/src/CellMesh.cpp index 3c3278643..66804f6fa 100644 --- a/codes/geometry/src/CellMesh.cpp +++ b/codes/geometry/src/CellMesh.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/CellTopo.cpp b/codes/geometry/src/CellTopo.cpp index deca70739..7bd85102e 100644 --- a/codes/geometry/src/CellTopo.cpp +++ b/codes/geometry/src/CellTopo.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/DomainInp.cpp b/codes/geometry/src/DomainInp.cpp index aa0275081..17e15428a 100644 --- a/codes/geometry/src/DomainInp.cpp +++ b/codes/geometry/src/DomainInp.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/ElemFeature.cpp b/codes/geometry/src/ElemFeature.cpp index 16d559f10..ae238bfcf 100644 --- a/codes/geometry/src/ElemFeature.cpp +++ b/codes/geometry/src/ElemFeature.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/ElementHome.cpp b/codes/geometry/src/ElementHome.cpp index 47786aa89..82874145d 100644 --- a/codes/geometry/src/ElementHome.cpp +++ b/codes/geometry/src/ElementHome.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/FaceMesh.cpp b/codes/geometry/src/FaceMesh.cpp index a5649a3cb..c9147fc89 100644 --- a/codes/geometry/src/FaceMesh.cpp +++ b/codes/geometry/src/FaceMesh.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/FaceSearch.cpp b/codes/geometry/src/FaceSearch.cpp index a7e038f06..84cd704a9 100644 --- a/codes/geometry/src/FaceSearch.cpp +++ b/codes/geometry/src/FaceSearch.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/FaceSolver.cpp b/codes/geometry/src/FaceSolver.cpp index 6a585ab73..be4948c28 100644 --- a/codes/geometry/src/FaceSolver.cpp +++ b/codes/geometry/src/FaceSolver.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/FaceTopo.cpp b/codes/geometry/src/FaceTopo.cpp index d6c5c5487..3dcb33773 100644 --- a/codes/geometry/src/FaceTopo.cpp +++ b/codes/geometry/src/FaceTopo.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/Grid.cpp b/codes/geometry/src/Grid.cpp index 9a1062043..27330700b 100644 --- a/codes/geometry/src/Grid.cpp +++ b/codes/geometry/src/Grid.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/GridDef.cpp b/codes/geometry/src/GridDef.cpp index 954864048..442cdd80b 100644 --- a/codes/geometry/src/GridDef.cpp +++ b/codes/geometry/src/GridDef.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/GridElem.cpp b/codes/geometry/src/GridElem.cpp index 3a1e6a027..05b743842 100644 --- a/codes/geometry/src/GridElem.cpp +++ b/codes/geometry/src/GridElem.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/GridFactory.cpp b/codes/geometry/src/GridFactory.cpp index 95245147a..04e2562ee 100644 --- a/codes/geometry/src/GridFactory.cpp +++ b/codes/geometry/src/GridFactory.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/GridMediator.cpp b/codes/geometry/src/GridMediator.cpp index 2da43e5ca..3930742e5 100644 --- a/codes/geometry/src/GridMediator.cpp +++ b/codes/geometry/src/GridMediator.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/GridPara.cpp b/codes/geometry/src/GridPara.cpp index 28452172c..2d2b127b1 100644 --- a/codes/geometry/src/GridPara.cpp +++ b/codes/geometry/src/GridPara.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/GridTask.cpp b/codes/geometry/src/GridTask.cpp index 706cdaabc..84957d2ab 100644 --- a/codes/geometry/src/GridTask.cpp +++ b/codes/geometry/src/GridTask.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/IFaceLink.cpp b/codes/geometry/src/IFaceLink.cpp index c3ecc6d2a..8605629e8 100644 --- a/codes/geometry/src/IFaceLink.cpp +++ b/codes/geometry/src/IFaceLink.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/InterFace.cpp b/codes/geometry/src/InterFace.cpp index a19bc3a48..0541e6087 100644 --- a/codes/geometry/src/InterFace.cpp +++ b/codes/geometry/src/InterFace.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/NodeMesh.cpp b/codes/geometry/src/NodeMesh.cpp index 1a09f7105..170761065 100644 --- a/codes/geometry/src/NodeMesh.cpp +++ b/codes/geometry/src/NodeMesh.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/Plot3D.cpp b/codes/geometry/src/Plot3D.cpp index e6a569370..8034036c5 100644 --- a/codes/geometry/src/Plot3D.cpp +++ b/codes/geometry/src/Plot3D.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/PointFactory.cpp b/codes/geometry/src/PointFactory.cpp index 6c87e3d65..8c60f7e58 100644 --- a/codes/geometry/src/PointFactory.cpp +++ b/codes/geometry/src/PointFactory.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/PointSearch.cpp b/codes/geometry/src/PointSearch.cpp index bc4578103..53a8adfac 100644 --- a/codes/geometry/src/PointSearch.cpp +++ b/codes/geometry/src/PointSearch.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/SlipFace.cpp b/codes/geometry/src/SlipFace.cpp index 601e0a746..ccd48211e 100644 --- a/codes/geometry/src/SlipFace.cpp +++ b/codes/geometry/src/SlipFace.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/StrGrid.cpp b/codes/geometry/src/StrGrid.cpp index 971bf185a..4b2998c9c 100644 --- a/codes/geometry/src/StrGrid.cpp +++ b/codes/geometry/src/StrGrid.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/StrRegion.cpp b/codes/geometry/src/StrRegion.cpp index 6e91b183c..4b6a91455 100644 --- a/codes/geometry/src/StrRegion.cpp +++ b/codes/geometry/src/StrRegion.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/Su2Grid.cpp b/codes/geometry/src/Su2Grid.cpp index d679e737d..f88fc6bfe 100644 --- a/codes/geometry/src/Su2Grid.cpp +++ b/codes/geometry/src/Su2Grid.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/UnitElement.cpp b/codes/geometry/src/UnitElement.cpp index 51070cc41..4df5c21df 100644 --- a/codes/geometry/src/UnitElement.cpp +++ b/codes/geometry/src/UnitElement.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/UnsGrid.cpp b/codes/geometry/src/UnsGrid.cpp index f91e69d5b..acc659623 100644 --- a/codes/geometry/src/UnsGrid.cpp +++ b/codes/geometry/src/UnsGrid.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/Visual.cpp b/codes/geometry/src/Visual.cpp index 0fcecb2fc..d8f20c547 100644 --- a/codes/geometry/src/Visual.cpp +++ b/codes/geometry/src/Visual.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/geometry/src/WallDist.cpp b/codes/geometry/src/WallDist.cpp index 8ad3df7f8..16aaf02b8 100644 --- a/codes/geometry/src/WallDist.cpp +++ b/codes/geometry/src/WallDist.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/include/Com.h b/codes/global/include/Com.h index d1f169d64..a82e29378 100644 --- a/codes/global/include/Com.h +++ b/codes/global/include/Com.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/include/Ctrl.h b/codes/global/include/Ctrl.h index 910839b4d..affc2d579 100644 --- a/codes/global/include/Ctrl.h +++ b/codes/global/include/Ctrl.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/include/Dimension.h b/codes/global/include/Dimension.h index 389c9a7a1..205735249 100644 --- a/codes/global/include/Dimension.h +++ b/codes/global/include/Dimension.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/include/DimensionImp.h b/codes/global/include/DimensionImp.h index d3381b8c1..7f8f27db7 100644 --- a/codes/global/include/DimensionImp.h +++ b/codes/global/include/DimensionImp.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/include/FieldAlloc.h b/codes/global/include/FieldAlloc.h index af490e08d..20a4389ad 100644 --- a/codes/global/include/FieldAlloc.h +++ b/codes/global/include/FieldAlloc.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/include/FieldBase.h b/codes/global/include/FieldBase.h index 1b55fa0bf..20cd314c0 100644 --- a/codes/global/include/FieldBase.h +++ b/codes/global/include/FieldBase.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/include/FieldImp.h b/codes/global/include/FieldImp.h index 464837639..156259a6c 100644 --- a/codes/global/include/FieldImp.h +++ b/codes/global/include/FieldImp.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/include/FieldRecord.h b/codes/global/include/FieldRecord.h index eb4370c1a..4ae14b92f 100644 --- a/codes/global/include/FieldRecord.h +++ b/codes/global/include/FieldRecord.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/include/FieldSimu.h b/codes/global/include/FieldSimu.h index dccb80dfc..dd1991d97 100644 --- a/codes/global/include/FieldSimu.h +++ b/codes/global/include/FieldSimu.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/include/FileMap.h b/codes/global/include/FileMap.h index 3ac27dfc6..bb64c397f 100644 --- a/codes/global/include/FileMap.h +++ b/codes/global/include/FileMap.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/include/Iteration.h b/codes/global/include/Iteration.h index 213119b2e..a54f28ea1 100644 --- a/codes/global/include/Iteration.h +++ b/codes/global/include/Iteration.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/include/SimuDef.h b/codes/global/include/SimuDef.h index 2fa3cf24d..ae049a22e 100644 --- a/codes/global/include/SimuDef.h +++ b/codes/global/include/SimuDef.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/include/SolverDef.h b/codes/global/include/SolverDef.h index 19c6d2d38..8389c0dc1 100644 --- a/codes/global/include/SolverDef.h +++ b/codes/global/include/SolverDef.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/include/SolverName.h b/codes/global/include/SolverName.h index 95df20d6e..6fc9cb6b0 100644 --- a/codes/global/include/SolverName.h +++ b/codes/global/include/SolverName.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/include/SolverRegister.h b/codes/global/include/SolverRegister.h index 812cd8a36..5b78b8668 100644 --- a/codes/global/include/SolverRegister.h +++ b/codes/global/include/SolverRegister.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/include/SolverTaskReg.h b/codes/global/include/SolverTaskReg.h index 22febf6ab..7a673463a 100644 --- a/codes/global/include/SolverTaskReg.h +++ b/codes/global/include/SolverTaskReg.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/include/System.h b/codes/global/include/System.h index 45407aab9..18fbf486e 100644 --- a/codes/global/include/System.h +++ b/codes/global/include/System.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/include/Tolerence.h b/codes/global/include/Tolerence.h index 42a010005..49ee28bc3 100644 --- a/codes/global/include/Tolerence.h +++ b/codes/global/include/Tolerence.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/src/Com.cpp b/codes/global/src/Com.cpp index af20592d7..98d7c1f09 100644 --- a/codes/global/src/Com.cpp +++ b/codes/global/src/Com.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/src/Ctrl.cpp b/codes/global/src/Ctrl.cpp index 803132a01..dac151581 100644 --- a/codes/global/src/Ctrl.cpp +++ b/codes/global/src/Ctrl.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/src/Dimension.cpp b/codes/global/src/Dimension.cpp index 29cc0d83b..ce1c314c1 100644 --- a/codes/global/src/Dimension.cpp +++ b/codes/global/src/Dimension.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/src/DimensionImp.cpp b/codes/global/src/DimensionImp.cpp index 599d051d5..564979963 100644 --- a/codes/global/src/DimensionImp.cpp +++ b/codes/global/src/DimensionImp.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/src/FieldAlloc.cpp b/codes/global/src/FieldAlloc.cpp index 1f706e4ae..1ce75d5b4 100644 --- a/codes/global/src/FieldAlloc.cpp +++ b/codes/global/src/FieldAlloc.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/src/FieldBase.cpp b/codes/global/src/FieldBase.cpp index 7bf47e49a..3bbd997f4 100644 --- a/codes/global/src/FieldBase.cpp +++ b/codes/global/src/FieldBase.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/src/FieldImp.cpp b/codes/global/src/FieldImp.cpp index a2b9badf8..2053b5f6b 100644 --- a/codes/global/src/FieldImp.cpp +++ b/codes/global/src/FieldImp.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/src/FieldRecord.cpp b/codes/global/src/FieldRecord.cpp index 10b869e56..25ac589e5 100644 --- a/codes/global/src/FieldRecord.cpp +++ b/codes/global/src/FieldRecord.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/src/FieldSimu.cpp b/codes/global/src/FieldSimu.cpp index f905467bd..f066c967e 100644 --- a/codes/global/src/FieldSimu.cpp +++ b/codes/global/src/FieldSimu.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/src/FileMap.cpp b/codes/global/src/FileMap.cpp index 2a0d30f53..088e4ded8 100644 --- a/codes/global/src/FileMap.cpp +++ b/codes/global/src/FileMap.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/src/Interation.cpp b/codes/global/src/Interation.cpp index 64dd8e0bd..c3ab9668d 100644 --- a/codes/global/src/Interation.cpp +++ b/codes/global/src/Interation.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/src/SimuDef.cpp b/codes/global/src/SimuDef.cpp index feea30307..da023efab 100644 --- a/codes/global/src/SimuDef.cpp +++ b/codes/global/src/SimuDef.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/src/SolverDef.cpp b/codes/global/src/SolverDef.cpp index 669dd10e2..93a0bf54d 100644 --- a/codes/global/src/SolverDef.cpp +++ b/codes/global/src/SolverDef.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/src/SolverName.cpp b/codes/global/src/SolverName.cpp index b75bbd755..6a3da5240 100644 --- a/codes/global/src/SolverName.cpp +++ b/codes/global/src/SolverName.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/src/SolverRegister.cpp b/codes/global/src/SolverRegister.cpp index 01497f7e6..2b6eda098 100644 --- a/codes/global/src/SolverRegister.cpp +++ b/codes/global/src/SolverRegister.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/src/SolverTaskReg.cpp b/codes/global/src/SolverTaskReg.cpp index ee3d57299..fb600bfbc 100644 --- a/codes/global/src/SolverTaskReg.cpp +++ b/codes/global/src/SolverTaskReg.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/src/System.cpp b/codes/global/src/System.cpp index 9b32374b9..3b2677820 100644 --- a/codes/global/src/System.cpp +++ b/codes/global/src/System.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/global/src/Tolerence.cpp b/codes/global/src/Tolerence.cpp index 1dd8baa7f..ed6849d5c 100644 --- a/codes/global/src/Tolerence.cpp +++ b/codes/global/src/Tolerence.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/grad/include/Grad.h b/codes/grad/include/Grad.h index 2d7fbe1d3..9d7e96207 100644 --- a/codes/grad/include/Grad.h +++ b/codes/grad/include/Grad.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/grad/src/Grad.cpp b/codes/grad/src/Grad.cpp index aca3d4dab..f27879e4b 100644 --- a/codes/grad/src/Grad.cpp +++ b/codes/grad/src/Grad.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/implicit/include/ImplicitTaskReg.h b/codes/implicit/include/ImplicitTaskReg.h index 7acade1a9..dd322ba53 100644 --- a/codes/implicit/include/ImplicitTaskReg.h +++ b/codes/implicit/include/ImplicitTaskReg.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/implicit/include/Lusgs.h b/codes/implicit/include/Lusgs.h index a914a014a..38fb0203b 100644 --- a/codes/implicit/include/Lusgs.h +++ b/codes/implicit/include/Lusgs.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/implicit/src/ImplicitTaskReg.cpp b/codes/implicit/src/ImplicitTaskReg.cpp index 1e6a73a11..8987608d8 100644 --- a/codes/implicit/src/ImplicitTaskReg.cpp +++ b/codes/implicit/src/ImplicitTaskReg.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/implicit/src/Lusgs.cpp b/codes/implicit/src/Lusgs.cpp index f972f9b3d..dd4f9cfbb 100644 --- a/codes/implicit/src/Lusgs.cpp +++ b/codes/implicit/src/Lusgs.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ins/include/INsBcSolver.h b/codes/ins/include/INsBcSolver.h index 964bc38b5..5a53b5358 100644 --- a/codes/ins/include/INsBcSolver.h +++ b/codes/ins/include/INsBcSolver.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ins/include/INsCom.h b/codes/ins/include/INsCom.h index e6e0740c7..1233b09a9 100644 --- a/codes/ins/include/INsCom.h +++ b/codes/ins/include/INsCom.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ins/include/INsCtrl.h b/codes/ins/include/INsCtrl.h index 89bb9968b..9cf2f9f96 100644 --- a/codes/ins/include/INsCtrl.h +++ b/codes/ins/include/INsCtrl.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ins/include/INsIdx.h b/codes/ins/include/INsIdx.h index 273b1fd8f..365c34d3c 100644 --- a/codes/ins/include/INsIdx.h +++ b/codes/ins/include/INsIdx.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ins/include/INsInvterm.h b/codes/ins/include/INsInvterm.h index fedd28288..0e8174794 100644 --- a/codes/ins/include/INsInvterm.h +++ b/codes/ins/include/INsInvterm.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ins/include/INsLusgs.h b/codes/ins/include/INsLusgs.h index cf9dabe19..fc3d505f8 100644 --- a/codes/ins/include/INsLusgs.h +++ b/codes/ins/include/INsLusgs.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ins/include/INsRestart.h b/codes/ins/include/INsRestart.h index 042d994dc..e42454ed2 100644 --- a/codes/ins/include/INsRestart.h +++ b/codes/ins/include/INsRestart.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ins/include/INsRhs.h b/codes/ins/include/INsRhs.h index a02e05993..918f80cc7 100644 --- a/codes/ins/include/INsRhs.h +++ b/codes/ins/include/INsRhs.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ins/include/INsSolver.h b/codes/ins/include/INsSolver.h index 60994022d..57a22855a 100644 --- a/codes/ins/include/INsSolver.h +++ b/codes/ins/include/INsSolver.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ins/include/INsSolverImp.h b/codes/ins/include/INsSolverImp.h index 20da1d8a0..ed7df88ee 100644 --- a/codes/ins/include/INsSolverImp.h +++ b/codes/ins/include/INsSolverImp.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ins/include/INsSpectrum.h b/codes/ins/include/INsSpectrum.h index 278657531..f985a9eda 100644 --- a/codes/ins/include/INsSpectrum.h +++ b/codes/ins/include/INsSpectrum.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ins/include/INsUnsteady.h b/codes/ins/include/INsUnsteady.h index 179001c2d..37e9476cc 100644 --- a/codes/ins/include/INsUnsteady.h +++ b/codes/ins/include/INsUnsteady.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ins/include/INsUpdate.h b/codes/ins/include/INsUpdate.h index 83097ca84..411d90fe3 100644 --- a/codes/ins/include/INsUpdate.h +++ b/codes/ins/include/INsUpdate.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ins/include/INsVisterm.h b/codes/ins/include/INsVisterm.h index 05d5eabf3..19208bef2 100644 --- a/codes/ins/include/INsVisterm.h +++ b/codes/ins/include/INsVisterm.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ins/src/INsBcSolver.cpp b/codes/ins/src/INsBcSolver.cpp index 6743a5320..1fefa27e0 100644 --- a/codes/ins/src/INsBcSolver.cpp +++ b/codes/ins/src/INsBcSolver.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ins/src/INsCom.cpp b/codes/ins/src/INsCom.cpp index 3cde5afcc..086038380 100644 --- a/codes/ins/src/INsCom.cpp +++ b/codes/ins/src/INsCom.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ins/src/INsCtrl.cpp b/codes/ins/src/INsCtrl.cpp index 374127b6b..51899dbf2 100644 --- a/codes/ins/src/INsCtrl.cpp +++ b/codes/ins/src/INsCtrl.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ins/src/INsInvterm.cpp b/codes/ins/src/INsInvterm.cpp index 1bea1af70..147c2642c 100644 --- a/codes/ins/src/INsInvterm.cpp +++ b/codes/ins/src/INsInvterm.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ins/src/INsLusgs.cpp b/codes/ins/src/INsLusgs.cpp index 92fec9f2b..9a076c363 100644 --- a/codes/ins/src/INsLusgs.cpp +++ b/codes/ins/src/INsLusgs.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ins/src/INsRestart.cpp b/codes/ins/src/INsRestart.cpp index df7cfa4ae..c9ccb7dad 100644 --- a/codes/ins/src/INsRestart.cpp +++ b/codes/ins/src/INsRestart.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ins/src/INsRhs.cpp b/codes/ins/src/INsRhs.cpp index a211ae496..6d197baf9 100644 --- a/codes/ins/src/INsRhs.cpp +++ b/codes/ins/src/INsRhs.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ins/src/INsSolver.cpp b/codes/ins/src/INsSolver.cpp index 197c15f46..f64be696e 100644 --- a/codes/ins/src/INsSolver.cpp +++ b/codes/ins/src/INsSolver.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ins/src/INsSolverImp.cpp b/codes/ins/src/INsSolverImp.cpp index 35cc079f0..ec26fb3ac 100644 --- a/codes/ins/src/INsSolverImp.cpp +++ b/codes/ins/src/INsSolverImp.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ins/src/INsSpectrum.cpp b/codes/ins/src/INsSpectrum.cpp index e3c53e7e3..4f6411bdb 100644 --- a/codes/ins/src/INsSpectrum.cpp +++ b/codes/ins/src/INsSpectrum.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ins/src/INsUnsteady.cpp b/codes/ins/src/INsUnsteady.cpp index b08745e83..170c6ae59 100644 --- a/codes/ins/src/INsUnsteady.cpp +++ b/codes/ins/src/INsUnsteady.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ins/src/INsUpdate.cpp b/codes/ins/src/INsUpdate.cpp index f64650616..8ed2da2ae 100644 --- a/codes/ins/src/INsUpdate.cpp +++ b/codes/ins/src/INsUpdate.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ins/src/INsVisterm.cpp b/codes/ins/src/INsVisterm.cpp index 4f1ea8e6f..6c39498d1 100644 --- a/codes/ins/src/INsVisterm.cpp +++ b/codes/ins/src/INsVisterm.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/interface/include/InterField.h b/codes/interface/include/InterField.h index 6933a6f47..913abbc59 100644 --- a/codes/interface/include/InterField.h +++ b/codes/interface/include/InterField.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/interface/include/InterfaceTaskReg.h b/codes/interface/include/InterfaceTaskReg.h index 7d9c9b170..af79550bd 100644 --- a/codes/interface/include/InterfaceTaskReg.h +++ b/codes/interface/include/InterfaceTaskReg.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/interface/src/InterField.cpp b/codes/interface/src/InterField.cpp index df2b25546..8face9d45 100644 --- a/codes/interface/src/InterField.cpp +++ b/codes/interface/src/InterField.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/interface/src/InterfaceTaskReg.cpp b/codes/interface/src/InterfaceTaskReg.cpp index 182083a7c..7e65fc22c 100644 --- a/codes/interface/src/InterfaceTaskReg.cpp +++ b/codes/interface/src/InterfaceTaskReg.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/io/include/CommentLine.h b/codes/io/include/CommentLine.h index 272ef3c46..78dd53fad 100644 --- a/codes/io/include/CommentLine.h +++ b/codes/io/include/CommentLine.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/io/include/FileIO.h b/codes/io/include/FileIO.h index 649a40f0d..bf0da52e0 100644 --- a/codes/io/include/FileIO.h +++ b/codes/io/include/FileIO.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/io/include/FileO.h b/codes/io/include/FileO.h index 2dc3d5868..8fd5f4ff0 100644 --- a/codes/io/include/FileO.h +++ b/codes/io/include/FileO.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/io/include/LogFile.h b/codes/io/include/LogFile.h index 4c109d52b..bd139298e 100644 --- a/codes/io/include/LogFile.h +++ b/codes/io/include/LogFile.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/io/include/PIO.h b/codes/io/include/PIO.h index e89d58efe..6a917b0d7 100644 --- a/codes/io/include/PIO.h +++ b/codes/io/include/PIO.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/io/include/ParaFile.h b/codes/io/include/ParaFile.h index 0660808b2..727d6bcbb 100644 --- a/codes/io/include/ParaFile.h +++ b/codes/io/include/ParaFile.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/io/include/StrUtil.h b/codes/io/include/StrUtil.h index a13e0bfab..97b6cee89 100644 --- a/codes/io/include/StrUtil.h +++ b/codes/io/include/StrUtil.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/io/include/Word.h b/codes/io/include/Word.h index 3b167bad9..2cae7fc23 100644 --- a/codes/io/include/Word.h +++ b/codes/io/include/Word.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/io/src/CommentLine.cpp b/codes/io/src/CommentLine.cpp index ba61e704b..884967a92 100644 --- a/codes/io/src/CommentLine.cpp +++ b/codes/io/src/CommentLine.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/io/src/FileIO.cpp b/codes/io/src/FileIO.cpp index 2de10417f..14f9de20a 100644 --- a/codes/io/src/FileIO.cpp +++ b/codes/io/src/FileIO.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/io/src/FileO.cpp b/codes/io/src/FileO.cpp index f61c5cd05..e2e76b4a6 100644 --- a/codes/io/src/FileO.cpp +++ b/codes/io/src/FileO.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/io/src/LogFile.cpp b/codes/io/src/LogFile.cpp index 9b56744bf..24c8bd1d9 100644 --- a/codes/io/src/LogFile.cpp +++ b/codes/io/src/LogFile.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/io/src/PIO.cpp b/codes/io/src/PIO.cpp index 9a0ad785a..095568225 100644 --- a/codes/io/src/PIO.cpp +++ b/codes/io/src/PIO.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/io/src/ParaFile.cpp b/codes/io/src/ParaFile.cpp index 698b8d50d..8bda2ff29 100644 --- a/codes/io/src/ParaFile.cpp +++ b/codes/io/src/ParaFile.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/io/src/StrUtil.cpp b/codes/io/src/StrUtil.cpp index ee6e59537..5f82b18e7 100644 --- a/codes/io/src/StrUtil.cpp +++ b/codes/io/src/StrUtil.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/io/src/Word.cpp b/codes/io/src/Word.cpp index 2414d3b7b..a92db1d75 100644 --- a/codes/io/src/Word.cpp +++ b/codes/io/src/Word.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/lhs/include/Lhs.h b/codes/lhs/include/Lhs.h index 1d3c12354..c86cef369 100644 --- a/codes/lhs/include/Lhs.h +++ b/codes/lhs/include/Lhs.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/lhs/include/LhsTaskReg.h b/codes/lhs/include/LhsTaskReg.h index e8a2c62b9..4bbca3445 100644 --- a/codes/lhs/include/LhsTaskReg.h +++ b/codes/lhs/include/LhsTaskReg.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/lhs/src/Lhs.cpp b/codes/lhs/src/Lhs.cpp index 5a56f7c15..6d4b11168 100644 --- a/codes/lhs/src/Lhs.cpp +++ b/codes/lhs/src/Lhs.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/lhs/src/LhsTaskReg.cpp b/codes/lhs/src/LhsTaskReg.cpp index 0b3b6f203..cc5d854e9 100644 --- a/codes/lhs/src/LhsTaskReg.cpp +++ b/codes/lhs/src/LhsTaskReg.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/main/include/SimpleSimu.h b/codes/main/include/SimpleSimu.h index da6555244..1d4e0ce24 100644 --- a/codes/main/include/SimpleSimu.h +++ b/codes/main/include/SimpleSimu.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/main/include/SimuImp.h b/codes/main/include/SimuImp.h index 32d2024af..55211ca13 100644 --- a/codes/main/include/SimuImp.h +++ b/codes/main/include/SimuImp.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/main/include/Simulation.h b/codes/main/include/Simulation.h index f3fc28515..2e8c62f8e 100644 --- a/codes/main/include/Simulation.h +++ b/codes/main/include/Simulation.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/main/src/OneFlow.cpp b/codes/main/src/OneFlow.cpp index dc7c0e255..e4906d8fc 100644 --- a/codes/main/src/OneFlow.cpp +++ b/codes/main/src/OneFlow.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/main/src/SimpleSimu.cpp b/codes/main/src/SimpleSimu.cpp index e0682c55c..ebb99af3c 100644 --- a/codes/main/src/SimpleSimu.cpp +++ b/codes/main/src/SimpleSimu.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/main/src/SimuImp.cpp b/codes/main/src/SimuImp.cpp index fb8dd48aa..3c9ed6163 100644 --- a/codes/main/src/SimuImp.cpp +++ b/codes/main/src/SimuImp.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/main/src/Simulation.cpp b/codes/main/src/Simulation.cpp index 68b5e1a52..645f065f5 100644 --- a/codes/main/src/Simulation.cpp +++ b/codes/main/src/Simulation.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/math/include/HXMath.h b/codes/math/include/HXMath.h index 1057a6300..27b7b33cd 100644 --- a/codes/math/include/HXMath.h +++ b/codes/math/include/HXMath.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/math/include/HXMathExt.h b/codes/math/include/HXMathExt.h index 33baf9c1e..ca1e4ffa9 100644 --- a/codes/math/include/HXMathExt.h +++ b/codes/math/include/HXMathExt.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/math/src/HXMath.cpp b/codes/math/src/HXMath.cpp index a3c6255e0..2adad38e8 100644 --- a/codes/math/src/HXMath.cpp +++ b/codes/math/src/HXMath.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/math/src/HXMathExt.cpp b/codes/math/src/HXMathExt.cpp index 6b30b9ef6..e0c6c1189 100644 --- a/codes/math/src/HXMathExt.cpp +++ b/codes/math/src/HXMathExt.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/model/include/FlowModel.h b/codes/model/include/FlowModel.h index ea29f68e7..324679559 100644 --- a/codes/model/include/FlowModel.h +++ b/codes/model/include/FlowModel.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/model/include/Sutherland.h b/codes/model/include/Sutherland.h index 3761adbf9..f73325ce1 100644 --- a/codes/model/include/Sutherland.h +++ b/codes/model/include/Sutherland.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/model/src/FlowModel.cpp b/codes/model/src/FlowModel.cpp index dd4931fbd..79bd61852 100644 --- a/codes/model/src/FlowModel.cpp +++ b/codes/model/src/FlowModel.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/model/src/Sutherland.cpp b/codes/model/src/Sutherland.cpp index 6446fc2bf..1232af2cd 100644 --- a/codes/model/src/Sutherland.cpp +++ b/codes/model/src/Sutherland.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/multiarray/include/Memop.h b/codes/multiarray/include/Memop.h index a8e9d99fd..c4cdda01e 100644 --- a/codes/multiarray/include/Memop.h +++ b/codes/multiarray/include/Memop.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/multiarray/include/Multiarray.h b/codes/multiarray/include/Multiarray.h index f88a1d9e2..bf5b45d33 100644 --- a/codes/multiarray/include/Multiarray.h +++ b/codes/multiarray/include/Multiarray.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/multiarray/include/Range.h b/codes/multiarray/include/Range.h index 646a14369..130624b48 100644 --- a/codes/multiarray/include/Range.h +++ b/codes/multiarray/include/Range.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/multiarray/src/Memop.cpp b/codes/multiarray/src/Memop.cpp index 5734b7f72..deb1afd06 100644 --- a/codes/multiarray/src/Memop.cpp +++ b/codes/multiarray/src/Memop.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/multiarray/src/Multiarray.cpp b/codes/multiarray/src/Multiarray.cpp index 5734b7f72..deb1afd06 100644 --- a/codes/multiarray/src/Multiarray.cpp +++ b/codes/multiarray/src/Multiarray.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/multiarray/src/Range.cpp b/codes/multiarray/src/Range.cpp index 325d14d21..56df0c783 100644 --- a/codes/multiarray/src/Range.cpp +++ b/codes/multiarray/src/Range.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/multigrid/include/BgField.h b/codes/multigrid/include/BgField.h index 8e27b9de9..bef254043 100644 --- a/codes/multigrid/include/BgField.h +++ b/codes/multigrid/include/BgField.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/multigrid/include/BgGrid.h b/codes/multigrid/include/BgGrid.h index 35a1dab4d..3e8bc1bc8 100644 --- a/codes/multigrid/include/BgGrid.h +++ b/codes/multigrid/include/BgGrid.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/multigrid/include/Multigrid.h b/codes/multigrid/include/Multigrid.h index 6608f8602..71ef2579a 100644 --- a/codes/multigrid/include/Multigrid.h +++ b/codes/multigrid/include/Multigrid.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/multigrid/include/MultigridTaskReg.h b/codes/multigrid/include/MultigridTaskReg.h index 7688dea75..1c7981df2 100644 --- a/codes/multigrid/include/MultigridTaskReg.h +++ b/codes/multigrid/include/MultigridTaskReg.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/multigrid/src/BgField.cpp b/codes/multigrid/src/BgField.cpp index a83816a4c..bb55ec659 100644 --- a/codes/multigrid/src/BgField.cpp +++ b/codes/multigrid/src/BgField.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/multigrid/src/BgGrid.cpp b/codes/multigrid/src/BgGrid.cpp index 919343b85..8aea18945 100644 --- a/codes/multigrid/src/BgGrid.cpp +++ b/codes/multigrid/src/BgGrid.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/multigrid/src/Multigrid.cpp b/codes/multigrid/src/Multigrid.cpp index 5700f5010..687fa6e5a 100644 --- a/codes/multigrid/src/Multigrid.cpp +++ b/codes/multigrid/src/Multigrid.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/multigrid/src/MultigridTaskReg.cpp b/codes/multigrid/src/MultigridTaskReg.cpp index ce0029a3c..49539aded 100644 --- a/codes/multigrid/src/MultigridTaskReg.cpp +++ b/codes/multigrid/src/MultigridTaskReg.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/include/NsBcSolver.h b/codes/ns/include/NsBcSolver.h index 53ce7ba37..1cb3808c4 100644 --- a/codes/ns/include/NsBcSolver.h +++ b/codes/ns/include/NsBcSolver.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/include/NsCom.h b/codes/ns/include/NsCom.h index 949101e15..feed437e3 100644 --- a/codes/ns/include/NsCom.h +++ b/codes/ns/include/NsCom.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/include/NsCtrl.h b/codes/ns/include/NsCtrl.h index 12d442377..cbaf92d70 100644 --- a/codes/ns/include/NsCtrl.h +++ b/codes/ns/include/NsCtrl.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/include/NsIdx.h b/codes/ns/include/NsIdx.h index c6d16b959..bd5e26709 100644 --- a/codes/ns/include/NsIdx.h +++ b/codes/ns/include/NsIdx.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/include/NsInvFlux.h b/codes/ns/include/NsInvFlux.h index d068a801d..c1c1b7392 100644 --- a/codes/ns/include/NsInvFlux.h +++ b/codes/ns/include/NsInvFlux.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/include/NsLusgs.h b/codes/ns/include/NsLusgs.h index 277dd5417..255f3ff03 100644 --- a/codes/ns/include/NsLusgs.h +++ b/codes/ns/include/NsLusgs.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/include/NsRestart.h b/codes/ns/include/NsRestart.h index eb7740b91..bf0345c94 100644 --- a/codes/ns/include/NsRestart.h +++ b/codes/ns/include/NsRestart.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/include/NsRhs.h b/codes/ns/include/NsRhs.h index 044f30dec..508e071e0 100644 --- a/codes/ns/include/NsRhs.h +++ b/codes/ns/include/NsRhs.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/include/NsSolver.h b/codes/ns/include/NsSolver.h index 79f68741b..56c0c7aa3 100644 --- a/codes/ns/include/NsSolver.h +++ b/codes/ns/include/NsSolver.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/include/NsSolverImp.h b/codes/ns/include/NsSolverImp.h index 932d481e7..df9efc903 100644 --- a/codes/ns/include/NsSolverImp.h +++ b/codes/ns/include/NsSolverImp.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/include/NsSpectrum.h b/codes/ns/include/NsSpectrum.h index b8ca158d2..702109125 100644 --- a/codes/ns/include/NsSpectrum.h +++ b/codes/ns/include/NsSpectrum.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/include/NsUnsteady.h b/codes/ns/include/NsUnsteady.h index 4cc662054..a609b5f1c 100644 --- a/codes/ns/include/NsUnsteady.h +++ b/codes/ns/include/NsUnsteady.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/include/NsUpdate.h b/codes/ns/include/NsUpdate.h index 8fa26d0c9..5353f720f 100644 --- a/codes/ns/include/NsUpdate.h +++ b/codes/ns/include/NsUpdate.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/include/NsVisFlux.h b/codes/ns/include/NsVisFlux.h index e6277f64d..fccd7ab82 100644 --- a/codes/ns/include/NsVisFlux.h +++ b/codes/ns/include/NsVisFlux.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/include/TimeStep.h b/codes/ns/include/TimeStep.h index d76404f08..32a6a6c7e 100644 --- a/codes/ns/include/TimeStep.h +++ b/codes/ns/include/TimeStep.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/src/NsBcSolver.cpp b/codes/ns/src/NsBcSolver.cpp index 53b2ff0bc..1b1e77ae0 100644 --- a/codes/ns/src/NsBcSolver.cpp +++ b/codes/ns/src/NsBcSolver.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/src/NsCom.cpp b/codes/ns/src/NsCom.cpp index 4ec2bb1f2..9078d82fc 100644 --- a/codes/ns/src/NsCom.cpp +++ b/codes/ns/src/NsCom.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/src/NsCtrl.cpp b/codes/ns/src/NsCtrl.cpp index 43c3cefcd..de757d4e6 100644 --- a/codes/ns/src/NsCtrl.cpp +++ b/codes/ns/src/NsCtrl.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/src/NsInvFlux.cpp b/codes/ns/src/NsInvFlux.cpp index ddd8b25f8..8ab75d9d3 100644 --- a/codes/ns/src/NsInvFlux.cpp +++ b/codes/ns/src/NsInvFlux.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/src/NsLusgs.cpp b/codes/ns/src/NsLusgs.cpp index 2ec1ba608..d893d22d4 100644 --- a/codes/ns/src/NsLusgs.cpp +++ b/codes/ns/src/NsLusgs.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/src/NsRestart.cpp b/codes/ns/src/NsRestart.cpp index 4e2e632fd..3eacf4f96 100644 --- a/codes/ns/src/NsRestart.cpp +++ b/codes/ns/src/NsRestart.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/src/NsRhs.cpp b/codes/ns/src/NsRhs.cpp index 05133543b..b582d187b 100644 --- a/codes/ns/src/NsRhs.cpp +++ b/codes/ns/src/NsRhs.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/src/NsSolver.cpp b/codes/ns/src/NsSolver.cpp index 52578a668..5c7eced3f 100644 --- a/codes/ns/src/NsSolver.cpp +++ b/codes/ns/src/NsSolver.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/src/NsSolverImp.cpp b/codes/ns/src/NsSolverImp.cpp index d0c94e01b..db2d8d309 100644 --- a/codes/ns/src/NsSolverImp.cpp +++ b/codes/ns/src/NsSolverImp.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/src/NsSpectrum.cpp b/codes/ns/src/NsSpectrum.cpp index 071f6b77e..c9e3615aa 100644 --- a/codes/ns/src/NsSpectrum.cpp +++ b/codes/ns/src/NsSpectrum.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/src/NsUnsteady.cpp b/codes/ns/src/NsUnsteady.cpp index c5b32d19a..3aeb976f2 100644 --- a/codes/ns/src/NsUnsteady.cpp +++ b/codes/ns/src/NsUnsteady.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/src/NsUpdate.cpp b/codes/ns/src/NsUpdate.cpp index f19e37d12..8f83b322c 100644 --- a/codes/ns/src/NsUpdate.cpp +++ b/codes/ns/src/NsUpdate.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/src/NsVisFlux.cpp b/codes/ns/src/NsVisFlux.cpp index e7a85d8ab..47ce072cd 100644 --- a/codes/ns/src/NsVisFlux.cpp +++ b/codes/ns/src/NsVisFlux.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/ns/src/TimeStep.cpp b/codes/ns/src/TimeStep.cpp index a5e353809..67a409d99 100644 --- a/codes/ns/src/TimeStep.cpp +++ b/codes/ns/src/TimeStep.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/parallel/include/BasicParallel.h b/codes/parallel/include/BasicParallel.h index d1eddc541..ddf6b8cbd 100644 --- a/codes/parallel/include/BasicParallel.h +++ b/codes/parallel/include/BasicParallel.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/parallel/include/Parallel.h b/codes/parallel/include/Parallel.h index 79bac8d2b..bcf78a2b6 100644 --- a/codes/parallel/include/Parallel.h +++ b/codes/parallel/include/Parallel.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/parallel/src/BasicParallel.cpp b/codes/parallel/src/BasicParallel.cpp index 749af76db..9656ecf61 100644 --- a/codes/parallel/src/BasicParallel.cpp +++ b/codes/parallel/src/BasicParallel.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/parallel/src/Parallel.cpp b/codes/parallel/src/Parallel.cpp index 2ad2516f4..3c70c690d 100644 --- a/codes/parallel/src/Parallel.cpp +++ b/codes/parallel/src/Parallel.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/partition/include/Partition.h b/codes/partition/include/Partition.h index fcdba41f4..88d2ed9dd 100644 --- a/codes/partition/include/Partition.h +++ b/codes/partition/include/Partition.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/partition/include/SmartGrid.h b/codes/partition/include/SmartGrid.h index e621b4564..7731116d5 100644 --- a/codes/partition/include/SmartGrid.h +++ b/codes/partition/include/SmartGrid.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/partition/src/Partition.cpp b/codes/partition/src/Partition.cpp index b5ea45fe6..4095cce02 100644 --- a/codes/partition/src/Partition.cpp +++ b/codes/partition/src/Partition.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/partition/src/SmartGrid.cpp b/codes/partition/src/SmartGrid.cpp index c150a9ff5..cb749a3aa 100644 --- a/codes/partition/src/SmartGrid.cpp +++ b/codes/partition/src/SmartGrid.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/physics/include/Atmosphere.h b/codes/physics/include/Atmosphere.h index 2f0ecf169..3e81414d1 100644 --- a/codes/physics/include/Atmosphere.h +++ b/codes/physics/include/Atmosphere.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/physics/src/Atmosphere.cpp b/codes/physics/src/Atmosphere.cpp index 6a8d56a42..5b679bceb 100644 --- a/codes/physics/src/Atmosphere.cpp +++ b/codes/physics/src/Atmosphere.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/postprocess/include/PostProcess.h b/codes/postprocess/include/PostProcess.h index 9b4fe4d48..0f82aeb3f 100644 --- a/codes/postprocess/include/PostProcess.h +++ b/codes/postprocess/include/PostProcess.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/postprocess/src/PostProcess.cpp b/codes/postprocess/src/PostProcess.cpp index e4c6c27a4..b3aea6605 100644 --- a/codes/postprocess/src/PostProcess.cpp +++ b/codes/postprocess/src/PostProcess.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/project/include/Configure.h b/codes/project/include/Configure.h index 7f03c82f1..5a9da9cb1 100644 --- a/codes/project/include/Configure.h +++ b/codes/project/include/Configure.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/project/include/FileUtil.h b/codes/project/include/FileUtil.h index c382d87e4..37b385c47 100644 --- a/codes/project/include/FileUtil.h +++ b/codes/project/include/FileUtil.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/project/include/OStream.h b/codes/project/include/OStream.h index fb26de113..d74774555 100644 --- a/codes/project/include/OStream.h +++ b/codes/project/include/OStream.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/project/include/Prj.h b/codes/project/include/Prj.h index a9dadc01c..830f2b0c6 100644 --- a/codes/project/include/Prj.h +++ b/codes/project/include/Prj.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/project/include/Stop.h b/codes/project/include/Stop.h index 90294eb01..822e6c307 100644 --- a/codes/project/include/Stop.h +++ b/codes/project/include/Stop.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/project/src/FileUtil.cpp b/codes/project/src/FileUtil.cpp index 8dcaca470..f34add3ca 100644 --- a/codes/project/src/FileUtil.cpp +++ b/codes/project/src/FileUtil.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/project/src/OStream.cpp b/codes/project/src/OStream.cpp index 7bfd2bb3e..062d12c00 100644 --- a/codes/project/src/OStream.cpp +++ b/codes/project/src/OStream.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/project/src/Prj.cpp b/codes/project/src/Prj.cpp index 21c49f1e0..09495a1a3 100644 --- a/codes/project/src/Prj.cpp +++ b/codes/project/src/Prj.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/project/src/Stop.cpp b/codes/project/src/Stop.cpp index 2f143359a..42b6b6277 100644 --- a/codes/project/src/Stop.cpp +++ b/codes/project/src/Stop.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/register/include/HXClone.h b/codes/register/include/HXClone.h index ebae759a2..1030365d0 100644 --- a/codes/register/include/HXClone.h +++ b/codes/register/include/HXClone.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/register/include/Message.h b/codes/register/include/Message.h index 83dde89bc..0d5bd353c 100644 --- a/codes/register/include/Message.h +++ b/codes/register/include/Message.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/register/include/MsgMapImp.h b/codes/register/include/MsgMapImp.h index 2e2110e6b..fdc204da2 100644 --- a/codes/register/include/MsgMapImp.h +++ b/codes/register/include/MsgMapImp.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/register/include/Register.h b/codes/register/include/Register.h index 13398d1e0..ff01cbf2e 100644 --- a/codes/register/include/Register.h +++ b/codes/register/include/Register.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/register/include/RegisterUtil.h b/codes/register/include/RegisterUtil.h index f00025fd2..176729dae 100644 --- a/codes/register/include/RegisterUtil.h +++ b/codes/register/include/RegisterUtil.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/register/src/HXClone.cpp b/codes/register/src/HXClone.cpp index 8aae6bea8..5ee088314 100644 --- a/codes/register/src/HXClone.cpp +++ b/codes/register/src/HXClone.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/register/src/Message.cpp b/codes/register/src/Message.cpp index 351917683..1bc7f23ea 100644 --- a/codes/register/src/Message.cpp +++ b/codes/register/src/Message.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/register/src/MsgMapImp.cpp b/codes/register/src/MsgMapImp.cpp index a8711bc22..2e254ca3d 100644 --- a/codes/register/src/MsgMapImp.cpp +++ b/codes/register/src/MsgMapImp.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/register/src/Register.cpp b/codes/register/src/Register.cpp index 4c78d2c33..297eb3ae1 100644 --- a/codes/register/src/Register.cpp +++ b/codes/register/src/Register.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/register/src/RegisterUtil.cpp b/codes/register/src/RegisterUtil.cpp index c030d666d..8a3933521 100644 --- a/codes/register/src/RegisterUtil.cpp +++ b/codes/register/src/RegisterUtil.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/residual/include/Residual.h b/codes/residual/include/Residual.h index 384ee91ca..5c4016592 100644 --- a/codes/residual/include/Residual.h +++ b/codes/residual/include/Residual.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/residual/include/ResidualTask.h b/codes/residual/include/ResidualTask.h index 186675ec1..2f1d91bcf 100644 --- a/codes/residual/include/ResidualTask.h +++ b/codes/residual/include/ResidualTask.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/residual/include/ResidualTaskReg.h b/codes/residual/include/ResidualTaskReg.h index 0601b3066..6e7d681ff 100644 --- a/codes/residual/include/ResidualTaskReg.h +++ b/codes/residual/include/ResidualTaskReg.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/residual/src/Residual.cpp b/codes/residual/src/Residual.cpp index ba5235264..bfbba076b 100644 --- a/codes/residual/src/Residual.cpp +++ b/codes/residual/src/Residual.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/residual/src/ResidualTask.cpp b/codes/residual/src/ResidualTask.cpp index c4d02f4d1..a706fd373 100644 --- a/codes/residual/src/ResidualTask.cpp +++ b/codes/residual/src/ResidualTask.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/residual/src/ResidualTaskReg.cpp b/codes/residual/src/ResidualTaskReg.cpp index 68b991e94..777e89f6e 100644 --- a/codes/residual/src/ResidualTaskReg.cpp +++ b/codes/residual/src/ResidualTaskReg.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/restart/include/Restart.h b/codes/restart/include/Restart.h index 7fad4c6b6..4da5c6369 100644 --- a/codes/restart/include/Restart.h +++ b/codes/restart/include/Restart.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/restart/include/RestartTaskReg.h b/codes/restart/include/RestartTaskReg.h index fbbe34262..94be38390 100644 --- a/codes/restart/include/RestartTaskReg.h +++ b/codes/restart/include/RestartTaskReg.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/restart/src/Restart.cpp b/codes/restart/src/Restart.cpp index 504a78f15..20c19f462 100644 --- a/codes/restart/src/Restart.cpp +++ b/codes/restart/src/Restart.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/restart/src/RestartTaskReg.cpp b/codes/restart/src/RestartTaskReg.cpp index 5b5d7845c..9f9845f86 100644 --- a/codes/restart/src/RestartTaskReg.cpp +++ b/codes/restart/src/RestartTaskReg.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/include/Blasius.h b/codes/scalar/include/Blasius.h index 5614970f3..3026e62f5 100644 --- a/codes/scalar/include/Blasius.h +++ b/codes/scalar/include/Blasius.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/include/FieldPara.h b/codes/scalar/include/FieldPara.h index 1b75866f0..c76298f5d 100644 --- a/codes/scalar/include/FieldPara.h +++ b/codes/scalar/include/FieldPara.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/include/FieldSolver.h b/codes/scalar/include/FieldSolver.h index 33663fa96..32a056947 100644 --- a/codes/scalar/include/FieldSolver.h +++ b/codes/scalar/include/FieldSolver.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/include/FieldSolverBasic.h b/codes/scalar/include/FieldSolverBasic.h index 1ceab7f2a..775ea4283 100644 --- a/codes/scalar/include/FieldSolverBasic.h +++ b/codes/scalar/include/FieldSolverBasic.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/include/FieldSolverCuda.h b/codes/scalar/include/FieldSolverCuda.h index b78c481d0..28a9eb7d1 100644 --- a/codes/scalar/include/FieldSolverCuda.h +++ b/codes/scalar/include/FieldSolverCuda.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/include/FieldSolverOpenMP.h b/codes/scalar/include/FieldSolverOpenMP.h index 7a052612f..43f4e0440 100644 --- a/codes/scalar/include/FieldSolverOpenMP.h +++ b/codes/scalar/include/FieldSolverOpenMP.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/include/MetisGrid.h b/codes/scalar/include/MetisGrid.h index e8bd90ee4..5a3100f8f 100644 --- a/codes/scalar/include/MetisGrid.h +++ b/codes/scalar/include/MetisGrid.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/include/Numpy.h b/codes/scalar/include/Numpy.h index f9e5d03e5..c9f50d70d 100644 --- a/codes/scalar/include/Numpy.h +++ b/codes/scalar/include/Numpy.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/include/Scalar.h b/codes/scalar/include/Scalar.h index e0716608c..bb91a6148 100644 --- a/codes/scalar/include/Scalar.h +++ b/codes/scalar/include/Scalar.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/include/ScalarAlloc.h b/codes/scalar/include/ScalarAlloc.h index e48a71dbd..7ba2fcc79 100644 --- a/codes/scalar/include/ScalarAlloc.h +++ b/codes/scalar/include/ScalarAlloc.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/include/ScalarCgns.h b/codes/scalar/include/ScalarCgns.h index 921dd934a..9c1852916 100644 --- a/codes/scalar/include/ScalarCgns.h +++ b/codes/scalar/include/ScalarCgns.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/include/ScalarDataIO.h b/codes/scalar/include/ScalarDataIO.h index 137947b45..18bc0ff37 100644 --- a/codes/scalar/include/ScalarDataIO.h +++ b/codes/scalar/include/ScalarDataIO.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/include/ScalarField.h b/codes/scalar/include/ScalarField.h index a15477fb5..6fcbd4751 100644 --- a/codes/scalar/include/ScalarField.h +++ b/codes/scalar/include/ScalarField.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/include/ScalarFieldRecord.h b/codes/scalar/include/ScalarFieldRecord.h index 9908baeab..be5925f4c 100644 --- a/codes/scalar/include/ScalarFieldRecord.h +++ b/codes/scalar/include/ScalarFieldRecord.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/include/ScalarGrid.h b/codes/scalar/include/ScalarGrid.h index ce2f25d7f..2534c995c 100644 --- a/codes/scalar/include/ScalarGrid.h +++ b/codes/scalar/include/ScalarGrid.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/include/ScalarIFace.h b/codes/scalar/include/ScalarIFace.h index dd15cd456..ee789f82b 100644 --- a/codes/scalar/include/ScalarIFace.h +++ b/codes/scalar/include/ScalarIFace.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/include/ScalarMetis.h b/codes/scalar/include/ScalarMetis.h index 61149bc5e..c76dda627 100644 --- a/codes/scalar/include/ScalarMetis.h +++ b/codes/scalar/include/ScalarMetis.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/include/ScalarOrder.h b/codes/scalar/include/ScalarOrder.h index cbae1d912..b8a4e5e82 100644 --- a/codes/scalar/include/ScalarOrder.h +++ b/codes/scalar/include/ScalarOrder.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/include/ScalarSolver.h b/codes/scalar/include/ScalarSolver.h index 2fbab4278..f852b534d 100644 --- a/codes/scalar/include/ScalarSolver.h +++ b/codes/scalar/include/ScalarSolver.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/include/ScalarZone.h b/codes/scalar/include/ScalarZone.h index 473180d6c..919b5eb19 100644 --- a/codes/scalar/include/ScalarZone.h +++ b/codes/scalar/include/ScalarZone.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/include/SolverDevice.h b/codes/scalar/include/SolverDevice.h index 5b09b2754..b4c70e0be 100644 --- a/codes/scalar/include/SolverDevice.h +++ b/codes/scalar/include/SolverDevice.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/src/Blasius.cpp b/codes/scalar/src/Blasius.cpp index 7f489d500..a16a4fcdd 100644 --- a/codes/scalar/src/Blasius.cpp +++ b/codes/scalar/src/Blasius.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/src/FieldPara.cpp b/codes/scalar/src/FieldPara.cpp index 2852f6d5b..1c40ddcbf 100644 --- a/codes/scalar/src/FieldPara.cpp +++ b/codes/scalar/src/FieldPara.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/src/FieldSolver.cpp b/codes/scalar/src/FieldSolver.cpp index 2571e20a0..539f559ce 100644 --- a/codes/scalar/src/FieldSolver.cpp +++ b/codes/scalar/src/FieldSolver.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/src/FieldSolverBasic.cpp b/codes/scalar/src/FieldSolverBasic.cpp index 306b08cb5..cbc457514 100644 --- a/codes/scalar/src/FieldSolverBasic.cpp +++ b/codes/scalar/src/FieldSolverBasic.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/src/FieldSolverCuda.cpp b/codes/scalar/src/FieldSolverCuda.cpp index d02582be7..e6e773cbc 100644 --- a/codes/scalar/src/FieldSolverCuda.cpp +++ b/codes/scalar/src/FieldSolverCuda.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/src/FieldSolverOpenMP.cpp b/codes/scalar/src/FieldSolverOpenMP.cpp index 7be3f4ad2..5e69667b9 100644 --- a/codes/scalar/src/FieldSolverOpenMP.cpp +++ b/codes/scalar/src/FieldSolverOpenMP.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/src/MetisGrid.cpp b/codes/scalar/src/MetisGrid.cpp index 5c3a1476d..097cef570 100644 --- a/codes/scalar/src/MetisGrid.cpp +++ b/codes/scalar/src/MetisGrid.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/src/Numpy.cpp b/codes/scalar/src/Numpy.cpp index 37cf9d2ed..ff9d0cf92 100644 --- a/codes/scalar/src/Numpy.cpp +++ b/codes/scalar/src/Numpy.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/src/Scalar.cpp b/codes/scalar/src/Scalar.cpp index d4868f1d8..2c773a181 100644 --- a/codes/scalar/src/Scalar.cpp +++ b/codes/scalar/src/Scalar.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/src/ScalarAlloc.cpp b/codes/scalar/src/ScalarAlloc.cpp index bb32681dc..735a15108 100644 --- a/codes/scalar/src/ScalarAlloc.cpp +++ b/codes/scalar/src/ScalarAlloc.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/src/ScalarCgns.cpp b/codes/scalar/src/ScalarCgns.cpp index 84475991b..17e513637 100644 --- a/codes/scalar/src/ScalarCgns.cpp +++ b/codes/scalar/src/ScalarCgns.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/src/ScalarDataIO.cpp b/codes/scalar/src/ScalarDataIO.cpp index 0023ad750..f656a6f42 100644 --- a/codes/scalar/src/ScalarDataIO.cpp +++ b/codes/scalar/src/ScalarDataIO.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/src/ScalarField.cpp b/codes/scalar/src/ScalarField.cpp index 455dc1fa4..c3f64bf98 100644 --- a/codes/scalar/src/ScalarField.cpp +++ b/codes/scalar/src/ScalarField.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/src/ScalarFieldRecord.cpp b/codes/scalar/src/ScalarFieldRecord.cpp index 949fc30ab..0073ab891 100644 --- a/codes/scalar/src/ScalarFieldRecord.cpp +++ b/codes/scalar/src/ScalarFieldRecord.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/src/ScalarGrid.cpp b/codes/scalar/src/ScalarGrid.cpp index 0be346bc1..8b62ab91f 100644 --- a/codes/scalar/src/ScalarGrid.cpp +++ b/codes/scalar/src/ScalarGrid.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/src/ScalarIFace.cpp b/codes/scalar/src/ScalarIFace.cpp index 8f446da1a..c943ccd54 100644 --- a/codes/scalar/src/ScalarIFace.cpp +++ b/codes/scalar/src/ScalarIFace.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/src/ScalarMetis.cpp b/codes/scalar/src/ScalarMetis.cpp index a7dbf7a8c..7e5415d2e 100644 --- a/codes/scalar/src/ScalarMetis.cpp +++ b/codes/scalar/src/ScalarMetis.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/src/ScalarOrder.cpp b/codes/scalar/src/ScalarOrder.cpp index ce013218a..b1d903834 100644 --- a/codes/scalar/src/ScalarOrder.cpp +++ b/codes/scalar/src/ScalarOrder.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/src/ScalarSolver.cpp b/codes/scalar/src/ScalarSolver.cpp index 6f61720b2..080d73140 100644 --- a/codes/scalar/src/ScalarSolver.cpp +++ b/codes/scalar/src/ScalarSolver.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/scalar/src/ScalarZone.cpp b/codes/scalar/src/ScalarZone.cpp index 6f0ac08e5..1996d5a13 100644 --- a/codes/scalar/src/ScalarZone.cpp +++ b/codes/scalar/src/ScalarZone.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/solver/include/BcData.h b/codes/solver/include/BcData.h index 797849eda..0c6bb98b5 100644 --- a/codes/solver/include/BcData.h +++ b/codes/solver/include/BcData.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/solver/include/BcSolver.h b/codes/solver/include/BcSolver.h index 00c6b816b..bfbec6b0c 100644 --- a/codes/solver/include/BcSolver.h +++ b/codes/solver/include/BcSolver.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/solver/include/Converge.h b/codes/solver/include/Converge.h index b99e5f3be..8caec5e86 100644 --- a/codes/solver/include/Converge.h +++ b/codes/solver/include/Converge.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/solver/include/FieldTaskReg.h b/codes/solver/include/FieldTaskReg.h index 03894d5af..56cfa213a 100644 --- a/codes/solver/include/FieldTaskReg.h +++ b/codes/solver/include/FieldTaskReg.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/solver/include/FieldWrap.h b/codes/solver/include/FieldWrap.h index 9cedd528f..644056438 100644 --- a/codes/solver/include/FieldWrap.h +++ b/codes/solver/include/FieldWrap.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/solver/include/Rhs.h b/codes/solver/include/Rhs.h index 20a499076..79a8d409e 100644 --- a/codes/solver/include/Rhs.h +++ b/codes/solver/include/Rhs.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/solver/include/Solver.h b/codes/solver/include/Solver.h index 0cb214a30..88d228b17 100644 --- a/codes/solver/include/Solver.h +++ b/codes/solver/include/Solver.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/solver/include/SolverImp.h b/codes/solver/include/SolverImp.h index ae9615727..70299e428 100644 --- a/codes/solver/include/SolverImp.h +++ b/codes/solver/include/SolverImp.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/solver/include/SolverInfo.h b/codes/solver/include/SolverInfo.h index cc7b5a6d8..9e5cb84a3 100644 --- a/codes/solver/include/SolverInfo.h +++ b/codes/solver/include/SolverInfo.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/solver/include/SolverMap.h b/codes/solver/include/SolverMap.h index 7b4f93062..fa8289284 100644 --- a/codes/solver/include/SolverMap.h +++ b/codes/solver/include/SolverMap.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/solver/include/SolverRegData.h b/codes/solver/include/SolverRegData.h index 5d727e2cb..c1a40d56c 100644 --- a/codes/solver/include/SolverRegData.h +++ b/codes/solver/include/SolverRegData.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/solver/include/SolverState.h b/codes/solver/include/SolverState.h index 8145ab984..2504b3b6d 100644 --- a/codes/solver/include/SolverState.h +++ b/codes/solver/include/SolverState.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/solver/include/Stress.h b/codes/solver/include/Stress.h index 01bc75ca4..0d2ef6d88 100644 --- a/codes/solver/include/Stress.h +++ b/codes/solver/include/Stress.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/solver/include/TimeIntegral.h b/codes/solver/include/TimeIntegral.h index 32965e681..40c77ed0f 100644 --- a/codes/solver/include/TimeIntegral.h +++ b/codes/solver/include/TimeIntegral.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/solver/src/BcData.cpp b/codes/solver/src/BcData.cpp index 9814c05a8..6688cd04d 100644 --- a/codes/solver/src/BcData.cpp +++ b/codes/solver/src/BcData.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/solver/src/BcSolver.cpp b/codes/solver/src/BcSolver.cpp index f401e81f8..8dbdc0b26 100644 --- a/codes/solver/src/BcSolver.cpp +++ b/codes/solver/src/BcSolver.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/solver/src/Converge.cpp b/codes/solver/src/Converge.cpp index 1461cf098..7329cbb26 100644 --- a/codes/solver/src/Converge.cpp +++ b/codes/solver/src/Converge.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/solver/src/FieldTaskReg.cpp b/codes/solver/src/FieldTaskReg.cpp index 42682567e..616baf564 100644 --- a/codes/solver/src/FieldTaskReg.cpp +++ b/codes/solver/src/FieldTaskReg.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/solver/src/FieldWrap.cpp b/codes/solver/src/FieldWrap.cpp index 8ebc70b27..389bca413 100644 --- a/codes/solver/src/FieldWrap.cpp +++ b/codes/solver/src/FieldWrap.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/solver/src/Rhs.cpp b/codes/solver/src/Rhs.cpp index 85b186bd0..cd65cf0b2 100644 --- a/codes/solver/src/Rhs.cpp +++ b/codes/solver/src/Rhs.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/solver/src/Solver.cpp b/codes/solver/src/Solver.cpp index bc2954d97..9c1d5fe09 100644 --- a/codes/solver/src/Solver.cpp +++ b/codes/solver/src/Solver.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/solver/src/SolverImp.cpp b/codes/solver/src/SolverImp.cpp index 56f9a1efa..1b7b9d6b6 100644 --- a/codes/solver/src/SolverImp.cpp +++ b/codes/solver/src/SolverImp.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/solver/src/SolverInfo.cpp b/codes/solver/src/SolverInfo.cpp index f353ad597..d71c47e1c 100644 --- a/codes/solver/src/SolverInfo.cpp +++ b/codes/solver/src/SolverInfo.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/solver/src/SolverMap.cpp b/codes/solver/src/SolverMap.cpp index 0c6244133..e8a5c87ff 100644 --- a/codes/solver/src/SolverMap.cpp +++ b/codes/solver/src/SolverMap.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/solver/src/SolverRegData.cpp b/codes/solver/src/SolverRegData.cpp index 9417fe54d..6368385a5 100644 --- a/codes/solver/src/SolverRegData.cpp +++ b/codes/solver/src/SolverRegData.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/solver/src/SolverState.cpp b/codes/solver/src/SolverState.cpp index c0df21029..ac9d96f0c 100644 --- a/codes/solver/src/SolverState.cpp +++ b/codes/solver/src/SolverState.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/solver/src/Stress.cpp b/codes/solver/src/Stress.cpp index 38ff22999..5bb114e8b 100644 --- a/codes/solver/src/Stress.cpp +++ b/codes/solver/src/Stress.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/solver/src/TimeIntegral.cpp b/codes/solver/src/TimeIntegral.cpp index d46cc2895..ed449907d 100644 --- a/codes/solver/src/TimeIntegral.cpp +++ b/codes/solver/src/TimeIntegral.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/BlkMesh.h b/codes/special/include/BlkMesh.h index 4f892a08b..c899c6aed 100644 --- a/codes/special/include/BlkMesh.h +++ b/codes/special/include/BlkMesh.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/Block2D.h b/codes/special/include/Block2D.h index 20b0a19cd..1a20fe11a 100644 --- a/codes/special/include/Block2D.h +++ b/codes/special/include/Block2D.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/Block3D.h b/codes/special/include/Block3D.h index 43543f214..551437ddc 100644 --- a/codes/special/include/Block3D.h +++ b/codes/special/include/Block3D.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/BlockElem.h b/codes/special/include/BlockElem.h index 44001ab94..ba636bdcb 100644 --- a/codes/special/include/BlockElem.h +++ b/codes/special/include/BlockElem.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/BlockFaceSolver.h b/codes/special/include/BlockFaceSolver.h index a20bdab4b..015cdb4b7 100644 --- a/codes/special/include/BlockFaceSolver.h +++ b/codes/special/include/BlockFaceSolver.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/BlockMachine.h b/codes/special/include/BlockMachine.h index ce48ba6af..c0378d42b 100644 --- a/codes/special/include/BlockMachine.h +++ b/codes/special/include/BlockMachine.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/CalcCoor.h b/codes/special/include/CalcCoor.h index 2b6e529ac..4b0291e55 100644 --- a/codes/special/include/CalcCoor.h +++ b/codes/special/include/CalcCoor.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/Cavity.h b/codes/special/include/Cavity.h index c8b2ec152..3142c0e0c 100644 --- a/codes/special/include/Cavity.h +++ b/codes/special/include/Cavity.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/CgnsTest.h b/codes/special/include/CgnsTest.h index c3c08fb51..5ba740a4f 100644 --- a/codes/special/include/CgnsTest.h +++ b/codes/special/include/CgnsTest.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/CgnsTestTmp.h b/codes/special/include/CgnsTestTmp.h index 17c974c9c..6875588ed 100644 --- a/codes/special/include/CgnsTestTmp.h +++ b/codes/special/include/CgnsTestTmp.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/CircleInfo.h b/codes/special/include/CircleInfo.h index e977c55ef..d4097f51d 100644 --- a/codes/special/include/CircleInfo.h +++ b/codes/special/include/CircleInfo.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/CircleLineMesh.h b/codes/special/include/CircleLineMesh.h index 7babc6006..30b41a72b 100644 --- a/codes/special/include/CircleLineMesh.h +++ b/codes/special/include/CircleLineMesh.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/ClassicGrid.h b/codes/special/include/ClassicGrid.h index 39b045c4e..6b0d140fb 100644 --- a/codes/special/include/ClassicGrid.h +++ b/codes/special/include/ClassicGrid.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/CurveInfo.h b/codes/special/include/CurveInfo.h index 21eeb9b64..bf1360594 100644 --- a/codes/special/include/CurveInfo.h +++ b/codes/special/include/CurveInfo.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/CurveLine.h b/codes/special/include/CurveLine.h index 271c3f672..94544f2e1 100644 --- a/codes/special/include/CurveLine.h +++ b/codes/special/include/CurveLine.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/CurveMachine.h b/codes/special/include/CurveMachine.h index 5e70c4256..78b150e06 100644 --- a/codes/special/include/CurveMachine.h +++ b/codes/special/include/CurveMachine.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/CurveMesh.h b/codes/special/include/CurveMesh.h index 17f73d4e3..545414f84 100644 --- a/codes/special/include/CurveMesh.h +++ b/codes/special/include/CurveMesh.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/Cylinder.h b/codes/special/include/Cylinder.h index 573bcefc1..baed4eff0 100644 --- a/codes/special/include/Cylinder.h +++ b/codes/special/include/Cylinder.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/DomainMachine.h b/codes/special/include/DomainMachine.h index fc19bc15c..61a859919 100644 --- a/codes/special/include/DomainMachine.h +++ b/codes/special/include/DomainMachine.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/GridCreate.h b/codes/special/include/GridCreate.h index ab22a7283..9de245f4e 100644 --- a/codes/special/include/GridCreate.h +++ b/codes/special/include/GridCreate.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/GridMachine.h b/codes/special/include/GridMachine.h index 22eca26d4..8e91de2ad 100644 --- a/codes/special/include/GridMachine.h +++ b/codes/special/include/GridMachine.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/LineInfo.h b/codes/special/include/LineInfo.h index 566c6d398..1ef153876 100644 --- a/codes/special/include/LineInfo.h +++ b/codes/special/include/LineInfo.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/LineMachine.h b/codes/special/include/LineMachine.h index d100b1cbb..11d9631e6 100644 --- a/codes/special/include/LineMachine.h +++ b/codes/special/include/LineMachine.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/LineMesh.h b/codes/special/include/LineMesh.h index 20ffb35d3..4ad593cc8 100644 --- a/codes/special/include/LineMesh.h +++ b/codes/special/include/LineMesh.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/LineMeshImp.h b/codes/special/include/LineMeshImp.h index 26de39890..95e729b51 100644 --- a/codes/special/include/LineMeshImp.h +++ b/codes/special/include/LineMeshImp.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/MDomain.h b/codes/special/include/MDomain.h index bfa54d9f9..ed3ee37cf 100644 --- a/codes/special/include/MDomain.h +++ b/codes/special/include/MDomain.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/MLine.h b/codes/special/include/MLine.h index dd9817935..e8c549f7e 100644 --- a/codes/special/include/MLine.h +++ b/codes/special/include/MLine.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/Mesh.h b/codes/special/include/Mesh.h index 0b97563cb..9e60ed4c7 100644 --- a/codes/special/include/Mesh.h +++ b/codes/special/include/Mesh.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/MpiTest.h b/codes/special/include/MpiTest.h index 1ad15da63..6868d6a31 100644 --- a/codes/special/include/MpiTest.h +++ b/codes/special/include/MpiTest.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/PointMachine.h b/codes/special/include/PointMachine.h index 7503388df..09d5386c1 100644 --- a/codes/special/include/PointMachine.h +++ b/codes/special/include/PointMachine.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/Rae2822.h b/codes/special/include/Rae2822.h index ab4bf8a1b..6e76ea144 100644 --- a/codes/special/include/Rae2822.h +++ b/codes/special/include/Rae2822.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/SDomain.h b/codes/special/include/SDomain.h index 0bc021285..9fbadad81 100644 --- a/codes/special/include/SDomain.h +++ b/codes/special/include/SDomain.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/SegmentCtrl.h b/codes/special/include/SegmentCtrl.h index 58c10a3dc..366540d73 100644 --- a/codes/special/include/SegmentCtrl.h +++ b/codes/special/include/SegmentCtrl.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/SimpleDomain.h b/codes/special/include/SimpleDomain.h index 8e2b3230c..875423fd5 100644 --- a/codes/special/include/SimpleDomain.h +++ b/codes/special/include/SimpleDomain.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/StrBcSetting.h b/codes/special/include/StrBcSetting.h index eee47db9a..79fa36617 100644 --- a/codes/special/include/StrBcSetting.h +++ b/codes/special/include/StrBcSetting.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/include/Transfinite.h b/codes/special/include/Transfinite.h index a807dce84..733345807 100644 --- a/codes/special/include/Transfinite.h +++ b/codes/special/include/Transfinite.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/BlkMesh.cpp b/codes/special/src/BlkMesh.cpp index adb711d3f..9aaeeb27c 100644 --- a/codes/special/src/BlkMesh.cpp +++ b/codes/special/src/BlkMesh.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/Block2D.cpp b/codes/special/src/Block2D.cpp index 163f50375..365e4b8ae 100644 --- a/codes/special/src/Block2D.cpp +++ b/codes/special/src/Block2D.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/Block3D.cpp b/codes/special/src/Block3D.cpp index 59777ecd7..bd9fe9e85 100644 --- a/codes/special/src/Block3D.cpp +++ b/codes/special/src/Block3D.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/BlockElem.cpp b/codes/special/src/BlockElem.cpp index da513aa47..2affd7f4a 100644 --- a/codes/special/src/BlockElem.cpp +++ b/codes/special/src/BlockElem.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/BlockFaceSolver.cpp b/codes/special/src/BlockFaceSolver.cpp index 1e588148c..106fe910c 100644 --- a/codes/special/src/BlockFaceSolver.cpp +++ b/codes/special/src/BlockFaceSolver.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/BlockMachine.cpp b/codes/special/src/BlockMachine.cpp index c4f7dfd89..dec350394 100644 --- a/codes/special/src/BlockMachine.cpp +++ b/codes/special/src/BlockMachine.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/CalcCoor.cpp b/codes/special/src/CalcCoor.cpp index 8f17112cc..d185f5a8d 100644 --- a/codes/special/src/CalcCoor.cpp +++ b/codes/special/src/CalcCoor.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/Cavity.cpp b/codes/special/src/Cavity.cpp index f78909cb1..3c18ea1c1 100644 --- a/codes/special/src/Cavity.cpp +++ b/codes/special/src/Cavity.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/CgnsTest.cpp b/codes/special/src/CgnsTest.cpp index 599a55a9c..f97e0e491 100644 --- a/codes/special/src/CgnsTest.cpp +++ b/codes/special/src/CgnsTest.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/CgnsTestTmp.cpp b/codes/special/src/CgnsTestTmp.cpp index 27084abe3..1f80c0521 100644 --- a/codes/special/src/CgnsTestTmp.cpp +++ b/codes/special/src/CgnsTestTmp.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/CircleInfo.cpp b/codes/special/src/CircleInfo.cpp index 5574e93c3..a84ce6a68 100644 --- a/codes/special/src/CircleInfo.cpp +++ b/codes/special/src/CircleInfo.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/CircleLineMesh.cpp b/codes/special/src/CircleLineMesh.cpp index ee4f7d089..fd19bd482 100644 --- a/codes/special/src/CircleLineMesh.cpp +++ b/codes/special/src/CircleLineMesh.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/ClassicGrid.cpp b/codes/special/src/ClassicGrid.cpp index 2e587c80a..49f5c2e81 100644 --- a/codes/special/src/ClassicGrid.cpp +++ b/codes/special/src/ClassicGrid.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/CurveInfo.cpp b/codes/special/src/CurveInfo.cpp index 549ca30f6..90da9977e 100644 --- a/codes/special/src/CurveInfo.cpp +++ b/codes/special/src/CurveInfo.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/CurveLine.cpp b/codes/special/src/CurveLine.cpp index d83245204..5bcf4db19 100644 --- a/codes/special/src/CurveLine.cpp +++ b/codes/special/src/CurveLine.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/CurveMachine.cpp b/codes/special/src/CurveMachine.cpp index d358d1808..ac2f95fa1 100644 --- a/codes/special/src/CurveMachine.cpp +++ b/codes/special/src/CurveMachine.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/CurveMesh.cpp b/codes/special/src/CurveMesh.cpp index 86984e7a2..2e4b4da26 100644 --- a/codes/special/src/CurveMesh.cpp +++ b/codes/special/src/CurveMesh.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/Cylinder.cpp b/codes/special/src/Cylinder.cpp index fbed91803..57d361912 100644 --- a/codes/special/src/Cylinder.cpp +++ b/codes/special/src/Cylinder.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/DomainMachine.cpp b/codes/special/src/DomainMachine.cpp index 9474863b5..db2ea7717 100644 --- a/codes/special/src/DomainMachine.cpp +++ b/codes/special/src/DomainMachine.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/GridCreate.cpp b/codes/special/src/GridCreate.cpp index 31d2f76f0..2e0eacf88 100644 --- a/codes/special/src/GridCreate.cpp +++ b/codes/special/src/GridCreate.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/GridMachine.cpp b/codes/special/src/GridMachine.cpp index 93d7d1285..41d26a0f6 100644 --- a/codes/special/src/GridMachine.cpp +++ b/codes/special/src/GridMachine.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/LineInfo.cpp b/codes/special/src/LineInfo.cpp index c8c2ae499..6df2d02c4 100644 --- a/codes/special/src/LineInfo.cpp +++ b/codes/special/src/LineInfo.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/LineMachine.cpp b/codes/special/src/LineMachine.cpp index 337c33e24..fe21a74ab 100644 --- a/codes/special/src/LineMachine.cpp +++ b/codes/special/src/LineMachine.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/LineMesh.cpp b/codes/special/src/LineMesh.cpp index 479ec1fba..671efa48f 100644 --- a/codes/special/src/LineMesh.cpp +++ b/codes/special/src/LineMesh.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/LineMeshImp.cpp b/codes/special/src/LineMeshImp.cpp index a3d7f2ef4..d66cb42e2 100644 --- a/codes/special/src/LineMeshImp.cpp +++ b/codes/special/src/LineMeshImp.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/MDomain.cpp b/codes/special/src/MDomain.cpp index c6df8d022..61cf45d00 100644 --- a/codes/special/src/MDomain.cpp +++ b/codes/special/src/MDomain.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/MLine.cpp b/codes/special/src/MLine.cpp index d1caa410c..f35b5e05c 100644 --- a/codes/special/src/MLine.cpp +++ b/codes/special/src/MLine.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/Mesh.cpp b/codes/special/src/Mesh.cpp index a760182b9..36de79446 100644 --- a/codes/special/src/Mesh.cpp +++ b/codes/special/src/Mesh.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/MpiTest.cpp b/codes/special/src/MpiTest.cpp index 42c168928..3868fff63 100644 --- a/codes/special/src/MpiTest.cpp +++ b/codes/special/src/MpiTest.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/PointMachine.cpp b/codes/special/src/PointMachine.cpp index c1675072f..a7a39d39f 100644 --- a/codes/special/src/PointMachine.cpp +++ b/codes/special/src/PointMachine.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/Rae2822.cpp b/codes/special/src/Rae2822.cpp index 4f70a4075..1b86e4d89 100644 --- a/codes/special/src/Rae2822.cpp +++ b/codes/special/src/Rae2822.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/SDomain.cpp b/codes/special/src/SDomain.cpp index b16de12e5..d6173658f 100644 --- a/codes/special/src/SDomain.cpp +++ b/codes/special/src/SDomain.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/SegmentCtrl.cpp b/codes/special/src/SegmentCtrl.cpp index dbe454415..9373fdb1e 100644 --- a/codes/special/src/SegmentCtrl.cpp +++ b/codes/special/src/SegmentCtrl.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/SimpleDomain.cpp b/codes/special/src/SimpleDomain.cpp index 1ffcda63a..ddc20f1b7 100644 --- a/codes/special/src/SimpleDomain.cpp +++ b/codes/special/src/SimpleDomain.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/StrBcSetting.cpp b/codes/special/src/StrBcSetting.cpp index 65532b892..ec86db21d 100644 --- a/codes/special/src/StrBcSetting.cpp +++ b/codes/special/src/StrBcSetting.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/special/src/Transfinite.cpp b/codes/special/src/Transfinite.cpp index 27bf951b3..d7e86d62b 100644 --- a/codes/special/src/Transfinite.cpp +++ b/codes/special/src/Transfinite.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/include/ActionMap.h b/codes/task/include/ActionMap.h index 586ed55b6..ff66613d2 100644 --- a/codes/task/include/ActionMap.h +++ b/codes/task/include/ActionMap.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/include/ActionState.h b/codes/task/include/ActionState.h index 1efbc76ef..8d10da34a 100644 --- a/codes/task/include/ActionState.h +++ b/codes/task/include/ActionState.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/include/Category.h b/codes/task/include/Category.h index 6eeb5c9ad..27285e493 100644 --- a/codes/task/include/Category.h +++ b/codes/task/include/Category.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/include/CmxTask.h b/codes/task/include/CmxTask.h index 94e41c788..de9bcd381 100644 --- a/codes/task/include/CmxTask.h +++ b/codes/task/include/CmxTask.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/include/Command.h b/codes/task/include/Command.h index 9a3ba4e7a..6b8e44e64 100644 --- a/codes/task/include/Command.h +++ b/codes/task/include/Command.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/include/FileInfo.h b/codes/task/include/FileInfo.h index 8b481ccf0..5ad056250 100644 --- a/codes/task/include/FileInfo.h +++ b/codes/task/include/FileInfo.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/include/GridState.h b/codes/task/include/GridState.h index 17ea7c5bb..d8879ca92 100644 --- a/codes/task/include/GridState.h +++ b/codes/task/include/GridState.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/include/InterfaceTask.h b/codes/task/include/InterfaceTask.h index 7b9e4e57f..58f71fafc 100644 --- a/codes/task/include/InterfaceTask.h +++ b/codes/task/include/InterfaceTask.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/include/OversetTask.h b/codes/task/include/OversetTask.h index 43a6363a8..f684644eb 100644 --- a/codes/task/include/OversetTask.h +++ b/codes/task/include/OversetTask.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/include/ReadTask.h b/codes/task/include/ReadTask.h index 182b2ae6e..a49095edb 100644 --- a/codes/task/include/ReadTask.h +++ b/codes/task/include/ReadTask.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/include/SimpleTask.h b/codes/task/include/SimpleTask.h index 7e842b126..b297e923e 100644 --- a/codes/task/include/SimpleTask.h +++ b/codes/task/include/SimpleTask.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/include/State.h b/codes/task/include/State.h index 6ab584bf7..9ba64d0a8 100644 --- a/codes/task/include/State.h +++ b/codes/task/include/State.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/include/Task.h b/codes/task/include/Task.h index 39cd07c58..7363ecab2 100644 --- a/codes/task/include/Task.h +++ b/codes/task/include/Task.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/include/TaskCom.h b/codes/task/include/TaskCom.h index dd40940d9..f5e86b68f 100644 --- a/codes/task/include/TaskCom.h +++ b/codes/task/include/TaskCom.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/include/TaskImp.h b/codes/task/include/TaskImp.h index 0a88830fb..0998ca493 100644 --- a/codes/task/include/TaskImp.h +++ b/codes/task/include/TaskImp.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/include/TaskRegister.h b/codes/task/include/TaskRegister.h index b9ee15cfc..0a60a0796 100644 --- a/codes/task/include/TaskRegister.h +++ b/codes/task/include/TaskRegister.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/include/TaskState.h b/codes/task/include/TaskState.h index 2c849f93f..fcbaf52db 100644 --- a/codes/task/include/TaskState.h +++ b/codes/task/include/TaskState.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/include/WriteTask.h b/codes/task/include/WriteTask.h index 8fb768a60..0c05d22ad 100644 --- a/codes/task/include/WriteTask.h +++ b/codes/task/include/WriteTask.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/src/ActionMap.cpp b/codes/task/src/ActionMap.cpp index e929655b5..6f4619c9d 100644 --- a/codes/task/src/ActionMap.cpp +++ b/codes/task/src/ActionMap.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/src/ActionState.cpp b/codes/task/src/ActionState.cpp index 490c06af0..6a09b2608 100644 --- a/codes/task/src/ActionState.cpp +++ b/codes/task/src/ActionState.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/src/Category.cpp b/codes/task/src/Category.cpp index b795aff99..b763c3088 100644 --- a/codes/task/src/Category.cpp +++ b/codes/task/src/Category.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/src/CmxTask.cpp b/codes/task/src/CmxTask.cpp index 65e011fc1..a838c2e6a 100644 --- a/codes/task/src/CmxTask.cpp +++ b/codes/task/src/CmxTask.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/src/Command.cpp b/codes/task/src/Command.cpp index 284e166cd..f28ac0f38 100644 --- a/codes/task/src/Command.cpp +++ b/codes/task/src/Command.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/src/FileInfo.cpp b/codes/task/src/FileInfo.cpp index e93d1bd80..9eb0fce2c 100644 --- a/codes/task/src/FileInfo.cpp +++ b/codes/task/src/FileInfo.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/src/GridState.cpp b/codes/task/src/GridState.cpp index b4eb63e13..859c212e9 100644 --- a/codes/task/src/GridState.cpp +++ b/codes/task/src/GridState.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/src/InterfaceTask.cpp b/codes/task/src/InterfaceTask.cpp index e7c311092..7245cf0ed 100644 --- a/codes/task/src/InterfaceTask.cpp +++ b/codes/task/src/InterfaceTask.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/src/OversetTask.cpp b/codes/task/src/OversetTask.cpp index 56c578e6a..33d68335c 100644 --- a/codes/task/src/OversetTask.cpp +++ b/codes/task/src/OversetTask.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/src/ReadTask.cpp b/codes/task/src/ReadTask.cpp index 62bf0d2c9..05a657a65 100644 --- a/codes/task/src/ReadTask.cpp +++ b/codes/task/src/ReadTask.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/src/SimpleTask.cpp b/codes/task/src/SimpleTask.cpp index 049fd0cc9..ce53ff3c9 100644 --- a/codes/task/src/SimpleTask.cpp +++ b/codes/task/src/SimpleTask.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/src/State.cpp b/codes/task/src/State.cpp index 8bed39f36..7435580c8 100644 --- a/codes/task/src/State.cpp +++ b/codes/task/src/State.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/src/Task.cpp b/codes/task/src/Task.cpp index 5981c9c7b..145abbdb7 100644 --- a/codes/task/src/Task.cpp +++ b/codes/task/src/Task.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/src/TaskCom.cpp b/codes/task/src/TaskCom.cpp index 15b6e4488..c6cbeaee9 100644 --- a/codes/task/src/TaskCom.cpp +++ b/codes/task/src/TaskCom.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/src/TaskImp.cpp b/codes/task/src/TaskImp.cpp index eb1816ca7..d943fffd0 100644 --- a/codes/task/src/TaskImp.cpp +++ b/codes/task/src/TaskImp.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/src/TaskRegister.cpp b/codes/task/src/TaskRegister.cpp index 4df159ceb..4309a1232 100644 --- a/codes/task/src/TaskRegister.cpp +++ b/codes/task/src/TaskRegister.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/src/TaskState.cpp b/codes/task/src/TaskState.cpp index 8a0822426..e5069c186 100644 --- a/codes/task/src/TaskState.cpp +++ b/codes/task/src/TaskState.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/task/src/WriteTask.cpp b/codes/task/src/WriteTask.cpp index 1eecb26ab..f32953e14 100644 --- a/codes/task/src/WriteTask.cpp +++ b/codes/task/src/WriteTask.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/test/include/JsonTest.h b/codes/test/include/JsonTest.h index dd773b412..b6afaa5ff 100644 --- a/codes/test/include/JsonTest.h +++ b/codes/test/include/JsonTest.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/test/include/Test.h b/codes/test/include/Test.h index 92e568182..d26bfd88f 100644 --- a/codes/test/include/Test.h +++ b/codes/test/include/Test.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/test/src/JsonTest.cpp b/codes/test/src/JsonTest.cpp index 9ff8ac3ec..5b6b9d71a 100644 --- a/codes/test/src/JsonTest.cpp +++ b/codes/test/src/JsonTest.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/test/src/Test.cpp b/codes/test/src/Test.cpp index b9cbfc2cf..9d8814eb5 100644 --- a/codes/test/src/Test.cpp +++ b/codes/test/src/Test.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/theory/include/Sod.h b/codes/theory/include/Sod.h index 88c6e3b68..74f4bcc3c 100644 --- a/codes/theory/include/Sod.h +++ b/codes/theory/include/Sod.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/theory/include/Theory.h b/codes/theory/include/Theory.h index ae664397a..f7f25b227 100644 --- a/codes/theory/include/Theory.h +++ b/codes/theory/include/Theory.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/theory/src/Sod.cpp b/codes/theory/src/Sod.cpp index b806e9e46..dfd0f1e00 100644 --- a/codes/theory/src/Sod.cpp +++ b/codes/theory/src/Sod.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/theory/src/Theory.cpp b/codes/theory/src/Theory.cpp index e7e23d4a4..9ae95c047 100644 --- a/codes/theory/src/Theory.cpp +++ b/codes/theory/src/Theory.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/time/include/TimeSpan.h b/codes/time/include/TimeSpan.h index 24174e9a4..94995d5e0 100644 --- a/codes/time/include/TimeSpan.h +++ b/codes/time/include/TimeSpan.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/time/include/TimeTest.h b/codes/time/include/TimeTest.h index 8bd99c560..6005b1416 100644 --- a/codes/time/include/TimeTest.h +++ b/codes/time/include/TimeTest.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/time/src/TimeSpan.cpp b/codes/time/src/TimeSpan.cpp index d62b8ceb9..7e4140f05 100644 --- a/codes/time/src/TimeSpan.cpp +++ b/codes/time/src/TimeSpan.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/time/src/TimeTest.cpp b/codes/time/src/TimeTest.cpp index 6d2b8b2e6..acb065328 100644 --- a/codes/time/src/TimeTest.cpp +++ b/codes/time/src/TimeTest.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/include/TurbBcSolver.h b/codes/turb/include/TurbBcSolver.h index a582aac15..9fc1753db 100644 --- a/codes/turb/include/TurbBcSolver.h +++ b/codes/turb/include/TurbBcSolver.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/include/TurbCom.h b/codes/turb/include/TurbCom.h index 04b867e1a..2f576eca4 100644 --- a/codes/turb/include/TurbCom.h +++ b/codes/turb/include/TurbCom.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/include/TurbCtrl.h b/codes/turb/include/TurbCtrl.h index e920811fc..4882b0f64 100644 --- a/codes/turb/include/TurbCtrl.h +++ b/codes/turb/include/TurbCtrl.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/include/TurbInvFlux.h b/codes/turb/include/TurbInvFlux.h index 14e437b90..90a34f486 100644 --- a/codes/turb/include/TurbInvFlux.h +++ b/codes/turb/include/TurbInvFlux.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/include/TurbLusgs.h b/codes/turb/include/TurbLusgs.h index 680cc2396..f8109a23c 100644 --- a/codes/turb/include/TurbLusgs.h +++ b/codes/turb/include/TurbLusgs.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/include/TurbRestart.h b/codes/turb/include/TurbRestart.h index cef6e486d..3b624aaa3 100644 --- a/codes/turb/include/TurbRestart.h +++ b/codes/turb/include/TurbRestart.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/include/TurbRhs.h b/codes/turb/include/TurbRhs.h index 606dbdd66..6f21bb160 100644 --- a/codes/turb/include/TurbRhs.h +++ b/codes/turb/include/TurbRhs.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/include/TurbSolver.h b/codes/turb/include/TurbSolver.h index 3e883dc4a..49d9494a7 100644 --- a/codes/turb/include/TurbSolver.h +++ b/codes/turb/include/TurbSolver.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/include/TurbSolverImp.h b/codes/turb/include/TurbSolverImp.h index 763a82dc3..65bf47f17 100644 --- a/codes/turb/include/TurbSolverImp.h +++ b/codes/turb/include/TurbSolverImp.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/include/TurbSpectrum.h b/codes/turb/include/TurbSpectrum.h index 097c295f3..4206e3b9b 100644 --- a/codes/turb/include/TurbSpectrum.h +++ b/codes/turb/include/TurbSpectrum.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/include/TurbSrcFlux.h b/codes/turb/include/TurbSrcFlux.h index 00b151d95..c57dff51f 100644 --- a/codes/turb/include/TurbSrcFlux.h +++ b/codes/turb/include/TurbSrcFlux.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/include/TurbTrans.h b/codes/turb/include/TurbTrans.h index 9eae03dc0..02409c260 100644 --- a/codes/turb/include/TurbTrans.h +++ b/codes/turb/include/TurbTrans.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/include/TurbUnsteady.h b/codes/turb/include/TurbUnsteady.h index fa57238b7..958939983 100644 --- a/codes/turb/include/TurbUnsteady.h +++ b/codes/turb/include/TurbUnsteady.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/include/TurbUpdate.h b/codes/turb/include/TurbUpdate.h index b4ee7c33a..c2b29ba52 100644 --- a/codes/turb/include/TurbUpdate.h +++ b/codes/turb/include/TurbUpdate.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/include/TurbVisFlux.h b/codes/turb/include/TurbVisFlux.h index 83f29180b..2c0548063 100644 --- a/codes/turb/include/TurbVisFlux.h +++ b/codes/turb/include/TurbVisFlux.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/src/TurbBcSolver.cpp b/codes/turb/src/TurbBcSolver.cpp index 8f5bac598..5fe18f691 100644 --- a/codes/turb/src/TurbBcSolver.cpp +++ b/codes/turb/src/TurbBcSolver.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/src/TurbCom.cpp b/codes/turb/src/TurbCom.cpp index 951e0ee22..0f055f4d6 100644 --- a/codes/turb/src/TurbCom.cpp +++ b/codes/turb/src/TurbCom.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/src/TurbCtrl.cpp b/codes/turb/src/TurbCtrl.cpp index 105ac93f6..f08f94c39 100644 --- a/codes/turb/src/TurbCtrl.cpp +++ b/codes/turb/src/TurbCtrl.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/src/TurbInvFlux.cpp b/codes/turb/src/TurbInvFlux.cpp index 8eb366864..194f93751 100644 --- a/codes/turb/src/TurbInvFlux.cpp +++ b/codes/turb/src/TurbInvFlux.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/src/TurbLusgs.cpp b/codes/turb/src/TurbLusgs.cpp index 29d9668b1..3dcda5e0a 100644 --- a/codes/turb/src/TurbLusgs.cpp +++ b/codes/turb/src/TurbLusgs.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/src/TurbRestart.cpp b/codes/turb/src/TurbRestart.cpp index cc8777189..569939ca2 100644 --- a/codes/turb/src/TurbRestart.cpp +++ b/codes/turb/src/TurbRestart.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/src/TurbRhs.cpp b/codes/turb/src/TurbRhs.cpp index 609744242..18c12dfe6 100644 --- a/codes/turb/src/TurbRhs.cpp +++ b/codes/turb/src/TurbRhs.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/src/TurbSolver.cpp b/codes/turb/src/TurbSolver.cpp index 754123575..5a246e3e7 100644 --- a/codes/turb/src/TurbSolver.cpp +++ b/codes/turb/src/TurbSolver.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/src/TurbSolverImp.cpp b/codes/turb/src/TurbSolverImp.cpp index 6c795ce23..3002f7305 100644 --- a/codes/turb/src/TurbSolverImp.cpp +++ b/codes/turb/src/TurbSolverImp.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/src/TurbSpectrum.cpp b/codes/turb/src/TurbSpectrum.cpp index 0eaf01da9..b95128252 100644 --- a/codes/turb/src/TurbSpectrum.cpp +++ b/codes/turb/src/TurbSpectrum.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/src/TurbSrcFlux.cpp b/codes/turb/src/TurbSrcFlux.cpp index d66ba3599..f52a651f0 100644 --- a/codes/turb/src/TurbSrcFlux.cpp +++ b/codes/turb/src/TurbSrcFlux.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/src/TurbTrans.cpp b/codes/turb/src/TurbTrans.cpp index 8d8eceda2..48cfae12e 100644 --- a/codes/turb/src/TurbTrans.cpp +++ b/codes/turb/src/TurbTrans.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/src/TurbUnsteady.cpp b/codes/turb/src/TurbUnsteady.cpp index 64863a8bc..ecc2863ef 100644 --- a/codes/turb/src/TurbUnsteady.cpp +++ b/codes/turb/src/TurbUnsteady.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/src/TurbUpdate.cpp b/codes/turb/src/TurbUpdate.cpp index ff9b78b36..be6982687 100644 --- a/codes/turb/src/TurbUpdate.cpp +++ b/codes/turb/src/TurbUpdate.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/turb/src/TurbVisFlux.cpp b/codes/turb/src/TurbVisFlux.cpp index 6ddc2ead7..fb2ed734f 100644 --- a/codes/turb/src/TurbVisFlux.cpp +++ b/codes/turb/src/TurbVisFlux.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uins/include/UINsBcSolver.h b/codes/uins/include/UINsBcSolver.h index 38f8637ed..b7a0b08b2 100644 --- a/codes/uins/include/UINsBcSolver.h +++ b/codes/uins/include/UINsBcSolver.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uins/include/UINsCom.h b/codes/uins/include/UINsCom.h index 2be666604..74465747e 100644 --- a/codes/uins/include/UINsCom.h +++ b/codes/uins/include/UINsCom.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uins/include/UINsGrad.h b/codes/uins/include/UINsGrad.h index b0e0417ef..aed8ef74e 100644 --- a/codes/uins/include/UINsGrad.h +++ b/codes/uins/include/UINsGrad.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uins/include/UINsInvterm.h b/codes/uins/include/UINsInvterm.h index 3dcc4e025..c8d88f20f 100644 --- a/codes/uins/include/UINsInvterm.h +++ b/codes/uins/include/UINsInvterm.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uins/include/UINsLimiter.h b/codes/uins/include/UINsLimiter.h index f3c141249..1519d2201 100644 --- a/codes/uins/include/UINsLimiter.h +++ b/codes/uins/include/UINsLimiter.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uins/include/UINsLusgs.h b/codes/uins/include/UINsLusgs.h index f2dc330be..50b157fa0 100644 --- a/codes/uins/include/UINsLusgs.h +++ b/codes/uins/include/UINsLusgs.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uins/include/UINsRestart.h b/codes/uins/include/UINsRestart.h index 0841f3182..75b830204 100644 --- a/codes/uins/include/UINsRestart.h +++ b/codes/uins/include/UINsRestart.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uins/include/UINsSolver.h b/codes/uins/include/UINsSolver.h index d418d8117..d672cca47 100644 --- a/codes/uins/include/UINsSolver.h +++ b/codes/uins/include/UINsSolver.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uins/include/UINsSpectrum.h b/codes/uins/include/UINsSpectrum.h index b344b264a..2ddfdcc9c 100644 --- a/codes/uins/include/UINsSpectrum.h +++ b/codes/uins/include/UINsSpectrum.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uins/include/UINsUnsteady.h b/codes/uins/include/UINsUnsteady.h index 3f18a73b2..b34ab19c2 100644 --- a/codes/uins/include/UINsUnsteady.h +++ b/codes/uins/include/UINsUnsteady.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uins/include/UINsUpdate.h b/codes/uins/include/UINsUpdate.h index d0dd59676..cd370583a 100644 --- a/codes/uins/include/UINsUpdate.h +++ b/codes/uins/include/UINsUpdate.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uins/include/UINsVisterm.h b/codes/uins/include/UINsVisterm.h index 379ad4168..baa02f169 100644 --- a/codes/uins/include/UINsVisterm.h +++ b/codes/uins/include/UINsVisterm.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uins/src/UINsBcSolver.cpp b/codes/uins/src/UINsBcSolver.cpp index 3a32f636c..23a083f26 100644 --- a/codes/uins/src/UINsBcSolver.cpp +++ b/codes/uins/src/UINsBcSolver.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uins/src/UINsCom.cpp b/codes/uins/src/UINsCom.cpp index 36be6509c..ce8d9f0a0 100644 --- a/codes/uins/src/UINsCom.cpp +++ b/codes/uins/src/UINsCom.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uins/src/UINsGrad.cpp b/codes/uins/src/UINsGrad.cpp index 0d79c5322..a916f3a43 100644 --- a/codes/uins/src/UINsGrad.cpp +++ b/codes/uins/src/UINsGrad.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uins/src/UINsInvterm.cpp b/codes/uins/src/UINsInvterm.cpp index 9ba1ad0b5..e6815d7d2 100644 --- a/codes/uins/src/UINsInvterm.cpp +++ b/codes/uins/src/UINsInvterm.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uins/src/UINsLimiter.cpp b/codes/uins/src/UINsLimiter.cpp index 91623e2ec..c74cb53a2 100644 --- a/codes/uins/src/UINsLimiter.cpp +++ b/codes/uins/src/UINsLimiter.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uins/src/UINsLusgs.cpp b/codes/uins/src/UINsLusgs.cpp index 2dd9e80df..d130e294e 100644 --- a/codes/uins/src/UINsLusgs.cpp +++ b/codes/uins/src/UINsLusgs.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uins/src/UINsRestart.cpp b/codes/uins/src/UINsRestart.cpp index 6b056f8e7..5a29459ac 100644 --- a/codes/uins/src/UINsRestart.cpp +++ b/codes/uins/src/UINsRestart.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uins/src/UINsSolver.cpp b/codes/uins/src/UINsSolver.cpp index fe31de515..890ce3471 100644 --- a/codes/uins/src/UINsSolver.cpp +++ b/codes/uins/src/UINsSolver.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uins/src/UINsSpectrum.cpp b/codes/uins/src/UINsSpectrum.cpp index 6aad47021..beede03f5 100644 --- a/codes/uins/src/UINsSpectrum.cpp +++ b/codes/uins/src/UINsSpectrum.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uins/src/UINsUnsteady.cpp b/codes/uins/src/UINsUnsteady.cpp index e6211e714..e25ba6140 100644 --- a/codes/uins/src/UINsUnsteady.cpp +++ b/codes/uins/src/UINsUnsteady.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uins/src/UINsUpdate.cpp b/codes/uins/src/UINsUpdate.cpp index f281ce9dd..e8540d0b2 100644 --- a/codes/uins/src/UINsUpdate.cpp +++ b/codes/uins/src/UINsUpdate.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uins/src/UINsVisterm.cpp b/codes/uins/src/UINsVisterm.cpp index e0a61410a..589eeb565 100644 --- a/codes/uins/src/UINsVisterm.cpp +++ b/codes/uins/src/UINsVisterm.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uns/include/UNsBcSolver.h b/codes/uns/include/UNsBcSolver.h index f3ea85fbb..a8d78b0c6 100644 --- a/codes/uns/include/UNsBcSolver.h +++ b/codes/uns/include/UNsBcSolver.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uns/include/UNsCom.h b/codes/uns/include/UNsCom.h index 39bc33624..f731bc6e3 100644 --- a/codes/uns/include/UNsCom.h +++ b/codes/uns/include/UNsCom.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uns/include/UNsGrad.h b/codes/uns/include/UNsGrad.h index 096584397..173bcd42e 100644 --- a/codes/uns/include/UNsGrad.h +++ b/codes/uns/include/UNsGrad.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uns/include/UNsInvFlux.h b/codes/uns/include/UNsInvFlux.h index bd8036c31..70a57998b 100644 --- a/codes/uns/include/UNsInvFlux.h +++ b/codes/uns/include/UNsInvFlux.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uns/include/UNsLimiter.h b/codes/uns/include/UNsLimiter.h index 4050f42fb..3e6dc2626 100644 --- a/codes/uns/include/UNsLimiter.h +++ b/codes/uns/include/UNsLimiter.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uns/include/UNsLusgs.h b/codes/uns/include/UNsLusgs.h index 86863a103..93e5b0da7 100644 --- a/codes/uns/include/UNsLusgs.h +++ b/codes/uns/include/UNsLusgs.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uns/include/UNsRestart.h b/codes/uns/include/UNsRestart.h index 0f12ce0f6..4e2d35bc9 100644 --- a/codes/uns/include/UNsRestart.h +++ b/codes/uns/include/UNsRestart.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uns/include/UNsSolver.h b/codes/uns/include/UNsSolver.h index a43123b14..0636f2bed 100644 --- a/codes/uns/include/UNsSolver.h +++ b/codes/uns/include/UNsSolver.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uns/include/UNsSpectrum.h b/codes/uns/include/UNsSpectrum.h index 27812c82e..8dfd7bfaa 100644 --- a/codes/uns/include/UNsSpectrum.h +++ b/codes/uns/include/UNsSpectrum.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uns/include/UNsUnsteady.h b/codes/uns/include/UNsUnsteady.h index 9c580e63e..c636f20ac 100644 --- a/codes/uns/include/UNsUnsteady.h +++ b/codes/uns/include/UNsUnsteady.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uns/include/UNsUpdate.h b/codes/uns/include/UNsUpdate.h index a22137667..d4bb7e4e4 100644 --- a/codes/uns/include/UNsUpdate.h +++ b/codes/uns/include/UNsUpdate.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uns/include/UNsVisFlux.h b/codes/uns/include/UNsVisFlux.h index bcc3c8b4a..cc7b53605 100644 --- a/codes/uns/include/UNsVisFlux.h +++ b/codes/uns/include/UNsVisFlux.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uns/include/UTimeStep.h b/codes/uns/include/UTimeStep.h index a37ac716e..b5c958041 100644 --- a/codes/uns/include/UTimeStep.h +++ b/codes/uns/include/UTimeStep.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uns/src/UNsBcSolver.cpp b/codes/uns/src/UNsBcSolver.cpp index 140a2840d..a899ac92a 100644 --- a/codes/uns/src/UNsBcSolver.cpp +++ b/codes/uns/src/UNsBcSolver.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uns/src/UNsCom.cpp b/codes/uns/src/UNsCom.cpp index f06f0f6bf..8cf454d1d 100644 --- a/codes/uns/src/UNsCom.cpp +++ b/codes/uns/src/UNsCom.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uns/src/UNsGrad.cpp b/codes/uns/src/UNsGrad.cpp index 9ca2711b9..59ec1e458 100644 --- a/codes/uns/src/UNsGrad.cpp +++ b/codes/uns/src/UNsGrad.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uns/src/UNsInvFlux.cpp b/codes/uns/src/UNsInvFlux.cpp index 69fff695c..2b4956176 100644 --- a/codes/uns/src/UNsInvFlux.cpp +++ b/codes/uns/src/UNsInvFlux.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uns/src/UNsLimiter.cpp b/codes/uns/src/UNsLimiter.cpp index 8be807681..18e44e3ce 100644 --- a/codes/uns/src/UNsLimiter.cpp +++ b/codes/uns/src/UNsLimiter.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uns/src/UNsLusgs.cpp b/codes/uns/src/UNsLusgs.cpp index fefbbba25..f3723cee6 100644 --- a/codes/uns/src/UNsLusgs.cpp +++ b/codes/uns/src/UNsLusgs.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uns/src/UNsRestart.cpp b/codes/uns/src/UNsRestart.cpp index 38194a285..44776f440 100644 --- a/codes/uns/src/UNsRestart.cpp +++ b/codes/uns/src/UNsRestart.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uns/src/UNsSolver.cpp b/codes/uns/src/UNsSolver.cpp index 93f273911..1f9e38d39 100644 --- a/codes/uns/src/UNsSolver.cpp +++ b/codes/uns/src/UNsSolver.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uns/src/UNsSpectrum.cpp b/codes/uns/src/UNsSpectrum.cpp index 705ff7bbe..be0296217 100644 --- a/codes/uns/src/UNsSpectrum.cpp +++ b/codes/uns/src/UNsSpectrum.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uns/src/UNsUnsteady.cpp b/codes/uns/src/UNsUnsteady.cpp index 3cc3bf1d0..d03939016 100644 --- a/codes/uns/src/UNsUnsteady.cpp +++ b/codes/uns/src/UNsUnsteady.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uns/src/UNsUpdate.cpp b/codes/uns/src/UNsUpdate.cpp index 239cc1b98..6162608ab 100644 --- a/codes/uns/src/UNsUpdate.cpp +++ b/codes/uns/src/UNsUpdate.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uns/src/UNsVisFlux.cpp b/codes/uns/src/UNsVisFlux.cpp index fe809c408..2f7b7aeb0 100644 --- a/codes/uns/src/UNsVisFlux.cpp +++ b/codes/uns/src/UNsVisFlux.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uns/src/UTimeStep.cpp b/codes/uns/src/UTimeStep.cpp index 16a666155..2cfaa5880 100644 --- a/codes/uns/src/UTimeStep.cpp +++ b/codes/uns/src/UTimeStep.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/unsteady/include/Unsteady.h b/codes/unsteady/include/Unsteady.h index 8d4d0458f..f393ee376 100644 --- a/codes/unsteady/include/Unsteady.h +++ b/codes/unsteady/include/Unsteady.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/unsteady/include/UnsteadyImp.h b/codes/unsteady/include/UnsteadyImp.h index 93db079fa..2fce3c8db 100644 --- a/codes/unsteady/include/UnsteadyImp.h +++ b/codes/unsteady/include/UnsteadyImp.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/unsteady/include/UnsteadyTaskReg.h b/codes/unsteady/include/UnsteadyTaskReg.h index 9166fe4f1..940c53a61 100644 --- a/codes/unsteady/include/UnsteadyTaskReg.h +++ b/codes/unsteady/include/UnsteadyTaskReg.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/unsteady/include/UsdBasic.h b/codes/unsteady/include/UsdBasic.h index c2758d846..2a3390589 100644 --- a/codes/unsteady/include/UsdBasic.h +++ b/codes/unsteady/include/UsdBasic.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/unsteady/include/UsdData.h b/codes/unsteady/include/UsdData.h index a1601f919..01ca1908c 100644 --- a/codes/unsteady/include/UsdData.h +++ b/codes/unsteady/include/UsdData.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/unsteady/include/UsdField.h b/codes/unsteady/include/UsdField.h index e57a29038..99dd00f04 100644 --- a/codes/unsteady/include/UsdField.h +++ b/codes/unsteady/include/UsdField.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/unsteady/include/UsdPara.h b/codes/unsteady/include/UsdPara.h index 366c236b4..34a7afe15 100644 --- a/codes/unsteady/include/UsdPara.h +++ b/codes/unsteady/include/UsdPara.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/unsteady/src/Unsteady.cpp b/codes/unsteady/src/Unsteady.cpp index 3f12a5015..c3b59e0bd 100644 --- a/codes/unsteady/src/Unsteady.cpp +++ b/codes/unsteady/src/Unsteady.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/unsteady/src/UnsteadyImp.cpp b/codes/unsteady/src/UnsteadyImp.cpp index e99491894..cd10bb80b 100644 --- a/codes/unsteady/src/UnsteadyImp.cpp +++ b/codes/unsteady/src/UnsteadyImp.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/unsteady/src/UnsteadyTaskReg.cpp b/codes/unsteady/src/UnsteadyTaskReg.cpp index 065c4b3d6..f590d69d7 100644 --- a/codes/unsteady/src/UnsteadyTaskReg.cpp +++ b/codes/unsteady/src/UnsteadyTaskReg.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/unsteady/src/UsdBasic.cpp b/codes/unsteady/src/UsdBasic.cpp index 25d4e51a3..3ff886844 100644 --- a/codes/unsteady/src/UsdBasic.cpp +++ b/codes/unsteady/src/UsdBasic.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/unsteady/src/UsdData.cpp b/codes/unsteady/src/UsdData.cpp index 9d772d3fe..69fae2d4d 100644 --- a/codes/unsteady/src/UsdData.cpp +++ b/codes/unsteady/src/UsdData.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/unsteady/src/UsdField.cpp b/codes/unsteady/src/UsdField.cpp index 823e8fc70..bfbbd4a37 100644 --- a/codes/unsteady/src/UsdField.cpp +++ b/codes/unsteady/src/UsdField.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/unsteady/src/UsdPara.cpp b/codes/unsteady/src/UsdPara.cpp index 62326be35..9ee5c8306 100644 --- a/codes/unsteady/src/UsdPara.cpp +++ b/codes/unsteady/src/UsdPara.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/update/include/Update.h b/codes/update/include/Update.h index 751f889db..7611ec91a 100644 --- a/codes/update/include/Update.h +++ b/codes/update/include/Update.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/update/include/UpdateTaskReg.h b/codes/update/include/UpdateTaskReg.h index 584cd6827..4354e6bf8 100644 --- a/codes/update/include/UpdateTaskReg.h +++ b/codes/update/include/UpdateTaskReg.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/update/src/Update.cpp b/codes/update/src/Update.cpp index 1111b2bd2..f33c48ee9 100644 --- a/codes/update/src/Update.cpp +++ b/codes/update/src/Update.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/update/src/UpdateTaskReg.cpp b/codes/update/src/UpdateTaskReg.cpp index 9613e9e91..45426274b 100644 --- a/codes/update/src/UpdateTaskReg.cpp +++ b/codes/update/src/UpdateTaskReg.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/usolver/include/UBcSolver.h b/codes/usolver/include/UBcSolver.h index 512edf36d..0ca27a209 100644 --- a/codes/usolver/include/UBcSolver.h +++ b/codes/usolver/include/UBcSolver.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/usolver/include/UCom.h b/codes/usolver/include/UCom.h index 4b36666f4..8fc28f5ee 100644 --- a/codes/usolver/include/UCom.h +++ b/codes/usolver/include/UCom.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/usolver/include/UGrad.h b/codes/usolver/include/UGrad.h index 7e7772e63..e5c6dab7e 100644 --- a/codes/usolver/include/UGrad.h +++ b/codes/usolver/include/UGrad.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/usolver/include/ULhs.h b/codes/usolver/include/ULhs.h index 969055c40..15c146e84 100644 --- a/codes/usolver/include/ULhs.h +++ b/codes/usolver/include/ULhs.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/usolver/include/ULimiter.h b/codes/usolver/include/ULimiter.h index 60300bd1b..4aa1c2bc2 100644 --- a/codes/usolver/include/ULimiter.h +++ b/codes/usolver/include/ULimiter.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/usolver/include/UResidual.h b/codes/usolver/include/UResidual.h index ff6012f5b..d56c85df7 100644 --- a/codes/usolver/include/UResidual.h +++ b/codes/usolver/include/UResidual.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/usolver/include/UUnsteady.h b/codes/usolver/include/UUnsteady.h index 7ca4db91d..f2f2cdb8e 100644 --- a/codes/usolver/include/UUnsteady.h +++ b/codes/usolver/include/UUnsteady.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/usolver/include/UVisualize.h b/codes/usolver/include/UVisualize.h index a74cc19be..31ff18a6e 100644 --- a/codes/usolver/include/UVisualize.h +++ b/codes/usolver/include/UVisualize.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/usolver/include/VisGrad.h b/codes/usolver/include/VisGrad.h index 79b72f845..dab2baef8 100644 --- a/codes/usolver/include/VisGrad.h +++ b/codes/usolver/include/VisGrad.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/usolver/src/UBcSolver.cpp b/codes/usolver/src/UBcSolver.cpp index 2f852753f..104883b91 100644 --- a/codes/usolver/src/UBcSolver.cpp +++ b/codes/usolver/src/UBcSolver.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/usolver/src/UCom.cpp b/codes/usolver/src/UCom.cpp index a271644c2..abd2b5765 100644 --- a/codes/usolver/src/UCom.cpp +++ b/codes/usolver/src/UCom.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/usolver/src/UGrad.cpp b/codes/usolver/src/UGrad.cpp index da9596f25..613e058ec 100644 --- a/codes/usolver/src/UGrad.cpp +++ b/codes/usolver/src/UGrad.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/usolver/src/ULhs.cpp b/codes/usolver/src/ULhs.cpp index 8cd972e3a..579e4d596 100644 --- a/codes/usolver/src/ULhs.cpp +++ b/codes/usolver/src/ULhs.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/usolver/src/ULimiter.cpp b/codes/usolver/src/ULimiter.cpp index 1a52b8d92..b80d3758e 100644 --- a/codes/usolver/src/ULimiter.cpp +++ b/codes/usolver/src/ULimiter.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/usolver/src/UResidual.cpp b/codes/usolver/src/UResidual.cpp index aa81506bc..aa766f0ae 100644 --- a/codes/usolver/src/UResidual.cpp +++ b/codes/usolver/src/UResidual.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/usolver/src/UUnsteady.cpp b/codes/usolver/src/UUnsteady.cpp index 594cb4cff..0cfebe02d 100644 --- a/codes/usolver/src/UUnsteady.cpp +++ b/codes/usolver/src/UUnsteady.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/usolver/src/UVisualize.cpp b/codes/usolver/src/UVisualize.cpp index 36dae893c..eff7075be 100644 --- a/codes/usolver/src/UVisualize.cpp +++ b/codes/usolver/src/UVisualize.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/usolver/src/VisGrad.cpp b/codes/usolver/src/VisGrad.cpp index 0c907e74d..f65e5bd66 100644 --- a/codes/usolver/src/VisGrad.cpp +++ b/codes/usolver/src/VisGrad.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uturb/include/UTurbBcSolver.h b/codes/uturb/include/UTurbBcSolver.h index a225431e6..b40a4a0dd 100644 --- a/codes/uturb/include/UTurbBcSolver.h +++ b/codes/uturb/include/UTurbBcSolver.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uturb/include/UTurbCom.h b/codes/uturb/include/UTurbCom.h index 2e94bcfed..b9121c17e 100644 --- a/codes/uturb/include/UTurbCom.h +++ b/codes/uturb/include/UTurbCom.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uturb/include/UTurbGrad.h b/codes/uturb/include/UTurbGrad.h index adc4dd68f..6462ba17c 100644 --- a/codes/uturb/include/UTurbGrad.h +++ b/codes/uturb/include/UTurbGrad.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uturb/include/UTurbInvFlux.h b/codes/uturb/include/UTurbInvFlux.h index c8ce2ce82..76118252a 100644 --- a/codes/uturb/include/UTurbInvFlux.h +++ b/codes/uturb/include/UTurbInvFlux.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uturb/include/UTurbLimiter.h b/codes/uturb/include/UTurbLimiter.h index 4b946cb41..a63edf7dc 100644 --- a/codes/uturb/include/UTurbLimiter.h +++ b/codes/uturb/include/UTurbLimiter.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uturb/include/UTurbLusgs.h b/codes/uturb/include/UTurbLusgs.h index b79377859..c0bf333b3 100644 --- a/codes/uturb/include/UTurbLusgs.h +++ b/codes/uturb/include/UTurbLusgs.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uturb/include/UTurbRestart.h b/codes/uturb/include/UTurbRestart.h index a5be3f931..60c7587bf 100644 --- a/codes/uturb/include/UTurbRestart.h +++ b/codes/uturb/include/UTurbRestart.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uturb/include/UTurbSolver.h b/codes/uturb/include/UTurbSolver.h index b6ebc3300..a0ad6b642 100644 --- a/codes/uturb/include/UTurbSolver.h +++ b/codes/uturb/include/UTurbSolver.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uturb/include/UTurbSpectrum.h b/codes/uturb/include/UTurbSpectrum.h index f2a9cbbeb..438b31b28 100644 --- a/codes/uturb/include/UTurbSpectrum.h +++ b/codes/uturb/include/UTurbSpectrum.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uturb/include/UTurbSrcFlux.h b/codes/uturb/include/UTurbSrcFlux.h index 031b1b860..5b9bee59c 100644 --- a/codes/uturb/include/UTurbSrcFlux.h +++ b/codes/uturb/include/UTurbSrcFlux.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uturb/include/UTurbUnsteady.h b/codes/uturb/include/UTurbUnsteady.h index ed29d4a7b..c7248bd03 100644 --- a/codes/uturb/include/UTurbUnsteady.h +++ b/codes/uturb/include/UTurbUnsteady.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uturb/include/UTurbUpdate.h b/codes/uturb/include/UTurbUpdate.h index 9d90d4c31..303ad0336 100644 --- a/codes/uturb/include/UTurbUpdate.h +++ b/codes/uturb/include/UTurbUpdate.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uturb/include/UTurbVisFlux.h b/codes/uturb/include/UTurbVisFlux.h index 574f30d26..402942de1 100644 --- a/codes/uturb/include/UTurbVisFlux.h +++ b/codes/uturb/include/UTurbVisFlux.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uturb/src/UTurbBcSolver.cpp b/codes/uturb/src/UTurbBcSolver.cpp index 373be97e2..ad46d1e8c 100644 --- a/codes/uturb/src/UTurbBcSolver.cpp +++ b/codes/uturb/src/UTurbBcSolver.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uturb/src/UTurbCom.cpp b/codes/uturb/src/UTurbCom.cpp index 534a7a337..4c4756b0a 100644 --- a/codes/uturb/src/UTurbCom.cpp +++ b/codes/uturb/src/UTurbCom.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uturb/src/UTurbGrad.cpp b/codes/uturb/src/UTurbGrad.cpp index dac38ce84..6651e74d3 100644 --- a/codes/uturb/src/UTurbGrad.cpp +++ b/codes/uturb/src/UTurbGrad.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uturb/src/UTurbInvFlux.cpp b/codes/uturb/src/UTurbInvFlux.cpp index 43f5c816a..31cf6d4ec 100644 --- a/codes/uturb/src/UTurbInvFlux.cpp +++ b/codes/uturb/src/UTurbInvFlux.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uturb/src/UTurbLimiter.cpp b/codes/uturb/src/UTurbLimiter.cpp index 9e5d7bf63..b43d8bb88 100644 --- a/codes/uturb/src/UTurbLimiter.cpp +++ b/codes/uturb/src/UTurbLimiter.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uturb/src/UTurbLusgs.cpp b/codes/uturb/src/UTurbLusgs.cpp index b21a618c9..a7b022ad2 100644 --- a/codes/uturb/src/UTurbLusgs.cpp +++ b/codes/uturb/src/UTurbLusgs.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uturb/src/UTurbRestart.cpp b/codes/uturb/src/UTurbRestart.cpp index 4f0f3b466..cdb704dbf 100644 --- a/codes/uturb/src/UTurbRestart.cpp +++ b/codes/uturb/src/UTurbRestart.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uturb/src/UTurbSolver.cpp b/codes/uturb/src/UTurbSolver.cpp index faed0db55..1368795a7 100644 --- a/codes/uturb/src/UTurbSolver.cpp +++ b/codes/uturb/src/UTurbSolver.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uturb/src/UTurbSpectrum.cpp b/codes/uturb/src/UTurbSpectrum.cpp index 2dc7a4eb7..9157c0d19 100644 --- a/codes/uturb/src/UTurbSpectrum.cpp +++ b/codes/uturb/src/UTurbSpectrum.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uturb/src/UTurbSrcFlux.cpp b/codes/uturb/src/UTurbSrcFlux.cpp index 73596fa39..119d94645 100644 --- a/codes/uturb/src/UTurbSrcFlux.cpp +++ b/codes/uturb/src/UTurbSrcFlux.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uturb/src/UTurbUnsteady.cpp b/codes/uturb/src/UTurbUnsteady.cpp index 9d5c430f7..4142f7754 100644 --- a/codes/uturb/src/UTurbUnsteady.cpp +++ b/codes/uturb/src/UTurbUnsteady.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uturb/src/UTurbUpdate.cpp b/codes/uturb/src/UTurbUpdate.cpp index 7208b8a75..a8f6e5ac4 100644 --- a/codes/uturb/src/UTurbUpdate.cpp +++ b/codes/uturb/src/UTurbUpdate.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/uturb/src/UTurbVisFlux.cpp b/codes/uturb/src/UTurbVisFlux.cpp index 2114da29b..9d219e81b 100644 --- a/codes/uturb/src/UTurbVisFlux.cpp +++ b/codes/uturb/src/UTurbVisFlux.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/visual/include/FaceJoint.h b/codes/visual/include/FaceJoint.h index ee817d900..c97b13ae6 100644 --- a/codes/visual/include/FaceJoint.h +++ b/codes/visual/include/FaceJoint.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/visual/include/HeatFlux.h b/codes/visual/include/HeatFlux.h index eb2e623c2..34a4c4ffa 100644 --- a/codes/visual/include/HeatFlux.h +++ b/codes/visual/include/HeatFlux.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/visual/include/HeatFluxTask.h b/codes/visual/include/HeatFluxTask.h index dcbc2afb6..2ceed9e7b 100644 --- a/codes/visual/include/HeatFluxTask.h +++ b/codes/visual/include/HeatFluxTask.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/visual/include/HeatFluxTaskReg.h b/codes/visual/include/HeatFluxTaskReg.h index bb51cb0a8..34826b1db 100644 --- a/codes/visual/include/HeatFluxTaskReg.h +++ b/codes/visual/include/HeatFluxTaskReg.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/visual/include/LaminarPlate.h b/codes/visual/include/LaminarPlate.h index 567d8e96f..1a24fdd0f 100644 --- a/codes/visual/include/LaminarPlate.h +++ b/codes/visual/include/LaminarPlate.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/visual/include/NodeField.h b/codes/visual/include/NodeField.h index 3bc54f560..de48acf93 100644 --- a/codes/visual/include/NodeField.h +++ b/codes/visual/include/NodeField.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/visual/include/Plate.h b/codes/visual/include/Plate.h index ae84df868..5befe950c 100644 --- a/codes/visual/include/Plate.h +++ b/codes/visual/include/Plate.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/visual/include/TurbPlate.h b/codes/visual/include/TurbPlate.h index b08bb4aea..580547e3a 100644 --- a/codes/visual/include/TurbPlate.h +++ b/codes/visual/include/TurbPlate.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/visual/include/VisualTaskReg.h b/codes/visual/include/VisualTaskReg.h index 2c7b0b384..b4d72b4ae 100644 --- a/codes/visual/include/VisualTaskReg.h +++ b/codes/visual/include/VisualTaskReg.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/visual/include/Visualize.h b/codes/visual/include/Visualize.h index 06fbb72d5..8cdbc9f43 100644 --- a/codes/visual/include/Visualize.h +++ b/codes/visual/include/Visualize.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/visual/include/WallVisual.h b/codes/visual/include/WallVisual.h index 12989cbfb..baf90bed4 100644 --- a/codes/visual/include/WallVisual.h +++ b/codes/visual/include/WallVisual.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/visual/src/FaceJoint.cpp b/codes/visual/src/FaceJoint.cpp index d5af8dc70..48cbbdd28 100644 --- a/codes/visual/src/FaceJoint.cpp +++ b/codes/visual/src/FaceJoint.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/visual/src/HeatFlux.cpp b/codes/visual/src/HeatFlux.cpp index c9dea2373..4afb90139 100644 --- a/codes/visual/src/HeatFlux.cpp +++ b/codes/visual/src/HeatFlux.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/visual/src/HeatFluxTask.cpp b/codes/visual/src/HeatFluxTask.cpp index e19c82954..0c146d4f2 100644 --- a/codes/visual/src/HeatFluxTask.cpp +++ b/codes/visual/src/HeatFluxTask.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/visual/src/HeatFluxTaskReg.cpp b/codes/visual/src/HeatFluxTaskReg.cpp index 08a2b4a9f..d8a52ad5f 100644 --- a/codes/visual/src/HeatFluxTaskReg.cpp +++ b/codes/visual/src/HeatFluxTaskReg.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/visual/src/LaminarPlate.cpp b/codes/visual/src/LaminarPlate.cpp index aea132e1a..2c1707b80 100644 --- a/codes/visual/src/LaminarPlate.cpp +++ b/codes/visual/src/LaminarPlate.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/visual/src/NodeField.cpp b/codes/visual/src/NodeField.cpp index 60192c1c0..045ae25aa 100644 --- a/codes/visual/src/NodeField.cpp +++ b/codes/visual/src/NodeField.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/visual/src/Plate.cpp b/codes/visual/src/Plate.cpp index dca1dc6f2..e9824ea03 100644 --- a/codes/visual/src/Plate.cpp +++ b/codes/visual/src/Plate.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/visual/src/TurbPlate.cpp b/codes/visual/src/TurbPlate.cpp index d2dd541a3..44d0d367a 100644 --- a/codes/visual/src/TurbPlate.cpp +++ b/codes/visual/src/TurbPlate.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/visual/src/VisualTaskReg.cpp b/codes/visual/src/VisualTaskReg.cpp index ad967dabc..137e6e60d 100644 --- a/codes/visual/src/VisualTaskReg.cpp +++ b/codes/visual/src/VisualTaskReg.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/visual/src/Visualize.cpp b/codes/visual/src/Visualize.cpp index 0e78c44c5..16ef26266 100644 --- a/codes/visual/src/Visualize.cpp +++ b/codes/visual/src/Visualize.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/visual/src/WallVisual.cpp b/codes/visual/src/WallVisual.cpp index 36c9d728e..c0ef665e2 100644 --- a/codes/visual/src/WallVisual.cpp +++ b/codes/visual/src/WallVisual.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/zone/include/GridGroup.h b/codes/zone/include/GridGroup.h index d77ad0807..5c5b1b9c1 100644 --- a/codes/zone/include/GridGroup.h +++ b/codes/zone/include/GridGroup.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/zone/include/MultiBlock.h b/codes/zone/include/MultiBlock.h index c47fdf4d4..43af86cd8 100644 --- a/codes/zone/include/MultiBlock.h +++ b/codes/zone/include/MultiBlock.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/zone/include/Zone.h b/codes/zone/include/Zone.h index 4afe7349c..1d8ba96e1 100644 --- a/codes/zone/include/Zone.h +++ b/codes/zone/include/Zone.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/zone/include/ZoneState.h b/codes/zone/include/ZoneState.h index 98d484ecc..dee99a31f 100644 --- a/codes/zone/include/ZoneState.h +++ b/codes/zone/include/ZoneState.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/zone/src/GridGroup.cpp b/codes/zone/src/GridGroup.cpp index 6c6a74283..fc78376b0 100644 --- a/codes/zone/src/GridGroup.cpp +++ b/codes/zone/src/GridGroup.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/zone/src/MultiBlock.cpp b/codes/zone/src/MultiBlock.cpp index 20868fdf7..7bdc34562 100644 --- a/codes/zone/src/MultiBlock.cpp +++ b/codes/zone/src/MultiBlock.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/zone/src/Zone.cpp b/codes/zone/src/Zone.cpp index 355ac9a1d..3e4af195c 100644 --- a/codes/zone/src/Zone.cpp +++ b/codes/zone/src/Zone.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/codes/zone/src/ZoneState.cpp b/codes/zone/src/ZoneState.cpp index 3f5a94ace..d9b3e7f72 100644 --- a/codes/zone/src/ZoneState.cpp +++ b/codes/zone/src/ZoneState.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/example/1d-linear-convection/weno3/fortran/FortranLibTest/01/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/FortranLibTest/01/CMakeLists.txt new file mode 100644 index 000000000..a31a8fae4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/FortranLibTest/01/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.18) +project(FortranLibTest VERSION 1.0 LANGUAGES Fortran) + +# 设置Fortran标准 +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +set(CMAKE_Fortran_MODULE_DIRECTORY "${CMAKE_BINARY_DIR}/modules") +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +# 添加库目录和测试目录 +add_subdirectory(src) +add_subdirectory(tests) diff --git a/example/1d-linear-convection/weno3/fortran/FortranLibTest/01/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/FortranLibTest/01/src/CMakeLists.txt new file mode 100644 index 000000000..29a6e9fed --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/FortranLibTest/01/src/CMakeLists.txt @@ -0,0 +1,2 @@ +# 构建静态库(核心:生成mymath.lib(Windows)/libmymath.a(Linux)) +add_library(mymath STATIC mymath.f90) diff --git a/example/1d-linear-convection/weno3/fortran/FortranLibTest/01/src/mymath.f90 b/example/1d-linear-convection/weno3/fortran/FortranLibTest/01/src/mymath.f90 new file mode 100644 index 000000000..11d02043a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/FortranLibTest/01/src/mymath.f90 @@ -0,0 +1,24 @@ +! 简单的数学库:提供加法和乘法函数 +module mymath_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + private + public :: add, multiply + +contains + + ! 实数加法 + function add(a, b) result(res) + real(real64), intent(in) :: a, b + real(real64) :: res + res = a + b + end function add + + ! 实数乘法 + function multiply(a, b) result(res) + real(real64), intent(in) :: a, b + real(real64) :: res + res = a * b + end function multiply + +end module mymath_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/FortranLibTest/01/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/FortranLibTest/01/tests/CMakeLists.txt new file mode 100644 index 000000000..209e0775f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/FortranLibTest/01/tests/CMakeLists.txt @@ -0,0 +1,7 @@ +# 构建测试可执行文件(依赖mymath库) +add_executable(test_mymath test_mymath.f90) + +# 关键:链接mymath库(自动继承mymath的公共包含目录,无需额外指定模块目录) +target_link_libraries(test_mymath PUBLIC mymath) + + diff --git a/example/1d-linear-convection/weno3/fortran/FortranLibTest/01/tests/test_mymath.f90 b/example/1d-linear-convection/weno3/fortran/FortranLibTest/01/tests/test_mymath.f90 new file mode 100644 index 000000000..ffc5b2b7e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/FortranLibTest/01/tests/test_mymath.f90 @@ -0,0 +1,33 @@ +! 测试程序:调用mymath库的函数,验证功能 +program test_mymath + use iso_fortran_env, only: real64 ! 显式引入,避免模块依赖缺失导致的real64未定义 + use mymath_module + implicit none + real(real64) :: a, b, sum_res, mul_res + + ! 测试数据 + a = 2.5_real64 + b = 4.0_real64 + + ! 调用库函数 + sum_res = add(a, b) + mul_res = multiply(a, b) + + ! 输出结果 + print *, "=== Testing mymath library ===" + print *, "a = ", a + print *, "b = ", b + print *, "a + b = ", sum_res + print *, "a * b = ", mul_res + print *, "=== Test completed ===" + + ! 简单验证(确保结果正确) + if (abs(sum_res - 6.5_real64) < 1e-10_real64 .and. & + abs(mul_res - 10.0_real64) < 1e-10_real64) then + print *, "✓ Test passed!" + else + print *, "✗ Test failed!" + stop 1 + end if + +end program test_mymath \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/FortranLibTest/01a/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/FortranLibTest/01a/CMakeLists.txt new file mode 100644 index 000000000..a31a8fae4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/FortranLibTest/01a/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.18) +project(FortranLibTest VERSION 1.0 LANGUAGES Fortran) + +# 设置Fortran标准 +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +set(CMAKE_Fortran_MODULE_DIRECTORY "${CMAKE_BINARY_DIR}/modules") +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +# 添加库目录和测试目录 +add_subdirectory(src) +add_subdirectory(tests) diff --git a/example/1d-linear-convection/weno3/fortran/FortranLibTest/01a/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/FortranLibTest/01a/src/CMakeLists.txt new file mode 100644 index 000000000..29a6e9fed --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/FortranLibTest/01a/src/CMakeLists.txt @@ -0,0 +1,2 @@ +# 构建静态库(核心:生成mymath.lib(Windows)/libmymath.a(Linux)) +add_library(mymath STATIC mymath.f90) diff --git a/example/1d-linear-convection/weno3/fortran/FortranLibTest/01a/src/mymath.f90 b/example/1d-linear-convection/weno3/fortran/FortranLibTest/01a/src/mymath.f90 new file mode 100644 index 000000000..b8379ec70 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/FortranLibTest/01a/src/mymath.f90 @@ -0,0 +1,21 @@ +module mymath_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + private + public :: add, multiply + +contains + + function add(a, b) result(res) + real(real64), intent(in) :: a, b + real(real64) :: res + res = a + b + end function add + + function multiply(a, b) result(res) + real(real64), intent(in) :: a, b + real(real64) :: res + res = a * b + end function multiply + +end module mymath_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/FortranLibTest/01a/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/FortranLibTest/01a/tests/CMakeLists.txt new file mode 100644 index 000000000..209e0775f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/FortranLibTest/01a/tests/CMakeLists.txt @@ -0,0 +1,7 @@ +# 构建测试可执行文件(依赖mymath库) +add_executable(test_mymath test_mymath.f90) + +# 关键:链接mymath库(自动继承mymath的公共包含目录,无需额外指定模块目录) +target_link_libraries(test_mymath PUBLIC mymath) + + diff --git a/example/1d-linear-convection/weno3/fortran/FortranLibTest/01a/tests/test_mymath.f90 b/example/1d-linear-convection/weno3/fortran/FortranLibTest/01a/tests/test_mymath.f90 new file mode 100644 index 000000000..ffc5b2b7e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/FortranLibTest/01a/tests/test_mymath.f90 @@ -0,0 +1,33 @@ +! 测试程序:调用mymath库的函数,验证功能 +program test_mymath + use iso_fortran_env, only: real64 ! 显式引入,避免模块依赖缺失导致的real64未定义 + use mymath_module + implicit none + real(real64) :: a, b, sum_res, mul_res + + ! 测试数据 + a = 2.5_real64 + b = 4.0_real64 + + ! 调用库函数 + sum_res = add(a, b) + mul_res = multiply(a, b) + + ! 输出结果 + print *, "=== Testing mymath library ===" + print *, "a = ", a + print *, "b = ", b + print *, "a + b = ", sum_res + print *, "a * b = ", mul_res + print *, "=== Test completed ===" + + ! 简单验证(确保结果正确) + if (abs(sum_res - 6.5_real64) < 1e-10_real64 .and. & + abs(mul_res - 10.0_real64) < 1e-10_real64) then + print *, "✓ Test passed!" + else + print *, "✗ Test failed!" + stop 1 + end if + +end program test_mymath \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/cfd/01/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/cfd/01/CMakeLists.txt new file mode 100644 index 000000000..aaa021b36 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/cfd/01/CMakeLists.txt @@ -0,0 +1,27 @@ +# CMakeLists.txt +cmake_minimum_required(VERSION 4.2.1) # 更高版本更好支持Fortran + +project(OneFLOW_CFD LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +# 按依赖顺序显式列出源文件(解决 Intel + VS 编译顺序问题) +set(SOURCES + cfd_solver.f90 +) + +add_executable(oneflow_cfd ${SOURCES}) + +# 包含模块目录(解决 .mod 未找到) +target_include_directories(oneflow_cfd PRIVATE ${CMAKE_Fortran_MODULE_DIRECTORY}) + +# Intel 特定(可选,链接数学库) +if(CMAKE_Fortran_COMPILER_ID MATCHES "Intel") + target_link_libraries(oneflow_cfd m) +endif() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/cfd/01/cfd_solver.f90 b/example/1d-linear-convection/weno3/fortran/cfd/01/cfd_solver.f90 new file mode 100644 index 000000000..bc4a3366d --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/cfd/01/cfd_solver.f90 @@ -0,0 +1,900 @@ +! OneFLOW-CFD Solver for 1D Convection Equation +! ENO/WENO Reconstruction Comparison - Single File Implementation + +module cfd_solver + use, intrinsic :: iso_fortran_env, only: dp => real64 + implicit none + + private + public :: CfdConfigType, MeshType, ComputationalDomainType, & + SolutionType, CfdType, run_simulation, init_field, & + analytical_solution, performEnoWenoAnalysis + + ! =================================================================== + ! Type Definitions + ! =================================================================== + + ! Configuration parameters + type :: CfdConfigType + character(len=10) :: recon_scheme = "eno" + integer :: flux_type = 0 + integer :: rk_order = 1 + integer :: spatial_order = 3 + real(dp) :: wave_speed = 1.0_dp + real(dp) :: final_time = 0.625_dp + real(dp) :: dt = 0.025_dp + end type CfdConfigType + + ! Mesh definition + type :: MeshType + real(dp) :: xmin = 0.0_dp + real(dp) :: xmax = 2.0_dp + integer :: ncells = 40 + integer :: nnodes + integer :: nx + real(dp) :: L, dx + real(dp), allocatable :: x(:), xcc(:) + contains + procedure :: init => mesh_init + end type MeshType + + ! Computational domain + type :: ComputationalDomainType + type(MeshType) :: mesh + type(CfdConfigType) :: config + integer :: nghosts, ist, ied, ntcells + contains + procedure :: init => domain_init + end type ComputationalDomainType + + ! Solution arrays + type :: SolutionType + type(ComputationalDomainType) :: domain + real(dp), allocatable :: q_face_left(:), q_face_right(:) + real(dp), allocatable :: flux(:), res(:) + real(dp), allocatable :: u(:), un(:) + contains + procedure :: init => solution_init + end type SolutionType + + ! Abstract reconstructor base class + type, abstract :: ReconstructorType + contains + procedure(reconstruct_interface), deferred :: reconstruct + end type ReconstructorType + + abstract interface + subroutine reconstruct_interface(this, q, cfd) + import :: ReconstructorType, CfdType, dp + class(ReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + end subroutine + end interface + + ! ENO reconstructor + type, extends(ReconstructorType) :: EnoReconstructorType + integer :: spatial_order + integer :: ntcells + integer, allocatable :: lmc(:) + real(dp), allocatable :: coef(:,:) + real(dp), allocatable :: dd(:,:) + contains + procedure :: reconstruct => eno_reconstruct + procedure :: init => eno_init + end type EnoReconstructorType + + ! WENO reconstructor + type, extends(ReconstructorType) :: WenoReconstructorType + contains + procedure :: reconstruct => weno_reconstruct + end type WenoReconstructorType + + ! Main CFD solver class + type :: CfdType + type(CfdConfigType) :: config + type(ComputationalDomainType) :: domain + type(SolutionType) :: solution + class(ReconstructorType), allocatable :: reconstructor + contains + procedure :: init => cfd_init + end type CfdType + + ! =================================================================== + ! Module Variables + ! =================================================================== + real(dp), parameter :: eps_weno = 1.0e-6_dp + +contains + + ! =================================================================== + ! Initialization Methods + ! =================================================================== + + ! Mesh initialization + subroutine mesh_init(this) + class(MeshType), intent(inout) :: this + integer :: i + + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, dp) + + allocate(this%x(this%nnodes), this%xcc(this%ncells)) + + ! Node coordinates + do i = 1, this%nnodes + this%x(i) = this%xmin + (i-1) * this%dx + end do + + ! Cell center coordinates + do i = 1, this%ncells + this%xcc(i) = 0.5_dp * (this%x(i) + this%x(i+1)) + end do + end subroutine mesh_init + + ! Domain initialization + subroutine domain_init(this, mesh, config) + class(ComputationalDomainType), intent(inout) :: this + type(MeshType), intent(in) :: mesh + type(CfdConfigType), intent(in) :: config + + this%mesh = mesh + this%config = config + + ! Calculate ghost cells + if (trim(config%recon_scheme) == "eno") then + this%nghosts = config%spatial_order + else if (trim(config%recon_scheme) == "weno") then + this%nghosts = config%spatial_order / 2 + 1 + else + error stop "Unknown reconstruction scheme" + end if + + this%ist = this%nghosts + 1 + this%ied = this%ist + mesh%ncells - 1 + this%ntcells = mesh%ncells + 2 * this%nghosts + + print *, "Domain initialized:" + print *, " mesh.ncells = ", mesh%ncells + print *, " spatial_order = ", config%spatial_order + print *, " nghosts = ", this%nghosts + print *, " ist = ", this%ist, ", ied = ", this%ied + end subroutine domain_init + + ! Solution initialization + subroutine solution_init(this, domain) + class(SolutionType), intent(inout) :: this + type(ComputationalDomainType), intent(in) :: domain + + this%domain = domain + + allocate(this%q_face_left(domain%mesh%nnodes)) + allocate(this%q_face_right(domain%mesh%nnodes)) + allocate(this%flux(domain%mesh%nnodes)) + allocate(this%res(domain%mesh%ncells)) + allocate(this%u(domain%ntcells)) + allocate(this%un(domain%ntcells)) + + this%q_face_left = 0.0_dp + this%q_face_right = 0.0_dp + this%flux = 0.0_dp + this%res = 0.0_dp + this%u = 0.0_dp + this%un = 0.0_dp + end subroutine solution_init + + ! ENO reconstructor initialization + subroutine eno_init(this, spatial_order, ntcells) + class(EnoReconstructorType), intent(inout) :: this + integer, intent(in) :: spatial_order + integer, intent(in) :: ntcells + + this%spatial_order = spatial_order + this%ntcells = ntcells + + allocate(this%lmc(ntcells)) + allocate(this%coef(spatial_order+1, spatial_order)) + allocate(this%dd(spatial_order, ntcells)) + + this%lmc = 0 + this%coef = 0.0_dp + this%dd = 0.0_dp + + ! Initialize coefficients + call init_coef(spatial_order, this%coef) + end subroutine eno_init + + ! CFD solver initialization + subroutine cfd_init(this, config, domain) + class(CfdType), intent(inout) :: this + type(CfdConfigType), intent(in) :: config + type(ComputationalDomainType), intent(in) :: domain + + this%config = config + this%domain = domain + call this%solution%init(domain) + + ! Create reconstructor based on scheme + if (trim(config%recon_scheme) == "eno") then + allocate(EnoReconstructorType :: this%reconstructor) + select type(rec => this%reconstructor) + type is (EnoReconstructorType) + call rec%init(config%spatial_order, domain%ntcells) + end select + else if (trim(config%recon_scheme) == "weno") then + allocate(WenoReconstructorType :: this%reconstructor) + else + error stop "Unknown reconstruction scheme" + end if + end subroutine cfd_init + + ! =================================================================== + ! Initial Conditions and Analytical Solution + ! =================================================================== + + ! Initial condition: step function + function initial_condition(x) result(u0) + real(dp), intent(in) :: x + real(dp) :: u0 + + if (0.5_dp <= x .and. x <= 1.0_dp) then + u0 = 2.0_dp + else + u0 = 1.0_dp + end if + end function initial_condition + + ! Analytical solution with periodic BC + function analytical_solution(x, t, a, L) result(u) + real(dp), intent(in) :: x, t, a, L + real(dp) :: u, x_shifted + + x_shifted = mod(x - a * t + L, L) + u = initial_condition(x_shifted) + end function analytical_solution + + ! Initialize field with step function + subroutine init_field(cfd) + type(CfdType), intent(inout) :: cfd + type(ComputationalDomainType) :: domain + type(SolutionType) :: solution + integer :: i, j + + domain = cfd%domain + solution = cfd%solution + + do i = domain%ist, domain%ied + j = i - domain%ist + 1 + if (0.5_dp <= domain%mesh%xcc(j) .and. domain%mesh%xcc(j) <= 1.0_dp) then + solution%u(i) = 2.0_dp + else + solution%u(i) = 1.0_dp + end if + end do + + call boundary(solution%u, cfd) + call update_oldfield(solution%un, solution%u, domain%ntcells) + end subroutine init_field + + ! =================================================================== + ! Boundary Conditions + ! =================================================================== + + ! Periodic boundary conditions + subroutine periodic_boundary(u, cfd) + real(dp), intent(inout) :: u(:) + type(CfdType), intent(in) :: cfd + type(ComputationalDomainType) :: domain + integer :: ig + + domain = cfd%domain + + ! Left ghost cells = right interior cells + do ig = 1, domain%nghosts + u(domain%ist - ig) = u(domain%ied - ig) + end do + + ! Right ghost cells = left interior cells + do ig = 1, domain%nghosts + u(domain%ied + ig) = u(domain%ist + ig - 1) + end do + end subroutine periodic_boundary + + ! Boundary condition wrapper + subroutine boundary(u, cfd) + real(dp), intent(inout) :: u(:) + type(CfdType), intent(in) :: cfd + + call periodic_boundary(u, cfd) + end subroutine boundary + + ! =================================================================== + ! Reconstruction Methods + ! =================================================================== + + ! Initialize ENO/WENO coefficients + subroutine init_coef(spatial_order, coef) + integer, intent(in) :: spatial_order + real(dp), intent(out) :: coef(:,:) + + coef = 0.0_dp + + select case(spatial_order) + case(1) + coef(1,1) = 1.0_dp + coef(2,1) = 1.0_dp + + case(2) + coef(1,1:2) = [ 3.0_dp/2.0_dp, -1.0_dp/2.0_dp ] + coef(2,1:2) = [ 1.0_dp/2.0_dp, 1.0_dp/2.0_dp ] + coef(3,1:2) = [ -1.0_dp/2.0_dp, 3.0_dp/2.0_dp ] + + case(3) + coef(1,1:3) = [ 11.0_dp/6.0_dp, -7.0_dp/6.0_dp, 1.0_dp/3.0_dp ] + coef(2,1:3) = [ 1.0_dp/3.0_dp, 5.0_dp/6.0_dp, -1.0_dp/6.0_dp ] + coef(3,1:3) = [ -1.0_dp/6.0_dp, 5.0_dp/6.0_dp, 1.0_dp/3.0_dp ] + coef(4,1:3) = [ 1.0_dp/3.0_dp, -7.0_dp/6.0_dp, 11.0_dp/6.0_dp ] + + case(4) + coef(1,1:4) = [ 25.0_dp/12.0_dp, -23.0_dp/12.0_dp, 13.0_dp/12.0_dp, -1.0_dp/4.0_dp ] + coef(2,1:4) = [ 1.0_dp/4.0_dp, 13.0_dp/12.0_dp, -5.0_dp/12.0_dp, 1.0_dp/12.0_dp ] + coef(3,1:4) = [ -1.0_dp/12.0_dp, 7.0_dp/12.0_dp, 7.0_dp/12.0_dp, -1.0_dp/12.0_dp ] + coef(4,1:4) = [ 1.0_dp/12.0_dp, -5.0_dp/12.0_dp, 13.0_dp/12.0_dp, 1.0_dp/4.0_dp ] + coef(5,1:4) = [ -1.0_dp/4.0_dp, 13.0_dp/12.0_dp, -23.0_dp/12.0_dp, 25.0_dp/12.0_dp ] + + case default + error stop "Unsupported spatial order" + end select + end subroutine init_coef + + ! ENO reconstruction + subroutine eno_reconstruct(this, q, cfd) + class(EnoReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + + type(ComputationalDomainType) :: domain + type(SolutionType) :: solution + integer :: i, j, m, k1, k2, r1, r2 + + domain = cfd%domain + solution = cfd%solution + + ! Compute divided differences + this%dd(1, :) = q + + do m = 2, this%spatial_order + do j = 1, this%ntcells - m + 1 + this%dd(m, j) = this%dd(m-1, j+1) - this%dd(m-1, j) + end do + end do + + ! Select left-biased stencil for each node + do i = domain%ist-1, domain%ied + this%lmc(i) = i + do m = 2, this%spatial_order + if (abs(this%dd(m, this%lmc(i)-1)) < abs(this%dd(m, this%lmc(i)))) then + this%lmc(i) = this%lmc(i) - 1 + end if + end do + end do + + ! Reconstruct values at cell interfaces + do i = domain%ist, domain%ied + j = i - domain%ist + 1 + k1 = this%lmc(i-1) + k2 = this%lmc(i) + r1 = i-1 - k1 + r2 = i - k2 + + solution%q_face_left(j) = 0.0_dp + solution%q_face_right(j) = 0.0_dp + + do m = 1, this%spatial_order + solution%q_face_left(j) = solution%q_face_left(j) + & + q(k1 + m - 1) * this%coef(r1+2, m) + solution%q_face_right(j) = solution%q_face_right(j) + & + q(k2 + m - 1) * this%coef(r2+1, m) + end do + end do + end subroutine eno_reconstruct + + ! WENO-3 nonlinear weights for left-biased stencil + function wc3L(v1, v2, v3) result(f) + real(dp), intent(in) :: v1, v2, v3 + real(dp) :: f, s0, s1, d0, d1, c0, c1, w0, w1, q0, q1 + + ! Smoothness indicators + s0 = (v3 - v2)**2 + s1 = (v2 - v1)**2 + + ! Nonlinear weights + d0 = 2.0_dp/3.0_dp + d1 = 1.0_dp/3.0_dp + + c0 = d0 / ((eps_weno + s0)**2) + c1 = d1 / ((eps_weno + s1)**2) + + w0 = c0 / (c0 + c1) + w1 = c1 / (c0 + c1) + + ! Candidate stencils + q0 = 0.5_dp * v2 + 0.5_dp * v3 + q1 = -0.5_dp * v1 + 1.5_dp * v2 + + ! Reconstructed value + f = w0 * q0 + w1 * q1 + end function wc3L + + ! WENO-3 nonlinear weights for right-biased stencil + function wc3R(v1, v2, v3) result(f) + real(dp), intent(in) :: v1, v2, v3 + real(dp) :: f, s0, s1, d0, d1, c0, c1, w0, w1, q0, q1 + + ! Smoothness indicators + s0 = (v2 - v1)**2 + s1 = (v3 - v2)**2 + + ! Nonlinear weights + d0 = 2.0_dp/3.0_dp + d1 = 1.0_dp/3.0_dp + + c0 = d0 / ((eps_weno + s0)**2) + c1 = d1 / ((eps_weno + s1)**2) + + w0 = c0 / (c0 + c1) + w1 = c1 / (c0 + c1) + + ! Candidate stencils + q0 = 0.5_dp * v1 + 0.5_dp * v2 + q1 = 1.5_dp * v2 - 0.5_dp * v3 + + ! Reconstructed value + f = w0 * q0 + w1 * q1 + end function wc3R + + ! WENO-3 reconstruction for left interface + subroutine weno3L_periodic(cfd, u, f) + type(CfdType), intent(in) :: cfd + real(dp), intent(in) :: u(:) + real(dp), intent(out) :: f(:) + + type(ComputationalDomainType) :: domain + integer :: i, j + + domain = cfd%domain + + do i = domain%ist-1, domain%ied-1 + j = i - (domain%ist - 1) + f(j) = wc3L(u(i-1), u(i), u(i+1)) + end do + end subroutine weno3L_periodic + + ! WENO-3 reconstruction for right interface + subroutine weno3R_periodic(cfd, u, f) + type(CfdType), intent(in) :: cfd + real(dp), intent(in) :: u(:) + real(dp), intent(out) :: f(:) + + type(ComputationalDomainType) :: domain + integer :: i, j + + domain = cfd%domain + + do i = domain%ist, domain%ied + j = i - domain%ist + 1 + f(j) = wc3R(u(i-1), u(i), u(i+1)) + end do + end subroutine weno3R_periodic + + ! WENO reconstruction + subroutine weno_reconstruct(this, q, cfd) + class(WenoReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + + call weno3L_periodic(cfd, q, cfd%solution%q_face_left) + call weno3R_periodic(cfd, q, cfd%solution%q_face_right) + end subroutine weno_reconstruct + + ! General reconstruction wrapper + subroutine reconstruction(q, cfd) + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + + call cfd%reconstructor%reconstruct(q, cfd) + end subroutine reconstruction + + ! =================================================================== + ! Flux Functions + ! =================================================================== + + ! Rusanov flux + subroutine rusanov_flux(q_face_left, q_face_right, flux, cfd) + real(dp), intent(in) :: q_face_left(:), q_face_right(:) + real(dp), intent(out) :: flux(:) + type(CfdType), intent(in) :: cfd + + integer :: i + real(dp) :: u_L, u_R, F_L, F_R, c_L, c_R, Smax + + c_L = cfd%config%wave_speed + c_R = cfd%config%wave_speed + + do i = 1, cfd%domain%mesh%nnodes + u_L = q_face_left(i) + u_R = q_face_right(i) + F_L = c_L * u_L + F_R = c_R * u_R + Smax = max(abs(c_L), abs(c_R)) + flux(i) = 0.5_dp * (F_L + F_R) - 0.5_dp * Smax * (u_R - u_L) + end do + end subroutine rusanov_flux + + ! Engquist-Osher flux + subroutine engquist_osher_flux(q_face_left, q_face_right, flux, cfd) + real(dp), intent(in) :: q_face_left(:), q_face_right(:) + real(dp), intent(out) :: flux(:) + type(CfdType), intent(in) :: cfd + + integer :: i + real(dp) :: c, cp, cm, u_L, u_R + + c = cfd%config%wave_speed + + do i = 1, cfd%domain%mesh%nnodes + cp = 0.5_dp * (c + abs(c)) + cm = 0.5_dp * (c - abs(c)) + u_L = q_face_left(i) + u_R = q_face_right(i) + flux(i) = cp * u_L + cm * u_R + end do + end subroutine engquist_osher_flux + + ! Inviscid flux selection + subroutine inviscid_flux(q_face_left, q_face_right, flux, cfd) + real(dp), intent(in) :: q_face_left(:), q_face_right(:) + real(dp), intent(out) :: flux(:) + type(CfdType), intent(in) :: cfd + + if (cfd%config%flux_type == 0) then + call rusanov_flux(q_face_left, q_face_right, flux, cfd) + else + call engquist_osher_flux(q_face_left, q_face_right, flux, cfd) + end if + end subroutine inviscid_flux + + ! =================================================================== + ! Residual Computation + ! =================================================================== + + ! Compute residual (flux divergence) + subroutine residual(q, cfd) + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + + type(SolutionType) :: solution + type(ComputationalDomainType) :: domain + type(MeshType) :: mesh + integer :: i + + solution = cfd%solution + domain = cfd%domain + mesh = domain%mesh + + ! Reconstruction + call reconstruction(q, cfd) + + ! Compute fluxes + call inviscid_flux(solution%q_face_left, solution%q_face_right, & + solution%flux, cfd) + + ! Compute residual + do i = 1, mesh%ncells + solution%res(i) = -(solution%flux(i+1) - solution%flux(i)) / mesh%dx + end do + end subroutine residual + + ! =================================================================== + ! Time Integration + ! =================================================================== + + ! Update old field + subroutine update_oldfield(qn, q, n) + real(dp), intent(out) :: qn(:) + real(dp), intent(in) :: q(:) + integer, intent(in) :: n + + qn(1:n) = q(1:n) + end subroutine update_oldfield + + ! 1st-order Runge-Kutta (Euler) + subroutine runge_kutta_1(cfd) + type(CfdType), intent(inout) :: cfd + + type(SolutionType) :: solution + type(ComputationalDomainType) :: domain + integer :: i, j + real(dp) :: dt + + solution = cfd%solution + domain = cfd%domain + dt = cfd%config%dt + + call residual(solution%u, cfd) + + do i = domain%ist, domain%ied + j = i - domain%ist + 1 + solution%u(i) = solution%u(i) + dt * solution%res(j) + end do + + call boundary(solution%u, cfd) + call update_oldfield(solution%un, solution%u, domain%ntcells) + end subroutine runge_kutta_1 + + ! 2nd-order Runge-Kutta (Heun) + subroutine runge_kutta_2(cfd) + type(CfdType), intent(inout) :: cfd + + type(SolutionType) :: solution + type(ComputationalDomainType) :: domain + integer :: i, j + real(dp) :: dt + + solution = cfd%solution + domain = cfd%domain + dt = cfd%config%dt + + ! Stage 1 + call residual(solution%u, cfd) + do i = domain%ist, domain%ied + j = i - domain%ist + 1 + solution%u(i) = solution%u(i) + dt * solution%res(j) + end do + call boundary(solution%u, cfd) + + ! Stage 2 + call residual(solution%u, cfd) + do i = domain%ist, domain%ied + j = i - domain%ist + 1 + solution%u(i) = 0.5_dp * solution%un(i) + & + 0.5_dp * solution%u(i) + & + 0.5_dp * dt * solution%res(j) + end do + call boundary(solution%u, cfd) + + call update_oldfield(solution%un, solution%u, domain%ntcells) + end subroutine runge_kutta_2 + + ! 3rd-order Runge-Kutta (SSPRK3) + subroutine runge_kutta_3(cfd) + type(CfdType), intent(inout) :: cfd + + type(SolutionType) :: solution + type(ComputationalDomainType) :: domain + integer :: i, j + real(dp) :: dt + + solution = cfd%solution + domain = cfd%domain + dt = cfd%config%dt + + ! Stage 1 + call residual(solution%u, cfd) + do i = domain%ist, domain%ied + j = i - domain%ist + 1 + solution%u(i) = solution%u(i) + dt * solution%res(j) + end do + call boundary(solution%u, cfd) + + ! Stage 2 + call residual(solution%u, cfd) + do i = domain%ist, domain%ied + j = i - domain%ist + 1 + solution%u(i) = 0.75_dp * solution%un(i) + & + 0.25_dp * solution%u(i) + & + 0.25_dp * dt * solution%res(j) + end do + call boundary(solution%u, cfd) + + ! Stage 3 + call residual(solution%u, cfd) + do i = domain%ist, domain%ied + j = i - domain%ist + 1 + solution%u(i) = (1.0_dp/3.0_dp) * solution%un(i) + & + (2.0_dp/3.0_dp) * solution%u(i) + & + (2.0_dp/3.0_dp) * dt * solution%res(j) + end do + call boundary(solution%u, cfd) + + call update_oldfield(solution%un, solution%u, domain%ntcells) + end subroutine runge_kutta_3 + + ! Runge-Kutta selection + subroutine runge_kutta(cfd) + type(CfdType), intent(inout) :: cfd + + select case(cfd%config%rk_order) + case(1) + call runge_kutta_1(cfd) + case(2) + call runge_kutta_2(cfd) + case(3) + call runge_kutta_3(cfd) + case default + call runge_kutta_1(cfd) + end select + end subroutine runge_kutta + + ! =================================================================== + ! Simulation Driver + ! =================================================================== + + ! Run simulation to final time + function run_simulation(cfd, final_time) result(u_result) + type(CfdType), intent(inout) :: cfd + real(dp), intent(in) :: final_time + real(dp), allocatable :: u_result(:) + + type(ComputationalDomainType) :: domain + real(dp) :: t, dt, dt_old + integer :: ncells + + domain = cfd%domain + ncells = domain%mesh%ncells + allocate(u_result(ncells)) + + t = 0.0_dp + dt_old = cfd%config%dt + dt = dt_old + + do while (t < final_time - 1.0e-12_dp) + if (t + dt > final_time) then + dt = final_time - t + end if + cfd%config%dt = dt + call runge_kutta(cfd) + t = t + dt + end do + + cfd%config%dt = dt_old + + ! Extract physical solution (without ghost cells) + u_result = cfd%solution%u(domain%ist:domain%ied) + end function run_simulation + + ! =================================================================== + ! Main Analysis Function + ! =================================================================== + + ! Perform ENO-WENO comparative analysis + subroutine performEnoWenoAnalysis() + type(CfdConfigType) :: config_eno3, config_weno3 + type(MeshType) :: mesh + type(ComputationalDomainType) :: domain_eno3, domain_weno3 + type(CfdType) :: cfd_eno3, cfd_weno3 + real(dp), allocatable :: u_eno(:), u_weno(:), u_analytical(:) + real(dp), allocatable :: xcc(:) + integer :: i, ncells, iunit + + ! Initialize mesh + mesh = MeshType() + call mesh%init() + ncells = mesh%ncells + allocate(xcc(ncells)) + xcc = mesh%xcc + + ! Configure ENO3 + config_eno3%recon_scheme = "eno" + config_eno3%spatial_order = 3 + config_eno3%flux_type = 0 + config_eno3%rk_order = 1 + config_eno3%wave_speed = 1.0_dp + config_eno3%final_time = 0.625_dp + config_eno3%dt = 0.0025_dp + + ! Configure WENO3 + config_weno3%recon_scheme = "weno" + config_weno3%spatial_order = 3 + config_weno3%flux_type = 0 + config_weno3%rk_order = 1 + config_weno3%wave_speed = 1.0_dp + config_weno3%final_time = 0.625_dp + config_weno3%dt = 0.0025_dp + + ! Create domains + domain_eno3 = ComputationalDomainType() + domain_weno3 = ComputationalDomainType() + + call domain_eno3%init(mesh, config_eno3) + call domain_weno3%init(mesh, config_weno3) + + ! Create CFD solvers + cfd_eno3 = CfdType() + cfd_weno3 = CfdType() + + call cfd_eno3%init(config_eno3, domain_eno3) + call cfd_weno3%init(config_weno3, domain_weno3) + + ! Allocate arrays + allocate(u_eno(ncells), u_weno(ncells), u_analytical(ncells)) + + ! Run ENO simulation + print *, "Running ENO3 simulation..." + call init_field(cfd_eno3) + u_eno = run_simulation(cfd_eno3, config_eno3%final_time) + + ! Run WENO simulation + print *, "Running WENO3 simulation..." + call init_field(cfd_weno3) + u_weno = run_simulation(cfd_weno3, config_weno3%final_time) + + ! Compute analytical solution + print *, "Computing analytical solution..." + do i = 1, ncells + u_analytical(i) = analytical_solution(xcc(i), config_weno3%final_time, & + config_weno3%wave_speed, mesh%L) + end do + + ! Write results to files + print *, "Writing results to files..." + + ! Write ENO results + open(newunit=iunit, file='eno_results.txt', status='replace') + write(iunit, '(A)') '# x, u (ENO3)' + do i = 1, ncells + write(iunit, '(2F12.6)') xcc(i), u_eno(i) + end do + close(iunit) + + ! Write WENO results + open(newunit=iunit, file='weno_results.txt', status='replace') + write(iunit, '(A)') '# x, u (WENO3)' + do i = 1, ncells + write(iunit, '(2F12.6)') xcc(i), u_weno(i) + end do + close(iunit) + + ! Write analytical results + open(newunit=iunit, file='analytical_results.txt', status='replace') + write(iunit, '(A)') '# x, u (Analytical)' + do i = 1, ncells + write(iunit, '(2F12.6)') xcc(i), u_analytical(i) + end do + close(iunit) + + print *, "==========================================" + print *, "Simulation completed successfully!" + print *, "Results written to:" + print *, " eno_results.txt" + print *, " weno_results.txt" + print *, " analytical_results.txt" + print *, "" + print *, "To generate the comparison plot, run:" + print *, " python postprocess.py" + print *, "==========================================" + + deallocate(u_eno, u_weno, u_analytical, xcc) + end subroutine performEnoWenoAnalysis + +end module cfd_solver + +! =================================================================== +! Main Program +! =================================================================== +program main + use cfd_solver + implicit none + + print *, "==========================================" + print *, "OneFLOW-CFD Solver for 1D Convection" + print *, "ENO3 vs WENO3 Comparison" + print *, "==========================================" + + call performEnoWenoAnalysis() + + print *, "Program finished successfully!" + +end program main \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/cfd/01/postprocess.py b/example/1d-linear-convection/weno3/fortran/cfd/01/postprocess.py new file mode 100644 index 000000000..6cbcc4d9b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/cfd/01/postprocess.py @@ -0,0 +1,38 @@ +import numpy as np +import matplotlib.pyplot as plt + +def read_results(filename): + """Read results from Fortran output file""" + data = np.loadtxt(filename, comments='#') + return data[:, 0], data[:, 1] + +def main(): + # Read results + x_eno, u_eno = read_results('eno_results.txt') + x_weno, u_weno = read_results('weno_results.txt') + x_analytical, u_analytical = read_results('analytical_results.txt') + + # Create plot + plt.figure(figsize=(12, 8)) + plt.plot(x_eno, u_eno, 'bo-', linewidth=1.5, markersize=4, + markerfacecolor='none', label='ENO3 (Rusanov)') + plt.plot(x_weno, u_weno, 'gs-', linewidth=1.5, markersize=4, + markerfacecolor='none', label='WENO3 (Rusanov)') + plt.plot(x_analytical, u_analytical, 'r--', linewidth=2, label='Analytical') + + plt.title('1D Convection Equation: ENO3 vs WENO3 Comparison (t=0.625)', fontsize=14) + plt.xlabel('x', fontsize=12) + plt.ylabel('u', fontsize=12) + plt.legend(fontsize=12) + plt.grid(True, alpha=0.3) + plt.tight_layout() + + # Save figure + plt.savefig('eno_weno_comparison.png', dpi=300, bbox_inches='tight') + print("Plot saved as: eno_weno_comparison.png") + + # Show plot + plt.show() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/cfd/01a/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/cfd/01a/CMakeLists.txt new file mode 100644 index 000000000..aaa021b36 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/cfd/01a/CMakeLists.txt @@ -0,0 +1,27 @@ +# CMakeLists.txt +cmake_minimum_required(VERSION 4.2.1) # 更高版本更好支持Fortran + +project(OneFLOW_CFD LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +# 按依赖顺序显式列出源文件(解决 Intel + VS 编译顺序问题) +set(SOURCES + cfd_solver.f90 +) + +add_executable(oneflow_cfd ${SOURCES}) + +# 包含模块目录(解决 .mod 未找到) +target_include_directories(oneflow_cfd PRIVATE ${CMAKE_Fortran_MODULE_DIRECTORY}) + +# Intel 特定(可选,链接数学库) +if(CMAKE_Fortran_COMPILER_ID MATCHES "Intel") + target_link_libraries(oneflow_cfd m) +endif() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/cfd/01a/cfd_solver.f90 b/example/1d-linear-convection/weno3/fortran/cfd/01a/cfd_solver.f90 new file mode 100644 index 000000000..8f07a767d --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/cfd/01a/cfd_solver.f90 @@ -0,0 +1,873 @@ +! OneFLOW-CFD Solver for 1D Convection Equation +! ENO/WENO Reconstruction Comparison - Single File Implementation + +module cfd_solver + use, intrinsic :: iso_fortran_env, only: dp => real64 + implicit none + + private + + ! =================================================================== + ! Forward Type Declarations + ! =================================================================== + type, public :: CfdConfigType + character(len=10) :: recon_scheme = "eno" + integer :: flux_type = 0 + integer :: rk_order = 1 + integer :: spatial_order = 3 + real(dp) :: wave_speed = 1.0_dp + real(dp) :: final_time = 0.625_dp + real(dp) :: dt = 0.025_dp + end type CfdConfigType + + type, public :: MeshType + real(dp) :: xmin = 0.0_dp + real(dp) :: xmax = 2.0_dp + integer :: ncells = 40 + integer :: nnodes = 0 + integer :: nx = 0 + real(dp) :: L = 0.0_dp + real(dp) :: dx = 0.0_dp + real(dp), allocatable :: x(:), xcc(:) + contains + procedure :: init => mesh_init + end type MeshType + + type, public :: ComputationalDomainType + type(MeshType) :: mesh + type(CfdConfigType) :: config + integer :: nghosts = 0 + integer :: ist = 0 + integer :: ied = 0 + integer :: ntcells = 0 + contains + procedure :: init => domain_init + end type ComputationalDomainType + + type, public :: SolutionType + type(ComputationalDomainType) :: domain + real(dp), allocatable :: q_face_left(:), q_face_right(:) + real(dp), allocatable :: flux(:), res(:) + real(dp), allocatable :: u(:), un(:) + contains + procedure :: init => solution_init + end type SolutionType + + ! Abstract reconstructor base class with explicit interface + type, abstract, public :: ReconstructorType + contains + procedure(reconstruct_interface), deferred, pass :: reconstruct + end type ReconstructorType + + ! Define abstract interface BEFORE using it + abstract interface + subroutine reconstruct_interface(this, q, cfd) + import :: ReconstructorType, dp + class(ReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + class(*), intent(inout) :: cfd + end subroutine reconstruct_interface + end interface + + ! ENO reconstructor + type, extends(ReconstructorType), public :: EnoReconstructorType + integer :: spatial_order + integer :: ntcells + integer, allocatable :: lmc(:) + real(dp), allocatable :: coef(:,:) + real(dp), allocatable :: dd(:,:) + contains + procedure :: reconstruct => eno_reconstruct + procedure :: init => eno_init + end type EnoReconstructorType + + ! WENO reconstructor + type, extends(ReconstructorType), public :: WenoReconstructorType + contains + procedure :: reconstruct => weno_reconstruct + end type WenoReconstructorType + + ! Main CFD solver class + type, public :: CfdType + type(CfdConfigType) :: config + type(ComputationalDomainType) :: domain + type(SolutionType) :: solution + class(ReconstructorType), allocatable :: reconstructor + contains + procedure :: init => cfd_init + end type CfdType + + ! =================================================================== + ! Public Procedures + ! =================================================================== + public :: run_simulation, init_field, analytical_solution, performEnoWenoAnalysis + + ! =================================================================== + ! Module Variables + ! =================================================================== + real(dp), parameter :: eps_weno = 1.0e-6_dp + +contains + + ! =================================================================== + ! Initialization Methods + ! =================================================================== + + ! Mesh initialization + subroutine mesh_init(this) + class(MeshType), intent(inout) :: this + integer :: i + + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, dp) + + allocate(this%x(this%nnodes), this%xcc(this%ncells)) + + ! Node coordinates + do i = 1, this%nnodes + this%x(i) = this%xmin + (i-1) * this%dx + end do + + ! Cell center coordinates + do i = 1, this%ncells + this%xcc(i) = 0.5_dp * (this%x(i) + this%x(i+1)) + end do + end subroutine mesh_init + + ! Domain initialization + subroutine domain_init(this, mesh, config) + class(ComputationalDomainType), intent(inout) :: this + type(MeshType), intent(in) :: mesh + type(CfdConfigType), intent(in) :: config + + this%mesh = mesh + this%config = config + + ! Calculate ghost cells + if (trim(config%recon_scheme) == "eno") then + this%nghosts = config%spatial_order + else if (trim(config%recon_scheme) == "weno") then + this%nghosts = config%spatial_order / 2 + 1 + else + error stop "Unknown reconstruction scheme" + end if + + this%ist = this%nghosts + 1 + this%ied = this%ist + mesh%ncells - 1 + this%ntcells = mesh%ncells + 2 * this%nghosts + + print *, "Domain initialized:" + print *, " mesh.ncells = ", mesh%ncells + print *, " spatial_order = ", config%spatial_order + print *, " nghosts = ", this%nghosts + print *, " ist = ", this%ist, ", ied = ", this%ied + end subroutine domain_init + + ! Solution initialization + subroutine solution_init(this, domain) + class(SolutionType), intent(inout) :: this + type(ComputationalDomainType), intent(in) :: domain + + this%domain = domain + + allocate(this%q_face_left(domain%mesh%nnodes)) + allocate(this%q_face_right(domain%mesh%nnodes)) + allocate(this%flux(domain%mesh%nnodes)) + allocate(this%res(domain%mesh%ncells)) + allocate(this%u(domain%ntcells)) + allocate(this%un(domain%ntcells)) + + this%q_face_left = 0.0_dp + this%q_face_right = 0.0_dp + this%flux = 0.0_dp + this%res = 0.0_dp + this%u = 0.0_dp + this%un = 0.0_dp + end subroutine solution_init + + ! ENO reconstructor initialization + subroutine eno_init(this, spatial_order, ntcells) + class(EnoReconstructorType), intent(inout) :: this + integer, intent(in) :: spatial_order + integer, intent(in) :: ntcells + + this%spatial_order = spatial_order + this%ntcells = ntcells + + allocate(this%lmc(ntcells)) + allocate(this%coef(spatial_order+1, spatial_order)) + allocate(this%dd(spatial_order, ntcells)) + + this%lmc = 0 + this%coef = 0.0_dp + this%dd = 0.0_dp + + ! Initialize coefficients + call init_coef(spatial_order, this%coef) + end subroutine eno_init + + ! CFD solver initialization + subroutine cfd_init(this, config, domain) + class(CfdType), intent(inout) :: this + type(CfdConfigType), intent(in) :: config + type(ComputationalDomainType), intent(in) :: domain + + this%config = config + this%domain = domain + call this%solution%init(domain) + + ! Create reconstructor based on scheme + if (trim(config%recon_scheme) == "eno") then + allocate(EnoReconstructorType :: this%reconstructor) + select type(rec => this%reconstructor) + type is (EnoReconstructorType) + call rec%init(config%spatial_order, domain%ntcells) + end select + else if (trim(config%recon_scheme) == "weno") then + allocate(WenoReconstructorType :: this%reconstructor) + else + error stop "Unknown reconstruction scheme" + end if + end subroutine cfd_init + + ! =================================================================== + ! Initial Conditions and Analytical Solution + ! =================================================================== + + ! Initial condition: step function + function initial_condition(x) result(u0) + real(dp), intent(in) :: x + real(dp) :: u0 + + if (0.5_dp <= x .and. x <= 1.0_dp) then + u0 = 2.0_dp + else + u0 = 1.0_dp + end if + end function initial_condition + + ! Analytical solution with periodic BC + function analytical_solution(x, t, a, L) result(u) + real(dp), intent(in) :: x, t, a, L + real(dp) :: u, x_shifted + + x_shifted = mod(x - a * t + L, L) + u = initial_condition(x_shifted) + end function analytical_solution + + ! Initialize field with step function + subroutine init_field(cfd) + type(CfdType), intent(inout) :: cfd + integer :: i, j + + do i = cfd%domain%ist, cfd%domain%ied + j = i - cfd%domain%ist + 1 + if (0.5_dp <= cfd%domain%mesh%xcc(j) .and. cfd%domain%mesh%xcc(j) <= 1.0_dp) then + cfd%solution%u(i) = 2.0_dp + else + cfd%solution%u(i) = 1.0_dp + end if + end do + + call boundary(cfd%solution%u, cfd) + call update_oldfield(cfd%solution%un, cfd%solution%u, cfd%domain%ntcells) + end subroutine init_field + + ! =================================================================== + ! Boundary Conditions + ! =================================================================== + + ! Periodic boundary conditions + subroutine periodic_boundary(u, cfd) + real(dp), intent(inout) :: u(:) + type(CfdType), intent(in) :: cfd + integer :: ig + + ! Left ghost cells = right interior cells + do ig = 1, cfd%domain%nghosts + u(cfd%domain%ist - ig) = u(cfd%domain%ied - ig) + end do + + ! Right ghost cells = left interior cells + do ig = 1, cfd%domain%nghosts + u(cfd%domain%ied + ig) = u(cfd%domain%ist + ig - 1) + end do + end subroutine periodic_boundary + + ! Boundary condition wrapper + subroutine boundary(u, cfd) + real(dp), intent(inout) :: u(:) + type(CfdType), intent(in) :: cfd + + call periodic_boundary(u, cfd) + end subroutine boundary + + ! =================================================================== + ! Reconstruction Methods + ! =================================================================== + + ! Initialize ENO/WENO coefficients + subroutine init_coef(spatial_order, coef) + integer, intent(in) :: spatial_order + real(dp), intent(out) :: coef(:,:) + + coef = 0.0_dp + + select case(spatial_order) + case(1) + coef(1,1) = 1.0_dp + coef(2,1) = 1.0_dp + + case(2) + coef(1,1:2) = [ 3.0_dp/2.0_dp, -1.0_dp/2.0_dp ] + coef(2,1:2) = [ 1.0_dp/2.0_dp, 1.0_dp/2.0_dp ] + coef(3,1:2) = [ -1.0_dp/2.0_dp, 3.0_dp/2.0_dp ] + + case(3) + coef(1,1:3) = [ 11.0_dp/6.0_dp, -7.0_dp/6.0_dp, 1.0_dp/3.0_dp ] + coef(2,1:3) = [ 1.0_dp/3.0_dp, 5.0_dp/6.0_dp, -1.0_dp/6.0_dp ] + coef(3,1:3) = [ -1.0_dp/6.0_dp, 5.0_dp/6.0_dp, 1.0_dp/3.0_dp ] + coef(4,1:3) = [ 1.0_dp/3.0_dp, -7.0_dp/6.0_dp, 11.0_dp/6.0_dp ] + + case(4) + coef(1,1:4) = [ 25.0_dp/12.0_dp, -23.0_dp/12.0_dp, 13.0_dp/12.0_dp, -1.0_dp/4.0_dp ] + coef(2,1:4) = [ 1.0_dp/4.0_dp, 13.0_dp/12.0_dp, -5.0_dp/12.0_dp, 1.0_dp/12.0_dp ] + coef(3,1:4) = [ -1.0_dp/12.0_dp, 7.0_dp/12.0_dp, 7.0_dp/12.0_dp, -1.0_dp/12.0_dp ] + coef(4,1:4) = [ 1.0_dp/12.0_dp, -5.0_dp/12.0_dp, 13.0_dp/12.0_dp, 1.0_dp/4.0_dp ] + coef(5,1:4) = [ -1.0_dp/4.0_dp, 13.0_dp/12.0_dp, -23.0_dp/12.0_dp, 25.0_dp/12.0_dp ] + + case default + error stop "Unsupported spatial order" + end select + end subroutine init_coef + + ! ENO reconstruction + subroutine eno_reconstruct(this, q, cfd_obj) + class(EnoReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + class(*), intent(inout) :: cfd_obj + + type(CfdType), pointer :: cfd + integer :: i, j, m, k1, k2, r1, r2 + + ! Cast to CfdType + select type(cfd_obj) + type is (CfdType) + cfd => cfd_obj + class default + error stop "Invalid CFD object type in eno_reconstruct" + end select + + ! Compute divided differences + this%dd(1, :) = q + + do m = 2, this%spatial_order + do j = 1, this%ntcells - m + 1 + this%dd(m, j) = this%dd(m-1, j+1) - this%dd(m-1, j) + end do + end do + + ! Select left-biased stencil for each node + do i = cfd%domain%ist-1, cfd%domain%ied + this%lmc(i) = i + do m = 2, this%spatial_order + if (abs(this%dd(m, this%lmc(i)-1)) < abs(this%dd(m, this%lmc(i)))) then + this%lmc(i) = this%lmc(i) - 1 + end if + end do + end do + + ! Reconstruct values at cell interfaces + do i = cfd%domain%ist, cfd%domain%ied + j = i - cfd%domain%ist + 1 + k1 = this%lmc(i-1) + k2 = this%lmc(i) + r1 = i-1 - k1 + r2 = i - k2 + + cfd%solution%q_face_left(j) = 0.0_dp + cfd%solution%q_face_right(j) = 0.0_dp + + do m = 1, this%spatial_order + cfd%solution%q_face_left(j) = cfd%solution%q_face_left(j) + & + q(k1 + m - 1) * this%coef(r1+2, m) + cfd%solution%q_face_right(j) = cfd%solution%q_face_right(j) + & + q(k2 + m - 1) * this%coef(r2+1, m) + end do + end do + end subroutine eno_reconstruct + + ! WENO-3 nonlinear weights for left-biased stencil + function wc3L(v1, v2, v3) result(f) + real(dp), intent(in) :: v1, v2, v3 + real(dp) :: f, s0, s1, d0, d1, c0, c1, w0, w1, q0, q1 + + ! Smoothness indicators + s0 = (v3 - v2)**2 + s1 = (v2 - v1)**2 + + ! Nonlinear weights + d0 = 2.0_dp/3.0_dp + d1 = 1.0_dp/3.0_dp + + c0 = d0 / ((eps_weno + s0)**2) + c1 = d1 / ((eps_weno + s1)**2) + + w0 = c0 / (c0 + c1) + w1 = c1 / (c0 + c1) + + ! Candidate stencils + q0 = 0.5_dp * v2 + 0.5_dp * v3 + q1 = -0.5_dp * v1 + 1.5_dp * v2 + + ! Reconstructed value + f = w0 * q0 + w1 * q1 + end function wc3L + + ! WENO-3 nonlinear weights for right-biased stencil + function wc3R(v1, v2, v3) result(f) + real(dp), intent(in) :: v1, v2, v3 + real(dp) :: f, s0, s1, d0, d1, c0, c1, w0, w1, q0, q1 + + ! Smoothness indicators + s0 = (v2 - v1)**2 + s1 = (v3 - v2)**2 + + ! Nonlinear weights + d0 = 2.0_dp/3.0_dp + d1 = 1.0_dp/3.0_dp + + c0 = d0 / ((eps_weno + s0)**2) + c1 = d1 / ((eps_weno + s1)**2) + + w0 = c0 / (c0 + c1) + w1 = c1 / (c0 + c1) + + ! Candidate stencils + q0 = 0.5_dp * v1 + 0.5_dp * v2 + q1 = 1.5_dp * v2 - 0.5_dp * v3 + + ! Reconstructed value + f = w0 * q0 + w1 * q1 + end function wc3R + + ! WENO-3 reconstruction for left interface + subroutine weno3L_periodic(cfd, u, f) + type(CfdType), intent(in) :: cfd + real(dp), intent(in) :: u(:) + real(dp), intent(out) :: f(:) + + integer :: i, j + + do i = cfd%domain%ist-1, cfd%domain%ied-1 + j = i - (cfd%domain%ist - 1) + f(j) = wc3L(u(i-1), u(i), u(i+1)) + end do + end subroutine weno3L_periodic + + ! WENO-3 reconstruction for right interface + subroutine weno3R_periodic(cfd, u, f) + type(CfdType), intent(in) :: cfd + real(dp), intent(in) :: u(:) + real(dp), intent(out) :: f(:) + + integer :: i, j + + do i = cfd%domain%ist, cfd%domain%ied + j = i - cfd%domain%ist + 1 + f(j) = wc3R(u(i-1), u(i), u(i+1)) + end do + end subroutine weno3R_periodic + + ! WENO reconstruction + subroutine weno_reconstruct(this, q, cfd_obj) + class(WenoReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + class(*), intent(inout) :: cfd_obj + + type(CfdType), pointer :: cfd + + ! Cast to CfdType + select type(cfd_obj) + type is (CfdType) + cfd => cfd_obj + class default + error stop "Invalid CFD object type in weno_reconstruct" + end select + + call weno3L_periodic(cfd, q, cfd%solution%q_face_left) + call weno3R_periodic(cfd, q, cfd%solution%q_face_right) + end subroutine weno_reconstruct + + ! General reconstruction wrapper + subroutine reconstruction(q, cfd) + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + + call cfd%reconstructor%reconstruct(q, cfd) + end subroutine reconstruction + + ! =================================================================== + ! Flux Functions + ! =================================================================== + + ! Rusanov flux + subroutine rusanov_flux(q_face_left, q_face_right, flux, cfd) + real(dp), intent(in) :: q_face_left(:), q_face_right(:) + real(dp), intent(out) :: flux(:) + type(CfdType), intent(in) :: cfd + + integer :: i + real(dp) :: u_L, u_R, F_L, F_R, c_L, c_R, Smax + + c_L = cfd%config%wave_speed + c_R = cfd%config%wave_speed + + do i = 1, cfd%domain%mesh%nnodes + u_L = q_face_left(i) + u_R = q_face_right(i) + F_L = c_L * u_L + F_R = c_R * u_R + Smax = max(abs(c_L), abs(c_R)) + flux(i) = 0.5_dp * (F_L + F_R) - 0.5_dp * Smax * (u_R - u_L) + end do + end subroutine rusanov_flux + + ! Engquist-Osher flux + subroutine engquist_osher_flux(q_face_left, q_face_right, flux, cfd) + real(dp), intent(in) :: q_face_left(:), q_face_right(:) + real(dp), intent(out) :: flux(:) + type(CfdType), intent(in) :: cfd + + integer :: i + real(dp) :: c, cp, cm, u_L, u_R + + c = cfd%config%wave_speed + + do i = 1, cfd%domain%mesh%nnodes + cp = 0.5_dp * (c + abs(c)) + cm = 0.5_dp * (c - abs(c)) + u_L = q_face_left(i) + u_R = q_face_right(i) + flux(i) = cp * u_L + cm * u_R + end do + end subroutine engquist_osher_flux + + ! Inviscid flux selection + subroutine inviscid_flux(q_face_left, q_face_right, flux, cfd) + real(dp), intent(in) :: q_face_left(:), q_face_right(:) + real(dp), intent(out) :: flux(:) + type(CfdType), intent(in) :: cfd + + if (cfd%config%flux_type == 0) then + call rusanov_flux(q_face_left, q_face_right, flux, cfd) + else + call engquist_osher_flux(q_face_left, q_face_right, flux, cfd) + end if + end subroutine inviscid_flux + + ! =================================================================== + ! Residual Computation + ! =================================================================== + + ! Compute residual (flux divergence) + subroutine residual(q, cfd) + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + + integer :: i + + ! Reconstruction + call reconstruction(q, cfd) + + ! Compute fluxes + call inviscid_flux(cfd%solution%q_face_left, cfd%solution%q_face_right, & + cfd%solution%flux, cfd) + + ! Compute residual + do i = 1, cfd%domain%mesh%ncells + cfd%solution%res(i) = -(cfd%solution%flux(i+1) - cfd%solution%flux(i)) / & + cfd%domain%mesh%dx + end do + end subroutine residual + + ! =================================================================== + ! Time Integration + ! =================================================================== + + ! Update old field + subroutine update_oldfield(qn, q, n) + real(dp), intent(out) :: qn(:) + real(dp), intent(in) :: q(:) + integer, intent(in) :: n + + qn(1:n) = q(1:n) + end subroutine update_oldfield + + ! 1st-order Runge-Kutta (Euler) + subroutine runge_kutta_1(cfd) + type(CfdType), intent(inout) :: cfd + + integer :: i, j + real(dp) :: dt + + dt = cfd%config%dt + + call residual(cfd%solution%u, cfd) + + do i = cfd%domain%ist, cfd%domain%ied + j = i - cfd%domain%ist + 1 + cfd%solution%u(i) = cfd%solution%u(i) + dt * cfd%solution%res(j) + end do + + call boundary(cfd%solution%u, cfd) + call update_oldfield(cfd%solution%un, cfd%solution%u, cfd%domain%ntcells) + end subroutine runge_kutta_1 + + ! 2nd-order Runge-Kutta (Heun) + subroutine runge_kutta_2(cfd) + type(CfdType), intent(inout) :: cfd + + integer :: i, j + real(dp) :: dt + + dt = cfd%config%dt + + ! Stage 1 + call residual(cfd%solution%u, cfd) + do i = cfd%domain%ist, cfd%domain%ied + j = i - cfd%domain%ist + 1 + cfd%solution%u(i) = cfd%solution%u(i) + dt * cfd%solution%res(j) + end do + call boundary(cfd%solution%u, cfd) + + ! Stage 2 + call residual(cfd%solution%u, cfd) + do i = cfd%domain%ist, cfd%domain%ied + j = i - cfd%domain%ist + 1 + cfd%solution%u(i) = 0.5_dp * cfd%solution%un(i) + & + 0.5_dp * cfd%solution%u(i) + & + 0.5_dp * dt * cfd%solution%res(j) + end do + call boundary(cfd%solution%u, cfd) + + call update_oldfield(cfd%solution%un, cfd%solution%u, cfd%domain%ntcells) + end subroutine runge_kutta_2 + + ! 3rd-order Runge-Kutta (SSPRK3) + subroutine runge_kutta_3(cfd) + type(CfdType), intent(inout) :: cfd + + integer :: i, j + real(dp) :: dt + + dt = cfd%config%dt + + ! Stage 1 + call residual(cfd%solution%u, cfd) + do i = cfd%domain%ist, cfd%domain%ied + j = i - cfd%domain%ist + 1 + cfd%solution%u(i) = cfd%solution%u(i) + dt * cfd%solution%res(j) + end do + call boundary(cfd%solution%u, cfd) + + ! Stage 2 + call residual(cfd%solution%u, cfd) + do i = cfd%domain%ist, cfd%domain%ied + j = i - cfd%domain%ist + 1 + cfd%solution%u(i) = 0.75_dp * cfd%solution%un(i) + & + 0.25_dp * cfd%solution%u(i) + & + 0.25_dp * dt * cfd%solution%res(j) + end do + call boundary(cfd%solution%u, cfd) + + ! Stage 3 + call residual(cfd%solution%u, cfd) + do i = cfd%domain%ist, cfd%domain%ied + j = i - cfd%domain%ist + 1 + cfd%solution%u(i) = (1.0_dp/3.0_dp) * cfd%solution%un(i) + & + (2.0_dp/3.0_dp) * cfd%solution%u(i) + & + (2.0_dp/3.0_dp) * dt * cfd%solution%res(j) + end do + call boundary(cfd%solution%u, cfd) + + call update_oldfield(cfd%solution%un, cfd%solution%u, cfd%domain%ntcells) + end subroutine runge_kutta_3 + + ! Runge-Kutta selection + subroutine runge_kutta(cfd) + type(CfdType), intent(inout) :: cfd + + select case(cfd%config%rk_order) + case(1) + call runge_kutta_1(cfd) + case(2) + call runge_kutta_2(cfd) + case(3) + call runge_kutta_3(cfd) + case default + call runge_kutta_1(cfd) + end select + end subroutine runge_kutta + + ! =================================================================== + ! Simulation Driver + ! =================================================================== + + ! Run simulation to final time + function run_simulation(cfd, final_time) result(u_result) + type(CfdType), intent(inout) :: cfd + real(dp), intent(in) :: final_time + real(dp), allocatable :: u_result(:) + + real(dp) :: t, dt, dt_old + + allocate(u_result(cfd%domain%mesh%ncells)) + + t = 0.0_dp + dt_old = cfd%config%dt + dt = dt_old + + do while (t < final_time - 1.0e-12_dp) + if (t + dt > final_time) then + dt = final_time - t + end if + cfd%config%dt = dt + call runge_kutta(cfd) + t = t + dt + end do + + cfd%config%dt = dt_old + + ! Extract physical solution (without ghost cells) + u_result = cfd%solution%u(cfd%domain%ist:cfd%domain%ied) + end function run_simulation + + ! =================================================================== + ! Main Analysis Function + ! =================================================================== + + ! Perform ENO-WENO comparative analysis + subroutine performEnoWenoAnalysis() + type(CfdConfigType) :: config_eno3, config_weno3 + type(MeshType) :: mesh + type(ComputationalDomainType) :: domain_eno3, domain_weno3 + type(CfdType) :: cfd_eno3, cfd_weno3 + real(dp), allocatable :: u_eno(:), u_weno(:), u_analytical(:) + real(dp), allocatable :: xcc(:) + integer :: i, ncells, iunit + + ! Initialize mesh + call mesh%init() + ncells = mesh%ncells + allocate(xcc(ncells)) + xcc = mesh%xcc + + ! Configure ENO3 + config_eno3%recon_scheme = "eno" + config_eno3%spatial_order = 3 + config_eno3%flux_type = 0 + config_eno3%rk_order = 1 + config_eno3%wave_speed = 1.0_dp + config_eno3%final_time = 0.625_dp + config_eno3%dt = 0.0025_dp + + ! Configure WENO3 + config_weno3%recon_scheme = "weno" + config_weno3%spatial_order = 3 + config_weno3%flux_type = 0 + config_weno3%rk_order = 1 + config_weno3%wave_speed = 1.0_dp + config_weno3%final_time = 0.625_dp + config_weno3%dt = 0.0025_dp + + ! Create domains + call domain_eno3%init(mesh, config_eno3) + call domain_weno3%init(mesh, config_weno3) + + ! Create CFD solvers + call cfd_eno3%init(config_eno3, domain_eno3) + call cfd_weno3%init(config_weno3, domain_weno3) + + ! Allocate arrays + allocate(u_eno(ncells), u_weno(ncells), u_analytical(ncells)) + + ! Run ENO simulation + print *, "Running ENO3 simulation..." + call init_field(cfd_eno3) + u_eno = run_simulation(cfd_eno3, config_eno3%final_time) + + ! Run WENO simulation + print *, "Running WENO3 simulation..." + call init_field(cfd_weno3) + u_weno = run_simulation(cfd_weno3, config_weno3%final_time) + + ! Compute analytical solution + print *, "Computing analytical solution..." + do i = 1, ncells + u_analytical(i) = analytical_solution(xcc(i), config_weno3%final_time, & + config_weno3%wave_speed, mesh%L) + end do + + ! Write results to files + print *, "Writing results to files..." + + ! Write ENO results + open(newunit=iunit, file='eno_results.txt', status='replace') + write(iunit, '(A)') '# x, u (ENO3)' + do i = 1, ncells + write(iunit, '(2F12.6)') xcc(i), u_eno(i) + end do + close(iunit) + + ! Write WENO results + open(newunit=iunit, file='weno_results.txt', status='replace') + write(iunit, '(A)') '# x, u (WENO3)' + do i = 1, ncells + write(iunit, '(2F12.6)') xcc(i), u_weno(i) + end do + close(iunit) + + ! Write analytical results + open(newunit=iunit, file='analytical_results.txt', status='replace') + write(iunit, '(A)') '# x, u (Analytical)' + do i = 1, ncells + write(iunit, '(2F12.6)') xcc(i), u_analytical(i) + end do + close(iunit) + + print *, "==========================================" + print *, "Simulation completed successfully!" + print *, "Results written to:" + print *, " eno_results.txt" + print *, " weno_results.txt" + print *, " analytical_results.txt" + print *, "" + print *, "To generate the comparison plot, run:" + print *, " python postprocess.py" + print *, "==========================================" + + deallocate(u_eno, u_weno, u_analytical, xcc) + end subroutine performEnoWenoAnalysis + +end module cfd_solver + +! =================================================================== +! Main Program +! =================================================================== +program main + use cfd_solver + implicit none + + print *, "==========================================" + print *, "OneFLOW-CFD Solver for 1D Convection" + print *, "ENO3 vs WENO3 Comparison" + print *, "==========================================" + + call performEnoWenoAnalysis() + + print *, "Program finished successfully!" + +end program main \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/cfd/01a/postprocess.py b/example/1d-linear-convection/weno3/fortran/cfd/01a/postprocess.py new file mode 100644 index 000000000..6cbcc4d9b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/cfd/01a/postprocess.py @@ -0,0 +1,38 @@ +import numpy as np +import matplotlib.pyplot as plt + +def read_results(filename): + """Read results from Fortran output file""" + data = np.loadtxt(filename, comments='#') + return data[:, 0], data[:, 1] + +def main(): + # Read results + x_eno, u_eno = read_results('eno_results.txt') + x_weno, u_weno = read_results('weno_results.txt') + x_analytical, u_analytical = read_results('analytical_results.txt') + + # Create plot + plt.figure(figsize=(12, 8)) + plt.plot(x_eno, u_eno, 'bo-', linewidth=1.5, markersize=4, + markerfacecolor='none', label='ENO3 (Rusanov)') + plt.plot(x_weno, u_weno, 'gs-', linewidth=1.5, markersize=4, + markerfacecolor='none', label='WENO3 (Rusanov)') + plt.plot(x_analytical, u_analytical, 'r--', linewidth=2, label='Analytical') + + plt.title('1D Convection Equation: ENO3 vs WENO3 Comparison (t=0.625)', fontsize=14) + plt.xlabel('x', fontsize=12) + plt.ylabel('u', fontsize=12) + plt.legend(fontsize=12) + plt.grid(True, alpha=0.3) + plt.tight_layout() + + # Save figure + plt.savefig('eno_weno_comparison.png', dpi=300, bbox_inches='tight') + print("Plot saved as: eno_weno_comparison.png") + + # Show plot + plt.show() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/cfd/01b/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/cfd/01b/CMakeLists.txt new file mode 100644 index 000000000..653a6751e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/cfd/01b/CMakeLists.txt @@ -0,0 +1,22 @@ +# CMakeLists.txt +cmake_minimum_required(VERSION 4.2.1) # 更高版本更好支持Fortran + +project(OneFLOW_CFD LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +# 按依赖顺序显式列出源文件(解决 Intel + VS 编译顺序问题) +set(SOURCES + cfd_solver.f90 +) + +add_executable(oneflow_cfd ${SOURCES}) + +# 包含模块目录(解决 .mod 未找到) +target_include_directories(oneflow_cfd PRIVATE ${CMAKE_Fortran_MODULE_DIRECTORY}) diff --git a/example/1d-linear-convection/weno3/fortran/cfd/01b/cfd_solver.f90 b/example/1d-linear-convection/weno3/fortran/cfd/01b/cfd_solver.f90 new file mode 100644 index 000000000..0bfb7747a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/cfd/01b/cfd_solver.f90 @@ -0,0 +1,861 @@ +! OneFLOW-CFD Solver for 1D Convection Equation +! ENO/WENO Reconstruction Comparison - Single File Implementation + +module cfd_solver + use, intrinsic :: iso_fortran_env, only: dp => real64 + implicit none + + private + + ! =================================================================== + ! Forward Type Declarations + ! =================================================================== + type, public :: CfdConfigType + character(len=10) :: recon_scheme = "eno" + integer :: flux_type = 0 + integer :: rk_order = 1 + integer :: spatial_order = 3 + real(dp) :: wave_speed = 1.0_dp + real(dp) :: final_time = 0.625_dp + real(dp) :: dt = 0.025_dp + end type CfdConfigType + + type, public :: MeshType + real(dp) :: xmin = 0.0_dp + real(dp) :: xmax = 2.0_dp + integer :: ncells = 40 + integer :: nnodes = 0 + integer :: nx = 0 + real(dp) :: L = 0.0_dp + real(dp) :: dx = 0.0_dp + real(dp), allocatable :: x(:), xcc(:) + contains + procedure :: init => mesh_init + end type MeshType + + type, public :: ComputationalDomainType + type(MeshType) :: mesh + type(CfdConfigType) :: config + integer :: nghosts = 0 + integer :: ist = 0 + integer :: ied = 0 + integer :: ntcells = 0 + contains + procedure :: init => domain_init + end type ComputationalDomainType + + type, public :: SolutionType + type(ComputationalDomainType) :: domain + real(dp), allocatable :: q_face_left(:), q_face_right(:) + real(dp), allocatable :: flux(:), res(:) + real(dp), allocatable :: u(:), un(:) + contains + procedure :: init => solution_init + end type SolutionType + + ! Main CFD solver class - defined BEFORE abstract interface + type, public :: CfdType + type(CfdConfigType) :: config + type(ComputationalDomainType) :: domain + type(SolutionType) :: solution + class(*), allocatable :: reconstructor + contains + procedure :: init => cfd_init + end type CfdType + + ! Abstract reconstructor base class + type, abstract, public :: ReconstructorType + contains + procedure(reconstruct_interface), deferred, pass :: reconstruct + end type ReconstructorType + + ! Define abstract interface + abstract interface + subroutine reconstruct_interface(this, q, cfd) + import :: ReconstructorType, CfdType, dp + class(ReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout), target :: cfd + end subroutine reconstruct_interface + end interface + + ! ENO reconstructor + type, extends(ReconstructorType), public :: EnoReconstructorType + integer :: spatial_order + integer :: ntcells + integer, allocatable :: lmc(:) + real(dp), allocatable :: coef(:,:) + real(dp), allocatable :: dd(:,:) + contains + procedure :: reconstruct => eno_reconstruct + procedure :: init => eno_init + end type EnoReconstructorType + + ! WENO reconstructor + type, extends(ReconstructorType), public :: WenoReconstructorType + contains + procedure :: reconstruct => weno_reconstruct + end type WenoReconstructorType + + ! =================================================================== + ! Public Procedures + ! =================================================================== + public :: run_simulation, init_field, analytical_solution, performEnoWenoAnalysis + + ! =================================================================== + ! Module Variables + ! =================================================================== + real(dp), parameter :: eps_weno = 1.0e-6_dp + +contains + + ! =================================================================== + ! Initialization Methods + ! =================================================================== + + ! Mesh initialization + subroutine mesh_init(this) + class(MeshType), intent(inout) :: this + integer :: i + + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, dp) + + allocate(this%x(this%nnodes), this%xcc(this%ncells)) + + ! Node coordinates + do i = 1, this%nnodes + this%x(i) = this%xmin + (i-1) * this%dx + end do + + ! Cell center coordinates + do i = 1, this%ncells + this%xcc(i) = 0.5_dp * (this%x(i) + this%x(i+1)) + end do + end subroutine mesh_init + + ! Domain initialization + subroutine domain_init(this, mesh, config) + class(ComputationalDomainType), intent(inout) :: this + type(MeshType), intent(in) :: mesh + type(CfdConfigType), intent(in) :: config + + this%mesh = mesh + this%config = config + + ! Calculate ghost cells + if (trim(config%recon_scheme) == "eno") then + this%nghosts = config%spatial_order + else if (trim(config%recon_scheme) == "weno") then + this%nghosts = config%spatial_order / 2 + 1 + else + error stop "Unknown reconstruction scheme" + end if + + this%ist = this%nghosts + 1 + this%ied = this%ist + mesh%ncells - 1 + this%ntcells = mesh%ncells + 2 * this%nghosts + + print *, "Domain initialized:" + print *, " mesh.ncells = ", mesh%ncells + print *, " spatial_order = ", config%spatial_order + print *, " nghosts = ", this%nghosts + print *, " ist = ", this%ist, ", ied = ", this%ied + end subroutine domain_init + + ! Solution initialization + subroutine solution_init(this, domain) + class(SolutionType), intent(inout) :: this + type(ComputationalDomainType), intent(in) :: domain + + this%domain = domain + + allocate(this%q_face_left(domain%mesh%nnodes)) + allocate(this%q_face_right(domain%mesh%nnodes)) + allocate(this%flux(domain%mesh%nnodes)) + allocate(this%res(domain%mesh%ncells)) + allocate(this%u(domain%ntcells)) + allocate(this%un(domain%ntcells)) + + this%q_face_left = 0.0_dp + this%q_face_right = 0.0_dp + this%flux = 0.0_dp + this%res = 0.0_dp + this%u = 0.0_dp + this%un = 0.0_dp + end subroutine solution_init + + ! ENO reconstructor initialization + subroutine eno_init(this, spatial_order, ntcells) + class(EnoReconstructorType), intent(inout) :: this + integer, intent(in) :: spatial_order + integer, intent(in) :: ntcells + + this%spatial_order = spatial_order + this%ntcells = ntcells + + allocate(this%lmc(ntcells)) + allocate(this%coef(spatial_order+1, spatial_order)) + allocate(this%dd(spatial_order, ntcells)) + + this%lmc = 0 + this%coef = 0.0_dp + this%dd = 0.0_dp + + ! Initialize coefficients + call init_coef(spatial_order, this%coef) + end subroutine eno_init + + ! CFD solver initialization + subroutine cfd_init(this, config, domain) + class(CfdType), intent(inout) :: this + type(CfdConfigType), intent(in) :: config + type(ComputationalDomainType), intent(in) :: domain + + this%config = config + this%domain = domain + call this%solution%init(domain) + + ! Create reconstructor based on scheme + if (trim(config%recon_scheme) == "eno") then + allocate(EnoReconstructorType :: this%reconstructor) + select type(rec => this%reconstructor) + type is (EnoReconstructorType) + call rec%init(config%spatial_order, domain%ntcells) + end select + else if (trim(config%recon_scheme) == "weno") then + allocate(WenoReconstructorType :: this%reconstructor) + else + error stop "Unknown reconstruction scheme" + end if + end subroutine cfd_init + + ! =================================================================== + ! Initial Conditions and Analytical Solution + ! =================================================================== + + ! Initial condition: step function + function initial_condition(x) result(u0) + real(dp), intent(in) :: x + real(dp) :: u0 + + if (0.5_dp <= x .and. x <= 1.0_dp) then + u0 = 2.0_dp + else + u0 = 1.0_dp + end if + end function initial_condition + + ! Analytical solution with periodic BC + function analytical_solution(x, t, a, L) result(u) + real(dp), intent(in) :: x, t, a, L + real(dp) :: u, x_shifted + + x_shifted = mod(x - a * t + L, L) + u = initial_condition(x_shifted) + end function analytical_solution + + ! Initialize field with step function + subroutine init_field(cfd) + type(CfdType), intent(inout) :: cfd + integer :: i, j + + do i = cfd%domain%ist, cfd%domain%ied + j = i - cfd%domain%ist + 1 + if (0.5_dp <= cfd%domain%mesh%xcc(j) .and. cfd%domain%mesh%xcc(j) <= 1.0_dp) then + cfd%solution%u(i) = 2.0_dp + else + cfd%solution%u(i) = 1.0_dp + end if + end do + + call boundary(cfd%solution%u, cfd) + call update_oldfield(cfd%solution%un, cfd%solution%u, cfd%domain%ntcells) + end subroutine init_field + + ! =================================================================== + ! Boundary Conditions + ! =================================================================== + + ! Periodic boundary conditions + subroutine periodic_boundary(u, cfd) + real(dp), intent(inout) :: u(:) + type(CfdType), intent(in) :: cfd + integer :: ig + + ! Left ghost cells = right interior cells + do ig = 1, cfd%domain%nghosts + u(cfd%domain%ist - ig) = u(cfd%domain%ied - ig) + end do + + ! Right ghost cells = left interior cells + do ig = 1, cfd%domain%nghosts + u(cfd%domain%ied + ig) = u(cfd%domain%ist + ig - 1) + end do + end subroutine periodic_boundary + + ! Boundary condition wrapper + subroutine boundary(u, cfd) + real(dp), intent(inout) :: u(:) + type(CfdType), intent(in) :: cfd + + call periodic_boundary(u, cfd) + end subroutine boundary + + ! =================================================================== + ! Reconstruction Methods + ! =================================================================== + + ! Initialize ENO/WENO coefficients + subroutine init_coef(spatial_order, coef) + integer, intent(in) :: spatial_order + real(dp), intent(out) :: coef(:,:) + + coef = 0.0_dp + + select case(spatial_order) + case(1) + coef(1,1) = 1.0_dp + coef(2,1) = 1.0_dp + + case(2) + coef(1,1:2) = [ 3.0_dp/2.0_dp, -1.0_dp/2.0_dp ] + coef(2,1:2) = [ 1.0_dp/2.0_dp, 1.0_dp/2.0_dp ] + coef(3,1:2) = [ -1.0_dp/2.0_dp, 3.0_dp/2.0_dp ] + + case(3) + coef(1,1:3) = [ 11.0_dp/6.0_dp, -7.0_dp/6.0_dp, 1.0_dp/3.0_dp ] + coef(2,1:3) = [ 1.0_dp/3.0_dp, 5.0_dp/6.0_dp, -1.0_dp/6.0_dp ] + coef(3,1:3) = [ -1.0_dp/6.0_dp, 5.0_dp/6.0_dp, 1.0_dp/3.0_dp ] + coef(4,1:3) = [ 1.0_dp/3.0_dp, -7.0_dp/6.0_dp, 11.0_dp/6.0_dp ] + + case(4) + coef(1,1:4) = [ 25.0_dp/12.0_dp, -23.0_dp/12.0_dp, 13.0_dp/12.0_dp, -1.0_dp/4.0_dp ] + coef(2,1:4) = [ 1.0_dp/4.0_dp, 13.0_dp/12.0_dp, -5.0_dp/12.0_dp, 1.0_dp/12.0_dp ] + coef(3,1:4) = [ -1.0_dp/12.0_dp, 7.0_dp/12.0_dp, 7.0_dp/12.0_dp, -1.0_dp/12.0_dp ] + coef(4,1:4) = [ 1.0_dp/12.0_dp, -5.0_dp/12.0_dp, 13.0_dp/12.0_dp, 1.0_dp/4.0_dp ] + coef(5,1:4) = [ -1.0_dp/4.0_dp, 13.0_dp/12.0_dp, -23.0_dp/12.0_dp, 25.0_dp/12.0_dp ] + + case default + error stop "Unsupported spatial order" + end select + end subroutine init_coef + + ! ENO reconstruction + subroutine eno_reconstruct(this, q, cfd) + class(EnoReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout), target :: cfd + + integer :: i, j, m, k1, k2, r1, r2 + + ! Compute divided differences + this%dd(1, :) = q + + do m = 2, this%spatial_order + do j = 1, this%ntcells - m + 1 + this%dd(m, j) = this%dd(m-1, j+1) - this%dd(m-1, j) + end do + end do + + ! Select left-biased stencil for each node + do i = cfd%domain%ist-1, cfd%domain%ied + this%lmc(i) = i + do m = 2, this%spatial_order + if (abs(this%dd(m, this%lmc(i)-1)) < abs(this%dd(m, this%lmc(i)))) then + this%lmc(i) = this%lmc(i) - 1 + end if + end do + end do + + ! Reconstruct values at cell interfaces + do i = cfd%domain%ist, cfd%domain%ied + j = i - cfd%domain%ist + 1 + k1 = this%lmc(i-1) + k2 = this%lmc(i) + r1 = i-1 - k1 + r2 = i - k2 + + cfd%solution%q_face_left(j) = 0.0_dp + cfd%solution%q_face_right(j) = 0.0_dp + + do m = 1, this%spatial_order + cfd%solution%q_face_left(j) = cfd%solution%q_face_left(j) + & + q(k1 + m - 1) * this%coef(r1+2, m) + cfd%solution%q_face_right(j) = cfd%solution%q_face_right(j) + & + q(k2 + m - 1) * this%coef(r2+1, m) + end do + end do + end subroutine eno_reconstruct + + ! WENO-3 nonlinear weights for left-biased stencil + function wc3L(v1, v2, v3) result(f) + real(dp), intent(in) :: v1, v2, v3 + real(dp) :: f, s0, s1, d0, d1, c0, c1, w0, w1, q0, q1 + + ! Smoothness indicators + s0 = (v3 - v2)**2 + s1 = (v2 - v1)**2 + + ! Nonlinear weights + d0 = 2.0_dp/3.0_dp + d1 = 1.0_dp/3.0_dp + + c0 = d0 / ((eps_weno + s0)**2) + c1 = d1 / ((eps_weno + s1)**2) + + w0 = c0 / (c0 + c1) + w1 = c1 / (c0 + c1) + + ! Candidate stencils + q0 = 0.5_dp * v2 + 0.5_dp * v3 + q1 = -0.5_dp * v1 + 1.5_dp * v2 + + ! Reconstructed value + f = w0 * q0 + w1 * q1 + end function wc3L + + ! WENO-3 nonlinear weights for right-biased stencil + function wc3R(v1, v2, v3) result(f) + real(dp), intent(in) :: v1, v2, v3 + real(dp) :: f, s0, s1, d0, d1, c0, c1, w0, w1, q0, q1 + + ! Smoothness indicators + s0 = (v2 - v1)**2 + s1 = (v3 - v2)**2 + + ! Nonlinear weights + d0 = 2.0_dp/3.0_dp + d1 = 1.0_dp/3.0_dp + + c0 = d0 / ((eps_weno + s0)**2) + c1 = d1 / ((eps_weno + s1)**2) + + w0 = c0 / (c0 + c1) + w1 = c1 / (c0 + c1) + + ! Candidate stencils + q0 = 0.5_dp * v1 + 0.5_dp * v2 + q1 = 1.5_dp * v2 - 0.5_dp * v3 + + ! Reconstructed value + f = w0 * q0 + w1 * q1 + end function wc3R + + ! WENO-3 reconstruction for left interface + subroutine weno3L_periodic(cfd, u, f) + type(CfdType), intent(in) :: cfd + real(dp), intent(in) :: u(:) + real(dp), intent(out) :: f(:) + + integer :: i, j + + do i = cfd%domain%ist-1, cfd%domain%ied-1 + j = i - (cfd%domain%ist - 1) + f(j) = wc3L(u(i-1), u(i), u(i+1)) + end do + end subroutine weno3L_periodic + + ! WENO-3 reconstruction for right interface + subroutine weno3R_periodic(cfd, u, f) + type(CfdType), intent(in) :: cfd + real(dp), intent(in) :: u(:) + real(dp), intent(out) :: f(:) + + integer :: i, j + + do i = cfd%domain%ist, cfd%domain%ied + j = i - cfd%domain%ist + 1 + f(j) = wc3R(u(i-1), u(i), u(i+1)) + end do + end subroutine weno3R_periodic + + ! WENO reconstruction + subroutine weno_reconstruct(this, q, cfd) + class(WenoReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout), target :: cfd + + call weno3L_periodic(cfd, q, cfd%solution%q_face_left) + call weno3R_periodic(cfd, q, cfd%solution%q_face_right) + end subroutine weno_reconstruct + + ! General reconstruction wrapper + subroutine reconstruction(q, cfd) + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + + select type(rec => cfd%reconstructor) + type is (EnoReconstructorType) + call rec%reconstruct(q, cfd) + type is (WenoReconstructorType) + call rec%reconstruct(q, cfd) + class default + error stop "Unknown reconstructor type" + end select + end subroutine reconstruction + + ! =================================================================== + ! Flux Functions + ! =================================================================== + + ! Rusanov flux + subroutine rusanov_flux(q_face_left, q_face_right, flux, cfd) + real(dp), intent(in) :: q_face_left(:), q_face_right(:) + real(dp), intent(out) :: flux(:) + type(CfdType), intent(in) :: cfd + + integer :: i + real(dp) :: u_L, u_R, F_L, F_R, c_L, c_R, Smax + + c_L = cfd%config%wave_speed + c_R = cfd%config%wave_speed + + do i = 1, cfd%domain%mesh%nnodes + u_L = q_face_left(i) + u_R = q_face_right(i) + F_L = c_L * u_L + F_R = c_R * u_R + Smax = max(abs(c_L), abs(c_R)) + flux(i) = 0.5_dp * (F_L + F_R) - 0.5_dp * Smax * (u_R - u_L) + end do + end subroutine rusanov_flux + + ! Engquist-Osher flux + subroutine engquist_osher_flux(q_face_left, q_face_right, flux, cfd) + real(dp), intent(in) :: q_face_left(:), q_face_right(:) + real(dp), intent(out) :: flux(:) + type(CfdType), intent(in) :: cfd + + integer :: i + real(dp) :: c, cp, cm, u_L, u_R + + c = cfd%config%wave_speed + + do i = 1, cfd%domain%mesh%nnodes + cp = 0.5_dp * (c + abs(c)) + cm = 0.5_dp * (c - abs(c)) + u_L = q_face_left(i) + u_R = q_face_right(i) + flux(i) = cp * u_L + cm * u_R + end do + end subroutine engquist_osher_flux + + ! Inviscid flux selection + subroutine inviscid_flux(q_face_left, q_face_right, flux, cfd) + real(dp), intent(in) :: q_face_left(:), q_face_right(:) + real(dp), intent(out) :: flux(:) + type(CfdType), intent(in) :: cfd + + if (cfd%config%flux_type == 0) then + call rusanov_flux(q_face_left, q_face_right, flux, cfd) + else + call engquist_osher_flux(q_face_left, q_face_right, flux, cfd) + end if + end subroutine inviscid_flux + + ! =================================================================== + ! Residual Computation + ! =================================================================== + + ! Compute residual (flux divergence) + subroutine residual(q, cfd) + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + + integer :: i + + ! Reconstruction + call reconstruction(q, cfd) + + ! Compute fluxes + call inviscid_flux(cfd%solution%q_face_left, cfd%solution%q_face_right, & + cfd%solution%flux, cfd) + + ! Compute residual + do i = 1, cfd%domain%mesh%ncells + cfd%solution%res(i) = -(cfd%solution%flux(i+1) - cfd%solution%flux(i)) / & + cfd%domain%mesh%dx + end do + end subroutine residual + + ! =================================================================== + ! Time Integration + ! =================================================================== + + ! Update old field + subroutine update_oldfield(qn, q, n) + real(dp), intent(out) :: qn(:) + real(dp), intent(in) :: q(:) + integer, intent(in) :: n + + qn(1:n) = q(1:n) + end subroutine update_oldfield + + ! 1st-order Runge-Kutta (Euler) + subroutine runge_kutta_1(cfd) + type(CfdType), intent(inout) :: cfd + + integer :: i, j + real(dp) :: dt + + dt = cfd%config%dt + + call residual(cfd%solution%u, cfd) + + do i = cfd%domain%ist, cfd%domain%ied + j = i - cfd%domain%ist + 1 + cfd%solution%u(i) = cfd%solution%u(i) + dt * cfd%solution%res(j) + end do + + call boundary(cfd%solution%u, cfd) + call update_oldfield(cfd%solution%un, cfd%solution%u, cfd%domain%ntcells) + end subroutine runge_kutta_1 + + ! 2nd-order Runge-Kutta (Heun) + subroutine runge_kutta_2(cfd) + type(CfdType), intent(inout) :: cfd + + integer :: i, j + real(dp) :: dt + + dt = cfd%config%dt + + ! Stage 1 + call residual(cfd%solution%u, cfd) + do i = cfd%domain%ist, cfd%domain%ied + j = i - cfd%domain%ist + 1 + cfd%solution%u(i) = cfd%solution%u(i) + dt * cfd%solution%res(j) + end do + call boundary(cfd%solution%u, cfd) + + ! Stage 2 + call residual(cfd%solution%u, cfd) + do i = cfd%domain%ist, cfd%domain%ied + j = i - cfd%domain%ist + 1 + cfd%solution%u(i) = 0.5_dp * cfd%solution%un(i) + & + 0.5_dp * cfd%solution%u(i) + & + 0.5_dp * dt * cfd%solution%res(j) + end do + call boundary(cfd%solution%u, cfd) + + call update_oldfield(cfd%solution%un, cfd%solution%u, cfd%domain%ntcells) + end subroutine runge_kutta_2 + + ! 3rd-order Runge-Kutta (SSPRK3) + subroutine runge_kutta_3(cfd) + type(CfdType), intent(inout) :: cfd + + integer :: i, j + real(dp) :: dt + + dt = cfd%config%dt + + ! Stage 1 + call residual(cfd%solution%u, cfd) + do i = cfd%domain%ist, cfd%domain%ied + j = i - cfd%domain%ist + 1 + cfd%solution%u(i) = cfd%solution%u(i) + dt * cfd%solution%res(j) + end do + call boundary(cfd%solution%u, cfd) + + ! Stage 2 + call residual(cfd%solution%u, cfd) + do i = cfd%domain%ist, cfd%domain%ied + j = i - cfd%domain%ist + 1 + cfd%solution%u(i) = 0.75_dp * cfd%solution%un(i) + & + 0.25_dp * cfd%solution%u(i) + & + 0.25_dp * dt * cfd%solution%res(j) + end do + call boundary(cfd%solution%u, cfd) + + ! Stage 3 + call residual(cfd%solution%u, cfd) + do i = cfd%domain%ist, cfd%domain%ied + j = i - cfd%domain%ist + 1 + cfd%solution%u(i) = (1.0_dp/3.0_dp) * cfd%solution%un(i) + & + (2.0_dp/3.0_dp) * cfd%solution%u(i) + & + (2.0_dp/3.0_dp) * dt * cfd%solution%res(j) + end do + call boundary(cfd%solution%u, cfd) + + call update_oldfield(cfd%solution%un, cfd%solution%u, cfd%domain%ntcells) + end subroutine runge_kutta_3 + + ! Runge-Kutta selection + subroutine runge_kutta(cfd) + type(CfdType), intent(inout) :: cfd + + select case(cfd%config%rk_order) + case(1) + call runge_kutta_1(cfd) + case(2) + call runge_kutta_2(cfd) + case(3) + call runge_kutta_3(cfd) + case default + call runge_kutta_1(cfd) + end select + end subroutine runge_kutta + + ! =================================================================== + ! Simulation Driver + ! =================================================================== + + ! Run simulation to final time + function run_simulation(cfd, final_time) result(u_result) + type(CfdType), intent(inout) :: cfd + real(dp), intent(in) :: final_time + real(dp), allocatable :: u_result(:) + + real(dp) :: t, dt, dt_old + + allocate(u_result(cfd%domain%mesh%ncells)) + + t = 0.0_dp + dt_old = cfd%config%dt + dt = dt_old + + do while (t < final_time - 1.0e-12_dp) + if (t + dt > final_time) then + dt = final_time - t + end if + cfd%config%dt = dt + call runge_kutta(cfd) + t = t + dt + end do + + cfd%config%dt = dt_old + + ! Extract physical solution (without ghost cells) + u_result = cfd%solution%u(cfd%domain%ist:cfd%domain%ied) + end function run_simulation + + ! =================================================================== + ! Main Analysis Function + ! =================================================================== + + ! Perform ENO-WENO comparative analysis + subroutine performEnoWenoAnalysis() + type(CfdConfigType) :: config_eno3, config_weno3 + type(MeshType) :: mesh + type(ComputationalDomainType) :: domain_eno3, domain_weno3 + type(CfdType) :: cfd_eno3, cfd_weno3 + real(dp), allocatable :: u_eno(:), u_weno(:), u_analytical(:) + real(dp), allocatable :: xcc(:) + integer :: i, ncells, iunit + + ! Initialize mesh + call mesh%init() + ncells = mesh%ncells + allocate(xcc(ncells)) + xcc = mesh%xcc + + ! Configure ENO3 + config_eno3%recon_scheme = "eno" + config_eno3%spatial_order = 3 + config_eno3%flux_type = 0 + config_eno3%rk_order = 1 + config_eno3%wave_speed = 1.0_dp + config_eno3%final_time = 0.625_dp + config_eno3%dt = 0.0025_dp + + ! Configure WENO3 + config_weno3%recon_scheme = "weno" + config_weno3%spatial_order = 3 + config_weno3%flux_type = 0 + config_weno3%rk_order = 1 + config_weno3%wave_speed = 1.0_dp + config_weno3%final_time = 0.625_dp + config_weno3%dt = 0.0025_dp + + ! Create domains + call domain_eno3%init(mesh, config_eno3) + call domain_weno3%init(mesh, config_weno3) + + ! Create CFD solvers + call cfd_eno3%init(config_eno3, domain_eno3) + call cfd_weno3%init(config_weno3, domain_weno3) + + ! Allocate arrays + allocate(u_eno(ncells), u_weno(ncells), u_analytical(ncells)) + + ! Run ENO simulation + print *, "Running ENO3 simulation..." + call init_field(cfd_eno3) + u_eno = run_simulation(cfd_eno3, config_eno3%final_time) + + ! Run WENO simulation + print *, "Running WENO3 simulation..." + call init_field(cfd_weno3) + u_weno = run_simulation(cfd_weno3, config_weno3%final_time) + + ! Compute analytical solution + print *, "Computing analytical solution..." + do i = 1, ncells + u_analytical(i) = analytical_solution(xcc(i), config_weno3%final_time, & + config_weno3%wave_speed, mesh%L) + end do + + ! Write results to files + print *, "Writing results to files..." + + ! Write ENO results + open(newunit=iunit, file='eno_results.txt', status='replace') + write(iunit, '(A)') '# x, u (ENO3)' + do i = 1, ncells + write(iunit, '(2F12.6)') xcc(i), u_eno(i) + end do + close(iunit) + + ! Write WENO results + open(newunit=iunit, file='weno_results.txt', status='replace') + write(iunit, '(A)') '# x, u (WENO3)' + do i = 1, ncells + write(iunit, '(2F12.6)') xcc(i), u_weno(i) + end do + close(iunit) + + ! Write analytical results + open(newunit=iunit, file='analytical_results.txt', status='replace') + write(iunit, '(A)') '# x, u (Analytical)' + do i = 1, ncells + write(iunit, '(2F12.6)') xcc(i), u_analytical(i) + end do + close(iunit) + + print *, "==========================================" + print *, "Simulation completed successfully!" + print *, "Results written to:" + print *, " eno_results.txt" + print *, " weno_results.txt" + print *, " analytical_results.txt" + print *, "" + print *, "To generate the comparison plot, run:" + print *, " python postprocess.py" + print *, "==========================================" + + deallocate(u_eno, u_weno, u_analytical, xcc) + end subroutine performEnoWenoAnalysis + +end module cfd_solver + +! =================================================================== +! Main Program +! =================================================================== +program main + use cfd_solver + implicit none + + print *, "==========================================" + print *, "OneFLOW-CFD Solver for 1D Convection" + print *, "ENO3 vs WENO3 Comparison" + print *, "==========================================" + + call performEnoWenoAnalysis() + + print *, "Program finished successfully!" + +end program main \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/cfd/01b/postprocess.py b/example/1d-linear-convection/weno3/fortran/cfd/01b/postprocess.py new file mode 100644 index 000000000..489212dd9 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/cfd/01b/postprocess.py @@ -0,0 +1,52 @@ +import numpy as np +import matplotlib.pyplot as plt + +def read_results(filename): + """Read results from Fortran output file""" + try: + data = np.loadtxt(filename, comments='#') + return data[:, 0], data[:, 1] + except Exception as e: + print(f"Error reading {filename}: {e}") + return np.array([]), np.array([]) + +def main(): + print("Reading Fortran output files...") + + # Read all data files + x_eno, u_eno = read_results('eno_results.txt') + x_weno, u_weno = read_results('weno_results.txt') + x_analytical, u_analytical = read_results('analytical_results.txt') + + # Check if we have data + if len(x_eno) == 0 or len(x_weno) == 0 or len(x_analytical) == 0: + print("Error: Could not read all data files.") + print("Make sure to run the Fortran program first.") + return + + # Create plot + plt.figure(figsize=(10, 6)) + + # Plot results + plt.plot(x_eno, u_eno, 'bo-', linewidth=1, markersize=3, + markerfacecolor='none', label='ENO3') + plt.plot(x_weno, u_weno, 'gs-', linewidth=1, markersize=3, + markerfacecolor='none', label='WENO3') + plt.plot(x_analytical, u_analytical, 'r-', linewidth=2, label='Analytical') + + # Customize plot + plt.title('1D Convection: ENO3 vs WENO3 (t=0.625)') + plt.xlabel('x') + plt.ylabel('u') + plt.legend() + plt.grid(True, alpha=0.3) + + # Save and show + plt.tight_layout() + plt.savefig('comparison.png', dpi=150) + plt.show() + + print("Plot saved as comparison.png") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/cfd/01c/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/cfd/01c/CMakeLists.txt new file mode 100644 index 000000000..653a6751e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/cfd/01c/CMakeLists.txt @@ -0,0 +1,22 @@ +# CMakeLists.txt +cmake_minimum_required(VERSION 4.2.1) # 更高版本更好支持Fortran + +project(OneFLOW_CFD LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +# 按依赖顺序显式列出源文件(解决 Intel + VS 编译顺序问题) +set(SOURCES + cfd_solver.f90 +) + +add_executable(oneflow_cfd ${SOURCES}) + +# 包含模块目录(解决 .mod 未找到) +target_include_directories(oneflow_cfd PRIVATE ${CMAKE_Fortran_MODULE_DIRECTORY}) diff --git a/example/1d-linear-convection/weno3/fortran/cfd/01c/cfd_solver.f90 b/example/1d-linear-convection/weno3/fortran/cfd/01c/cfd_solver.f90 new file mode 100644 index 000000000..b0d385d25 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/cfd/01c/cfd_solver.f90 @@ -0,0 +1,939 @@ +! OneFLOW-CFD Solver for 1D Convection Equation +! ENO/WENO Reconstruction Comparison - Single File Implementation + +module cfd_solver + use, intrinsic :: iso_fortran_env, only: dp => real64 + implicit none + + private + + ! =================================================================== + ! Forward Type Declarations + ! =================================================================== + type, public :: CfdConfigType + character(len=10) :: recon_scheme = "eno" + integer :: flux_type = 0 + integer :: rk_order = 1 + integer :: spatial_order = 3 + real(dp) :: wave_speed = 1.0_dp + real(dp) :: final_time = 0.625_dp + real(dp) :: dt = 0.001_dp ! 减小时间步长 + real(dp) :: cfl = 0.5_dp ! CFL数 + end type CfdConfigType + + type, public :: MeshType + real(dp) :: xmin = 0.0_dp + real(dp) :: xmax = 2.0_dp + integer :: ncells = 40 + integer :: nnodes = 0 + integer :: nx = 0 + real(dp) :: L = 0.0_dp + real(dp) :: dx = 0.0_dp + real(dp), allocatable :: x(:), xcc(:) + contains + procedure :: init => mesh_init + end type MeshType + + type, public :: ComputationalDomainType + type(MeshType) :: mesh + type(CfdConfigType) :: config + integer :: nghosts = 0 + integer :: ist = 0 + integer :: ied = 0 + integer :: ntcells = 0 + contains + procedure :: init => domain_init + end type ComputationalDomainType + + type, public :: SolutionType + type(ComputationalDomainType) :: domain + real(dp), allocatable :: q_face_left(:), q_face_right(:) + real(dp), allocatable :: flux(:), res(:) + real(dp), allocatable :: u(:), un(:) + contains + procedure :: init => solution_init + end type SolutionType + + ! Main CFD solver class + type, public :: CfdType + type(CfdConfigType) :: config + type(ComputationalDomainType) :: domain + type(SolutionType) :: solution + class(*), allocatable :: reconstructor + contains + procedure :: init => cfd_init + end type CfdType + + ! Abstract reconstructor base class + type, abstract, public :: ReconstructorType + contains + procedure(reconstruct_interface), deferred, pass :: reconstruct + end type ReconstructorType + + ! Define abstract interface + abstract interface + subroutine reconstruct_interface(this, q, cfd) + import :: ReconstructorType, CfdType, dp + class(ReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + end subroutine reconstruct_interface + end interface + + ! ENO reconstructor + type, extends(ReconstructorType), public :: EnoReconstructorType + integer :: spatial_order + integer :: ntcells + integer, allocatable :: lmc(:) + real(dp), allocatable :: coef(:,:) + real(dp), allocatable :: dd(:,:) + contains + procedure :: reconstruct => eno_reconstruct + procedure :: init => eno_init + end type EnoReconstructorType + + ! WENO reconstructor + type, extends(ReconstructorType), public :: WenoReconstructorType + contains + procedure :: reconstruct => weno_reconstruct + end type WenoReconstructorType + + ! =================================================================== + ! Public Procedures + ! =================================================================== + public :: run_simulation, init_field, analytical_solution, performEnoWenoAnalysis + + ! =================================================================== + ! Module Variables + ! =================================================================== + real(dp), parameter :: eps_weno = 1.0e-6_dp + +contains + + ! =================================================================== + ! Initialization Methods + ! =================================================================== + + ! Mesh initialization + subroutine mesh_init(this) + class(MeshType), intent(inout) :: this + integer :: i + + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, dp) + + allocate(this%x(this%nnodes), this%xcc(this%ncells)) + + ! Node coordinates + do i = 1, this%nnodes + this%x(i) = this%xmin + (i-1) * this%dx + end do + + ! Cell center coordinates + do i = 1, this%ncells + this%xcc(i) = 0.5_dp * (this%x(i) + this%x(i+1)) + end do + end subroutine mesh_init + + ! Domain initialization + subroutine domain_init(this, mesh, config) + class(ComputationalDomainType), intent(inout) :: this + type(MeshType), intent(in) :: mesh + type(CfdConfigType), intent(in) :: config + + this%mesh = mesh + this%config = config + + ! Calculate ghost cells + if (trim(config%recon_scheme) == "eno") then + this%nghosts = config%spatial_order + else if (trim(config%recon_scheme) == "weno") then + this%nghosts = config%spatial_order / 2 + 1 + else + error stop "Unknown reconstruction scheme" + end if + + this%ist = this%nghosts + 1 + this%ied = this%ist + mesh%ncells - 1 + this%ntcells = mesh%ncells + 2 * this%nghosts + + print *, "Domain initialized:" + print *, " mesh.ncells = ", mesh%ncells + print *, " spatial_order = ", config%spatial_order + print *, " nghosts = ", this%nghosts + print *, " ist = ", this%ist, ", ied = ", this%ied + print *, " dx = ", mesh%dx + end subroutine domain_init + + ! Solution initialization + subroutine solution_init(this, domain) + class(SolutionType), intent(inout) :: this + type(ComputationalDomainType), intent(in) :: domain + + this%domain = domain + + allocate(this%q_face_left(domain%mesh%nnodes)) + allocate(this%q_face_right(domain%mesh%nnodes)) + allocate(this%flux(domain%mesh%nnodes)) + allocate(this%res(domain%mesh%ncells)) + allocate(this%u(domain%ntcells)) + allocate(this%un(domain%ntcells)) + + this%q_face_left = 0.0_dp + this%q_face_right = 0.0_dp + this%flux = 0.0_dp + this%res = 0.0_dp + this%u = 0.0_dp + this%un = 0.0_dp + end subroutine solution_init + + ! ENO reconstructor initialization + subroutine eno_init(this, spatial_order, ntcells) + class(EnoReconstructorType), intent(inout) :: this + integer, intent(in) :: spatial_order + integer, intent(in) :: ntcells + + this%spatial_order = spatial_order + this%ntcells = ntcells + + allocate(this%lmc(ntcells)) + allocate(this%coef(spatial_order+1, spatial_order)) + allocate(this%dd(spatial_order, ntcells)) + + this%lmc = 0 + this%coef = 0.0_dp + this%dd = 0.0_dp + + ! Initialize coefficients + call init_coef(spatial_order, this%coef) + end subroutine eno_init + + ! CFD solver initialization + subroutine cfd_init(this, config, domain) + class(CfdType), intent(inout) :: this + type(CfdConfigType), intent(in) :: config + type(ComputationalDomainType), intent(in) :: domain + + this%config = config + this%domain = domain + call this%solution%init(domain) + + ! Create reconstructor based on scheme + if (trim(config%recon_scheme) == "eno") then + allocate(EnoReconstructorType :: this%reconstructor) + select type(rec => this%reconstructor) + type is (EnoReconstructorType) + call rec%init(config%spatial_order, domain%ntcells) + end select + else if (trim(config%recon_scheme) == "weno") then + allocate(WenoReconstructorType :: this%reconstructor) + else + error stop "Unknown reconstruction scheme" + end if + + ! Adjust time step based on CFL condition + call calculate_dt(this) + end subroutine cfd_init + + ! Calculate time step based on CFL condition + subroutine calculate_dt(cfd) + type(CfdType), intent(inout) :: cfd + + real(dp) :: dt_cfl + + ! CFL condition: dt <= CFL * dx / |wave_speed| + dt_cfl = cfd%config%cfl * cfd%domain%mesh%dx / abs(cfd%config%wave_speed) + + if (cfd%config%dt > dt_cfl) then + print *, "Adjusting time step for stability:" + print *, " Original dt = ", cfd%config%dt + print *, " CFL dt = ", dt_cfl + cfd%config%dt = dt_cfl + print *, " Using dt = ", cfd%config%dt + end if + end subroutine calculate_dt + + ! =================================================================== + ! Initial Conditions and Analytical Solution + ! =================================================================== + + ! Initial condition: step function + function initial_condition(x) result(u0) + real(dp), intent(in) :: x + real(dp) :: u0 + + if (0.5_dp <= x .and. x <= 1.0_dp) then + u0 = 2.0_dp + else + u0 = 1.0_dp + end if + end function initial_condition + + ! Analytical solution with periodic BC + function analytical_solution(x, t, a, L) result(u) + real(dp), intent(in) :: x, t, a, L + real(dp) :: u, x_shifted + + x_shifted = mod(x - a * t + L, L) + u = initial_condition(x_shifted) + end function analytical_solution + + ! Initialize field with step function + subroutine init_field(cfd) + type(CfdType), intent(inout) :: cfd + integer :: i, j + + do i = 1, cfd%domain%mesh%ncells + if (0.5_dp <= cfd%domain%mesh%xcc(i) .and. cfd%domain%mesh%xcc(i) <= 1.0_dp) then + cfd%solution%u(cfd%domain%ist + i - 1) = 2.0_dp + else + cfd%solution%u(cfd%domain%ist + i - 1) = 1.0_dp + end if + end do + + call boundary(cfd%solution%u, cfd) + call update_oldfield(cfd%solution%un, cfd%solution%u, cfd%domain%ntcells) + end subroutine init_field + + ! =================================================================== + ! Boundary Conditions + ! =================================================================== + + ! Periodic boundary conditions - CORRECTED VERSION + subroutine periodic_boundary(u, cfd) + real(dp), intent(inout) :: u(:) + type(CfdType), intent(in) :: cfd + integer :: i + + ! Copy interior cells to ghost cells + ! Left ghost cells = right interior cells + do i = 1, cfd%domain%nghosts + u(cfd%domain%ist - i) = u(cfd%domain%ied - cfd%domain%nghosts + i - 1) + end do + + ! Right ghost cells = left interior cells + do i = 1, cfd%domain%nghosts + u(cfd%domain%ied + i) = u(cfd%domain%ist + i - 1) + end do + end subroutine periodic_boundary + + ! Boundary condition wrapper + subroutine boundary(u, cfd) + real(dp), intent(inout) :: u(:) + type(CfdType), intent(in) :: cfd + + call periodic_boundary(u, cfd) + end subroutine boundary + + ! =================================================================== + ! Reconstruction Methods + ! =================================================================== + + ! Initialize ENO/WENO coefficients + subroutine init_coef(spatial_order, coef) + integer, intent(in) :: spatial_order + real(dp), intent(out) :: coef(:,:) + + coef = 0.0_dp + + select case(spatial_order) + case(1) + coef(1,1) = 1.0_dp + coef(2,1) = 1.0_dp + + case(2) + coef(1,1:2) = [ 3.0_dp/2.0_dp, -1.0_dp/2.0_dp ] + coef(2,1:2) = [ 1.0_dp/2.0_dp, 1.0_dp/2.0_dp ] + coef(3,1:2) = [ -1.0_dp/2.0_dp, 3.0_dp/2.0_dp ] + + case(3) + coef(1,1:3) = [ 11.0_dp/6.0_dp, -7.0_dp/6.0_dp, 1.0_dp/3.0_dp ] + coef(2,1:3) = [ 1.0_dp/3.0_dp, 5.0_dp/6.0_dp, -1.0_dp/6.0_dp ] + coef(3,1:3) = [ -1.0_dp/6.0_dp, 5.0_dp/6.0_dp, 1.0_dp/3.0_dp ] + coef(4,1:3) = [ 1.0_dp/3.0_dp, -7.0_dp/6.0_dp, 11.0_dp/6.0_dp ] + + case default + error stop "Unsupported spatial order" + end select + end subroutine init_coef + + ! ENO reconstruction - SIMPLIFIED VERSION + subroutine first_order_reconstruct(this, q, cfd) + class(EnoReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + + integer :: i, j + integer :: nghosts, ist, ied + + nghosts = cfd%domain%nghosts + ist = cfd%domain%ist + ied = cfd%domain%ied + + ! For now, use simple 2nd order reconstruction + do i = ist, ied + j = i - ist + 1 ! 1-based index for interfaces + + ! Simple averaging for testing + !cfd%solution%q_face_left(j) = 0.5_dp * (q(i-1) + q(i)) + !cfd%solution%q_face_right(j) = 0.5_dp * (q(i) + q(i+1)) + ! Simple averaging for testing + cfd%solution%q_face_left(j) = q(i-1) + cfd%solution%q_face_right(j) = q(i) + end do + end subroutine first_order_reconstruct + + subroutine eno_reconstruct(this, q, cfd) + class(EnoReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + type(ComputationalDomainType), pointer :: domain + + integer :: i, j, m, k1, k2, r1, r2 + integer :: nghosts, ist, ied + + nghosts = cfd%domain%nghosts + ist = cfd%domain%ist + ied = cfd%domain%ied + + ! 1. 差商计算 (dd[1,:] = q) + this%dd(1, :) = q(:) + do m = 2, this%spatial_order + do j = 1, cfd%domain%ntcells - m + 1 + this%dd(m, j) = this%dd(m-1, j+1) - this%dd(m-1, j) + end do + end do + + ! 2. 选择 smoothest stencil + do i = ist - 1, ied ! Python: range(ist-1, ied+1) → ied+1-1 = ied + this%lmc(i) = i + do m = 2, this%spatial_order + if ( abs(this%dd(m, this%lmc(i) - 1) ) < abs(this%dd(m, this%lmc(i)))) then + this%lmc(i) = this%lmc(i) - 1 + end if + end do + end do + + associate ( & + q_face_left => cfd%solution%q_face_left, & + q_face_right => cfd%solution%q_face_right & + ) + ! 这里可以直接使用 q_face_left 和 q_face_right + ! 3. 重构界面值 + do i = ist, ied + j = i - ist + 1 ! 1-based index for interfaces + k1 = this%lmc(i - 1) + k2 = this%lmc(i) + r1 = (i - 1) - k1 + 1 + r2 = i - k2 + 1 + q_face_left(j) = 0.0 + q_face_right(j) = 0.0 + do m = 1, this%spatial_order + q_face_left(j) = q_face_left(j) + q(k1 + m - 1) * this%coef(r1 + 1, m) + q_face_right(j) = q_face_right(j) + q(k2 + m - 1) * this%coef(r2, m) + end do + end do + end associate + + end subroutine eno_reconstruct + + ! WENO-3 nonlinear weights for left-biased stencil + function wc3L(v1, v2, v3) result(f) + real(dp), intent(in) :: v1, v2, v3 + real(dp) :: f, s0, s1, d0, d1, c0, c1, w0, w1, q0, q1 + + ! Smoothness indicators + s0 = (v3 - v2)**2 + s1 = (v2 - v1)**2 + + ! Nonlinear weights + d0 = 2.0_dp/3.0_dp + d1 = 1.0_dp/3.0_dp + + c0 = d0 / ((eps_weno + s0)**2) + c1 = d1 / ((eps_weno + s1)**2) + + w0 = c0 / (c0 + c1) + w1 = c1 / (c0 + c1) + + ! Candidate stencils + q0 = 0.5_dp * v2 + 0.5_dp * v3 + q1 = -0.5_dp * v1 + 1.5_dp * v2 + + ! Reconstructed value + f = w0 * q0 + w1 * q1 + end function wc3L + + ! WENO-3 nonlinear weights for right-biased stencil + function wc3R(v1, v2, v3) result(f) + real(dp), intent(in) :: v1, v2, v3 + real(dp) :: f, s0, s1, d0, d1, c0, c1, w0, w1, q0, q1 + + ! Smoothness indicators + s0 = (v2 - v1)**2 + s1 = (v3 - v2)**2 + + ! Nonlinear weights + d0 = 2.0_dp/3.0_dp + d1 = 1.0_dp/3.0_dp + + c0 = d0 / ((eps_weno + s0)**2) + c1 = d1 / ((eps_weno + s1)**2) + + w0 = c0 / (c0 + c1) + w1 = c1 / (c0 + c1) + + ! Candidate stencils + q0 = 0.5_dp * v1 + 0.5_dp * v2 + q1 = 1.5_dp * v2 - 0.5_dp * v3 + + ! Reconstructed value + f = w0 * q0 + w1 * q1 + end function wc3R + + ! WENO-3 reconstruction for left interface + subroutine weno3L_periodic(cfd, u, f) + type(CfdType), intent(in) :: cfd + real(dp), intent(in) :: u(:) + real(dp), intent(out) :: f(:) + + integer :: i, j, nghosts, ist, ied + + nghosts = cfd%domain%nghosts + ist = cfd%domain%ist + ied = cfd%domain%ied + + do i = ist-1, ied-1 + j = i - (ist - 1) + f(j) = wc3L(u(i-1), u(i), u(i+1)) + end do + end subroutine weno3L_periodic + + ! WENO-3 reconstruction for right interface + subroutine weno3R_periodic(cfd, u, f) + type(CfdType), intent(in) :: cfd + real(dp), intent(in) :: u(:) + real(dp), intent(out) :: f(:) + + integer :: i, j, nghosts, ist, ied + + nghosts = cfd%domain%nghosts + ist = cfd%domain%ist + ied = cfd%domain%ied + + do i = ist, ied + j = i - ist + 1 + f(j) = wc3R(u(i-1), u(i), u(i+1)) + end do + end subroutine weno3R_periodic + + ! WENO reconstruction + subroutine weno_reconstruct(this, q, cfd) + class(WenoReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + + call weno3L_periodic(cfd, q, cfd%solution%q_face_left) + call weno3R_periodic(cfd, q, cfd%solution%q_face_right) + end subroutine weno_reconstruct + + ! General reconstruction wrapper + subroutine reconstruction(q, cfd) + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + + select type(rec => cfd%reconstructor) + type is (EnoReconstructorType) + call rec%reconstruct(q, cfd) + type is (WenoReconstructorType) + call rec%reconstruct(q, cfd) + class default + error stop "Unknown reconstructor type" + end select + end subroutine reconstruction + + ! =================================================================== + ! Flux Functions + ! =================================================================== + + ! Rusanov flux + subroutine rusanov_flux(q_face_left, q_face_right, flux, cfd) + real(dp), intent(in) :: q_face_left(:), q_face_right(:) + real(dp), intent(out) :: flux(:) + type(CfdType), intent(in) :: cfd + + integer :: i + real(dp) :: u_L, u_R, F_L, F_R, c_L, c_R, Smax + + c_L = cfd%config%wave_speed + c_R = cfd%config%wave_speed + + do i = 1, cfd%domain%mesh%nnodes + u_L = q_face_left(i) + u_R = q_face_right(i) + F_L = c_L * u_L + F_R = c_R * u_R + Smax = max(abs(c_L), abs(c_R)) + flux(i) = 0.5_dp * (F_L + F_R) - 0.5_dp * Smax * (u_R - u_L) + end do + end subroutine rusanov_flux + + ! Engquist-Osher flux + subroutine engquist_osher_flux(q_face_left, q_face_right, flux, cfd) + real(dp), intent(in) :: q_face_left(:), q_face_right(:) + real(dp), intent(out) :: flux(:) + type(CfdType), intent(in) :: cfd + + integer :: i + real(dp) :: c, cp, cm, u_L, u_R + + c = cfd%config%wave_speed + + do i = 1, cfd%domain%mesh%nnodes + cp = 0.5_dp * (c + abs(c)) + cm = 0.5_dp * (c - abs(c)) + u_L = q_face_left(i) + u_R = q_face_right(i) + flux(i) = cp * u_L + cm * u_R + end do + end subroutine engquist_osher_flux + + ! Inviscid flux selection + subroutine inviscid_flux(q_face_left, q_face_right, flux, cfd) + real(dp), intent(in) :: q_face_left(:), q_face_right(:) + real(dp), intent(out) :: flux(:) + type(CfdType), intent(in) :: cfd + + if (cfd%config%flux_type == 0) then + call rusanov_flux(q_face_left, q_face_right, flux, cfd) + else + call engquist_osher_flux(q_face_left, q_face_right, flux, cfd) + end if + end subroutine inviscid_flux + + ! =================================================================== + ! Residual Computation + ! =================================================================== + + ! Compute residual (flux divergence) - CORRECTED VERSION + subroutine residual(q, cfd) + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + + integer :: i + + ! Apply boundary conditions first + call boundary(cfd%solution%u, cfd) + + ! Reconstruction + call reconstruction(q, cfd) + + ! Compute fluxes + call inviscid_flux(cfd%solution%q_face_left, cfd%solution%q_face_right, & + cfd%solution%flux, cfd) + + ! Compute residual - corrected indexing + do i = 1, cfd%domain%mesh%ncells + cfd%solution%res(i) = -(cfd%solution%flux(i+1) - cfd%solution%flux(i)) / & + cfd%domain%mesh%dx + end do + end subroutine residual + + ! =================================================================== + ! Time Integration + ! =================================================================== + + ! Update old field + subroutine update_oldfield(qn, q, n) + real(dp), intent(out) :: qn(:) + real(dp), intent(in) :: q(:) + integer, intent(in) :: n + + qn(1:n) = q(1:n) + end subroutine update_oldfield + + ! 1st-order Runge-Kutta (Euler) - SIMPLIFIED + subroutine runge_kutta_1(cfd) + type(CfdType), intent(inout) :: cfd + + integer :: i, j + real(dp) :: dt + + dt = cfd%config%dt + + ! Apply boundary conditions + call boundary(cfd%solution%u, cfd) + + ! Compute residual + call residual(cfd%solution%u, cfd) + + ! Update solution + do i = cfd%domain%ist, cfd%domain%ied - 1 + j = i - cfd%domain%ist + 1 + cfd%solution%u(i) = cfd%solution%u(i) + dt * cfd%solution%res(j) + end do + + ! Apply boundary conditions again + call boundary(cfd%solution%u, cfd) + + ! Save old solution + call update_oldfield(cfd%solution%un, cfd%solution%u, cfd%domain%ntcells) + end subroutine runge_kutta_1 + + ! 2nd-order Runge-Kutta (Heun) + subroutine runge_kutta_2(cfd) + type(CfdType), intent(inout) :: cfd + + integer :: i, j + real(dp) :: dt + + dt = cfd%config%dt + + ! Stage 1 + call boundary(cfd%solution%u, cfd) + call residual(cfd%solution%u, cfd) + + do i = cfd%domain%ist, cfd%domain%ied - 1 + j = i - cfd%domain%ist + 1 + cfd%solution%u(i) = cfd%solution%u(i) + dt * cfd%solution%res(j) + end do + call boundary(cfd%solution%u, cfd) + + ! Stage 2 + call residual(cfd%solution%u, cfd) + do i = cfd%domain%ist, cfd%domain%ied - 1 + j = i - cfd%domain%ist + 1 + cfd%solution%u(i) = 0.5_dp * cfd%solution%un(i) + & + 0.5_dp * cfd%solution%u(i) + & + 0.5_dp * dt * cfd%solution%res(j) + end do + call boundary(cfd%solution%u, cfd) + + call update_oldfield(cfd%solution%un, cfd%solution%u, cfd%domain%ntcells) + end subroutine runge_kutta_2 + + ! Runge-Kutta selection + subroutine runge_kutta(cfd) + type(CfdType), intent(inout) :: cfd + + select case(cfd%config%rk_order) + case(1) + call runge_kutta_1(cfd) + case(2) + call runge_kutta_2(cfd) + case default + call runge_kutta_1(cfd) + end select + end subroutine runge_kutta + + ! =================================================================== + ! Simulation Driver + ! =================================================================== + + ! Run simulation to final time + function run_simulation(cfd, final_time) result(u_result) + type(CfdType), intent(inout) :: cfd + real(dp), intent(in) :: final_time + real(dp), allocatable :: u_result(:) + + real(dp) :: t, dt, dt_old + integer :: step, max_steps + + allocate(u_result(cfd%domain%mesh%ncells)) + + t = 0.0_dp + dt_old = cfd%config%dt + dt = dt_old + max_steps = 10000 ! Safety limit + + print *, "Starting time integration..." + print *, " Final time: ", final_time + print *, " Time step: ", dt + print *, " CFL number: ", cfd%config%cfl + + step = 0 + do while (t < final_time - 1.0e-12_dp .and. step < max_steps) + step = step + 1 + + if (t + dt > final_time) then + dt = final_time - t + end if + + cfd%config%dt = dt + call runge_kutta(cfd) + t = t + dt + + ! Progress report + if (mod(step, 100) == 0) then + print *, " Step ", step, ", Time = ", t + end if + end do + + if (step >= max_steps) then + print *, "Warning: Reached maximum number of steps (", max_steps, ")" + end if + + cfd%config%dt = dt_old + + print *, "Time integration completed:" + print *, " Total steps: ", step + print *, " Final time: ", t + + ! Extract physical solution (without ghost cells) + u_result = cfd%solution%u(cfd%domain%ist:cfd%domain%ied) + end function run_simulation + + ! =================================================================== + ! Main Analysis Function + ! =================================================================== + + ! Perform ENO-WENO comparative analysis + subroutine performEnoWenoAnalysis() + type(CfdConfigType) :: config_eno3, config_weno3 + type(MeshType) :: mesh + type(ComputationalDomainType) :: domain_eno3, domain_weno3 + type(CfdType) :: cfd_eno3, cfd_weno3 + real(dp), allocatable :: u_eno(:), u_weno(:), u_analytical(:) + real(dp), allocatable :: xcc(:) + integer :: i, ncells, iunit + + ! Initialize mesh + call mesh%init() + ncells = mesh%ncells + allocate(xcc(ncells)) + xcc = mesh%xcc + + print *, "==========================================" + print *, "Mesh parameters:" + print *, " ncells = ", ncells + print *, " dx = ", mesh%dx + print *, " L = ", mesh%L + print *, "==========================================" + + ! Configure ENO3 - using simple 2nd order for stability + config_eno3%recon_scheme = "eno" + config_eno3%spatial_order = 2 ! Use 2nd order for stability + config_eno3%flux_type = 0 + config_eno3%rk_order = 1 + config_eno3%wave_speed = 1.0_dp + config_eno3%final_time = 0.625_dp + config_eno3%cfl = 0.5_dp + config_eno3%dt = 0.001_dp ! Small time step + + ! Configure WENO3 + config_weno3%recon_scheme = "weno" + config_weno3%spatial_order = 3 + config_weno3%flux_type = 0 + config_weno3%rk_order = 1 + config_weno3%wave_speed = 1.0_dp + config_weno3%final_time = 0.625_dp + config_weno3%cfl = 0.3_dp ! More strict CFL for WENO + config_weno3%dt = 0.0005_dp ! Even smaller time step + + ! Create domains + call domain_eno3%init(mesh, config_eno3) + call domain_weno3%init(mesh, config_weno3) + + ! Create CFD solvers + call cfd_eno3%init(config_eno3, domain_eno3) + call cfd_weno3%init(config_weno3, domain_weno3) + + ! Allocate arrays + allocate(u_eno(ncells), u_weno(ncells), u_analytical(ncells)) + + ! Run ENO simulation + print *, "==========================================" + print *, "Running ENO simulation..." + print *, " Scheme: ENO", config_eno3%spatial_order + print *, " Time step: ", config_eno3%dt + print *, "==========================================" + + call init_field(cfd_eno3) + u_eno = run_simulation(cfd_eno3, config_eno3%final_time) + + ! Run WENO simulation + print *, "==========================================" + print *, "Running WENO simulation..." + print *, " Scheme: WENO", config_weno3%spatial_order + print *, " Time step: ", config_weno3%dt + print *, "==========================================" + + call init_field(cfd_weno3) + u_weno = run_simulation(cfd_weno3, config_weno3%final_time) + + ! Compute analytical solution + print *, "Computing analytical solution..." + do i = 1, ncells + u_analytical(i) = analytical_solution(xcc(i), config_weno3%final_time, & + config_weno3%wave_speed, mesh%L) + end do + + ! Write results to files + print *, "Writing results to files..." + + ! Write ENO results + open(newunit=iunit, file='eno_results.txt', status='replace') + write(iunit, '(A)') '# x, u (ENO)' + do i = 1, ncells + write(iunit, '(2F12.6)') xcc(i), u_eno(i) + end do + close(iunit) + + ! Write WENO results + open(newunit=iunit, file='weno_results.txt', status='replace') + write(iunit, '(A)') '# x, u (WENO)' + do i = 1, ncells + write(iunit, '(2F12.6)') xcc(i), u_weno(i) + end do + close(iunit) + + ! Write analytical results + open(newunit=iunit, file='analytical_results.txt', status='replace') + write(iunit, '(A)') '# x, u (Analytical)' + do i = 1, ncells + write(iunit, '(2F12.6)') xcc(i), u_analytical(i) + end do + close(iunit) + + ! Print some statistics + print *, "==========================================" + print *, "Simulation statistics:" + print *, " ENO min/max: ", minval(u_eno), maxval(u_eno) + print *, " WENO min/max: ", minval(u_weno), maxval(u_weno) + print *, " Analytical min/max: ", minval(u_analytical), maxval(u_analytical) + print *, "==========================================" + + print *, "Simulation completed successfully!" + print *, "Results written to:" + print *, " eno_results.txt" + print *, " weno_results.txt" + print *, " analytical_results.txt" + print *, "" + print *, "To generate the comparison plot, run:" + print *, " python postprocess.py" + print *, "==========================================" + + deallocate(u_eno, u_weno, u_analytical, xcc) + end subroutine performEnoWenoAnalysis + +end module cfd_solver + +! =================================================================== +! Main Program +! =================================================================== +program main + use cfd_solver + implicit none + + print *, "==========================================" + print *, "OneFLOW-CFD Solver for 1D Convection" + print *, "ENO vs WENO Comparison" + print *, "==========================================" + + call performEnoWenoAnalysis() + + print *, "Program finished successfully!" + +end program main \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/cfd/01c/postprocess.py b/example/1d-linear-convection/weno3/fortran/cfd/01c/postprocess.py new file mode 100644 index 000000000..489212dd9 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/cfd/01c/postprocess.py @@ -0,0 +1,52 @@ +import numpy as np +import matplotlib.pyplot as plt + +def read_results(filename): + """Read results from Fortran output file""" + try: + data = np.loadtxt(filename, comments='#') + return data[:, 0], data[:, 1] + except Exception as e: + print(f"Error reading {filename}: {e}") + return np.array([]), np.array([]) + +def main(): + print("Reading Fortran output files...") + + # Read all data files + x_eno, u_eno = read_results('eno_results.txt') + x_weno, u_weno = read_results('weno_results.txt') + x_analytical, u_analytical = read_results('analytical_results.txt') + + # Check if we have data + if len(x_eno) == 0 or len(x_weno) == 0 or len(x_analytical) == 0: + print("Error: Could not read all data files.") + print("Make sure to run the Fortran program first.") + return + + # Create plot + plt.figure(figsize=(10, 6)) + + # Plot results + plt.plot(x_eno, u_eno, 'bo-', linewidth=1, markersize=3, + markerfacecolor='none', label='ENO3') + plt.plot(x_weno, u_weno, 'gs-', linewidth=1, markersize=3, + markerfacecolor='none', label='WENO3') + plt.plot(x_analytical, u_analytical, 'r-', linewidth=2, label='Analytical') + + # Customize plot + plt.title('1D Convection: ENO3 vs WENO3 (t=0.625)') + plt.xlabel('x') + plt.ylabel('u') + plt.legend() + plt.grid(True, alpha=0.3) + + # Save and show + plt.tight_layout() + plt.savefig('comparison.png', dpi=150) + plt.show() + + print("Plot saved as comparison.png") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/cfd/01d/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/cfd/01d/CMakeLists.txt new file mode 100644 index 000000000..653a6751e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/cfd/01d/CMakeLists.txt @@ -0,0 +1,22 @@ +# CMakeLists.txt +cmake_minimum_required(VERSION 4.2.1) # 更高版本更好支持Fortran + +project(OneFLOW_CFD LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +# 按依赖顺序显式列出源文件(解决 Intel + VS 编译顺序问题) +set(SOURCES + cfd_solver.f90 +) + +add_executable(oneflow_cfd ${SOURCES}) + +# 包含模块目录(解决 .mod 未找到) +target_include_directories(oneflow_cfd PRIVATE ${CMAKE_Fortran_MODULE_DIRECTORY}) diff --git a/example/1d-linear-convection/weno3/fortran/cfd/01d/cfd_solver.f90 b/example/1d-linear-convection/weno3/fortran/cfd/01d/cfd_solver.f90 new file mode 100644 index 000000000..0c608e851 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/cfd/01d/cfd_solver.f90 @@ -0,0 +1,941 @@ +! OneFLOW-CFD Solver for 1D Convection Equation +! ENO/WENO Reconstruction Comparison - Single File Implementation + +module cfd_solver + use, intrinsic :: iso_fortran_env, only: dp => real64 + implicit none + + private + + ! =================================================================== + ! Forward Type Declarations + ! =================================================================== + type, public :: CfdConfigType + character(len=10) :: recon_scheme = "eno" + integer :: flux_type = 0 + integer :: rk_order = 1 + integer :: spatial_order = 3 + real(dp) :: wave_speed = 1.0_dp + real(dp) :: final_time = 0.625_dp + real(dp) :: dt = 0.001_dp ! 减小时间步长 + real(dp) :: cfl = 0.5_dp ! CFL数 + end type CfdConfigType + + type, public :: MeshType + real(dp) :: xmin = 0.0_dp + real(dp) :: xmax = 2.0_dp + integer :: ncells = 40 + integer :: nnodes = 0 + integer :: nx = 0 + real(dp) :: L = 0.0_dp + real(dp) :: dx = 0.0_dp + real(dp), allocatable :: x(:), xcc(:) + contains + procedure :: init => mesh_init + end type MeshType + + type, public :: ComputationalDomainType + type(MeshType) :: mesh + type(CfdConfigType) :: config + integer :: nghosts = 0 + integer :: ist = 0 + integer :: ied = 0 + integer :: ntcells = 0 + contains + procedure :: init => domain_init + end type ComputationalDomainType + + type, public :: SolutionType + type(ComputationalDomainType) :: domain + real(dp), allocatable :: q_face_left(:), q_face_right(:) + real(dp), allocatable :: flux(:), res(:) + real(dp), allocatable :: u(:), un(:) + contains + procedure :: init => solution_init + end type SolutionType + + ! Main CFD solver class + type, public :: CfdType + type(CfdConfigType) :: config + type(ComputationalDomainType) :: domain + type(SolutionType) :: solution + class(*), allocatable :: reconstructor + contains + procedure :: init => cfd_init + end type CfdType + + ! Abstract reconstructor base class + type, abstract, public :: ReconstructorType + contains + procedure(reconstruct_interface), deferred, pass :: reconstruct + end type ReconstructorType + + ! Define abstract interface + abstract interface + subroutine reconstruct_interface(this, q, cfd) + import :: ReconstructorType, CfdType, dp + class(ReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + end subroutine reconstruct_interface + end interface + + ! ENO reconstructor + type, extends(ReconstructorType), public :: EnoReconstructorType + integer :: spatial_order + integer :: ntcells + integer, allocatable :: lmc(:) + real(dp), allocatable :: coef(:,:) + real(dp), allocatable :: dd(:,:) + contains + procedure :: reconstruct => eno_reconstruct + procedure :: init => eno_init + end type EnoReconstructorType + + ! WENO reconstructor + type, extends(ReconstructorType), public :: WenoReconstructorType + contains + procedure :: reconstruct => weno_reconstruct + end type WenoReconstructorType + + ! =================================================================== + ! Public Procedures + ! =================================================================== + public :: run_simulation, init_field, analytical_solution, performEnoWenoAnalysis + + ! =================================================================== + ! Module Variables + ! =================================================================== + real(dp), parameter :: eps_weno = 1.0e-6_dp + +contains + + ! =================================================================== + ! Initialization Methods + ! =================================================================== + + ! Mesh initialization + subroutine mesh_init(this) + class(MeshType), intent(inout) :: this + integer :: i + + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, dp) + + allocate(this%x(this%nnodes), this%xcc(this%ncells)) + + ! Node coordinates + do i = 1, this%nnodes + this%x(i) = this%xmin + (i-1) * this%dx + end do + + ! Cell center coordinates + do i = 1, this%ncells + this%xcc(i) = 0.5_dp * (this%x(i) + this%x(i+1)) + end do + end subroutine mesh_init + + ! Domain initialization + subroutine domain_init(this, mesh, config) + class(ComputationalDomainType), intent(inout) :: this + type(MeshType), intent(in) :: mesh + type(CfdConfigType), intent(in) :: config + + this%mesh = mesh + this%config = config + + ! Calculate ghost cells + if (trim(config%recon_scheme) == "eno") then + this%nghosts = config%spatial_order + else if (trim(config%recon_scheme) == "weno") then + this%nghosts = config%spatial_order / 2 + 1 + else + error stop "Unknown reconstruction scheme" + end if + + this%ist = this%nghosts + 1 + this%ied = this%ist + mesh%ncells - 1 + this%ntcells = mesh%ncells + 2 * this%nghosts + + print *, "Domain initialized:" + print *, " mesh.ncells = ", mesh%ncells + print *, " spatial_order = ", config%spatial_order + print *, " nghosts = ", this%nghosts + print *, " ist = ", this%ist, ", ied = ", this%ied + print *, " dx = ", mesh%dx + end subroutine domain_init + + ! Solution initialization + subroutine solution_init(this, domain) + class(SolutionType), intent(inout) :: this + type(ComputationalDomainType), intent(in) :: domain + + this%domain = domain + + allocate(this%q_face_left(domain%mesh%nnodes)) + allocate(this%q_face_right(domain%mesh%nnodes)) + allocate(this%flux(domain%mesh%nnodes)) + allocate(this%res(domain%mesh%ncells)) + allocate(this%u(domain%ntcells)) + allocate(this%un(domain%ntcells)) + + this%q_face_left = 0.0_dp + this%q_face_right = 0.0_dp + this%flux = 0.0_dp + this%res = 0.0_dp + this%u = 0.0_dp + this%un = 0.0_dp + end subroutine solution_init + + ! ENO reconstructor initialization + subroutine eno_init(this, spatial_order, ntcells) + class(EnoReconstructorType), intent(inout) :: this + integer, intent(in) :: spatial_order + integer, intent(in) :: ntcells + + this%spatial_order = spatial_order + this%ntcells = ntcells + + allocate(this%lmc(ntcells)) + allocate(this%coef(spatial_order+1, spatial_order)) + allocate(this%dd(spatial_order, ntcells)) + + this%lmc = 0 + this%coef = 0.0_dp + this%dd = 0.0_dp + + ! Initialize coefficients + call init_coef(spatial_order, this%coef) + end subroutine eno_init + + ! CFD solver initialization + subroutine cfd_init(this, config, domain) + class(CfdType), intent(inout) :: this + type(CfdConfigType), intent(in) :: config + type(ComputationalDomainType), intent(in) :: domain + + this%config = config + this%domain = domain + call this%solution%init(domain) + + ! Create reconstructor based on scheme + if (trim(config%recon_scheme) == "eno") then + allocate(EnoReconstructorType :: this%reconstructor) + select type(rec => this%reconstructor) + type is (EnoReconstructorType) + call rec%init(config%spatial_order, domain%ntcells) + end select + else if (trim(config%recon_scheme) == "weno") then + allocate(WenoReconstructorType :: this%reconstructor) + else + error stop "Unknown reconstruction scheme" + end if + + ! Adjust time step based on CFL condition + call calculate_dt(this) + end subroutine cfd_init + + ! Calculate time step based on CFL condition + subroutine calculate_dt(cfd) + type(CfdType), intent(inout) :: cfd + + real(dp) :: dt_cfl + + ! CFL condition: dt <= CFL * dx / |wave_speed| + dt_cfl = cfd%config%cfl * cfd%domain%mesh%dx / abs(cfd%config%wave_speed) + + if (cfd%config%dt > dt_cfl) then + print *, "Adjusting time step for stability:" + print *, " Original dt = ", cfd%config%dt + print *, " CFL dt = ", dt_cfl + cfd%config%dt = dt_cfl + print *, " Using dt = ", cfd%config%dt + end if + end subroutine calculate_dt + + ! =================================================================== + ! Initial Conditions and Analytical Solution + ! =================================================================== + + ! Initial condition: step function + function initial_condition(x) result(u0) + real(dp), intent(in) :: x + real(dp) :: u0 + + if (0.5_dp <= x .and. x <= 1.0_dp) then + u0 = 2.0_dp + else + u0 = 1.0_dp + end if + end function initial_condition + + ! Analytical solution with periodic BC + function analytical_solution(x, t, a, L) result(u) + real(dp), intent(in) :: x, t, a, L + real(dp) :: u, x_shifted + + x_shifted = mod(x - a * t + L, L) + u = initial_condition(x_shifted) + end function analytical_solution + + ! Initialize field with step function + subroutine init_field(cfd) + type(CfdType), intent(inout) :: cfd + integer :: i, j + + do i = 1, cfd%domain%mesh%ncells + if (0.5_dp <= cfd%domain%mesh%xcc(i) .and. cfd%domain%mesh%xcc(i) <= 1.0_dp) then + cfd%solution%u(cfd%domain%ist + i - 1) = 2.0_dp + else + cfd%solution%u(cfd%domain%ist + i - 1) = 1.0_dp + end if + end do + + call boundary(cfd%solution%u, cfd) + call update_oldfield(cfd%solution%un, cfd%solution%u, cfd%domain%ntcells) + end subroutine init_field + + ! =================================================================== + ! Boundary Conditions + ! =================================================================== + + ! Periodic boundary conditions - CORRECTED VERSION + subroutine periodic_boundary(u, cfd) + real(dp), intent(inout) :: u(:) + type(CfdType), intent(in) :: cfd + integer :: i + + ! Copy interior cells to ghost cells + ! Left ghost cells = right interior cells + do i = 1, cfd%domain%nghosts + u(cfd%domain%ist - i) = u(cfd%domain%ied - cfd%domain%nghosts + i - 1) + end do + + ! Right ghost cells = left interior cells + do i = 1, cfd%domain%nghosts + u(cfd%domain%ied + i) = u(cfd%domain%ist + i - 1) + end do + end subroutine periodic_boundary + + ! Boundary condition wrapper + subroutine boundary(u, cfd) + real(dp), intent(inout) :: u(:) + type(CfdType), intent(in) :: cfd + + call periodic_boundary(u, cfd) + end subroutine boundary + + ! =================================================================== + ! Reconstruction Methods + ! =================================================================== + + ! Initialize ENO/WENO coefficients + subroutine init_coef(spatial_order, coef) + integer, intent(in) :: spatial_order + real(dp), intent(out) :: coef(:,:) + + coef = 0.0_dp + + select case(spatial_order) + case(1) + coef(1,1) = 1.0_dp + coef(2,1) = 1.0_dp + + case(2) + coef(1,1:2) = [ 3.0_dp/2.0_dp, -1.0_dp/2.0_dp ] + coef(2,1:2) = [ 1.0_dp/2.0_dp, 1.0_dp/2.0_dp ] + coef(3,1:2) = [ -1.0_dp/2.0_dp, 3.0_dp/2.0_dp ] + + case(3) + coef(1,1:3) = [ 11.0_dp/6.0_dp, -7.0_dp/6.0_dp, 1.0_dp/3.0_dp ] + coef(2,1:3) = [ 1.0_dp/3.0_dp, 5.0_dp/6.0_dp, -1.0_dp/6.0_dp ] + coef(3,1:3) = [ -1.0_dp/6.0_dp, 5.0_dp/6.0_dp, 1.0_dp/3.0_dp ] + coef(4,1:3) = [ 1.0_dp/3.0_dp, -7.0_dp/6.0_dp, 11.0_dp/6.0_dp ] + + case default + error stop "Unsupported spatial order" + end select + end subroutine init_coef + + ! ENO reconstruction - SIMPLIFIED VERSION + subroutine first_order_reconstruct(this, q, cfd) + class(EnoReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + + integer :: i, j + integer :: nghosts, ist, ied + + nghosts = cfd%domain%nghosts + ist = cfd%domain%ist + ied = cfd%domain%ied + + ! For now, use simple 2nd order reconstruction + do i = ist, ied + j = i - ist + 1 ! 1-based index for interfaces + + ! Simple averaging for testing + !cfd%solution%q_face_left(j) = 0.5_dp * (q(i-1) + q(i)) + !cfd%solution%q_face_right(j) = 0.5_dp * (q(i) + q(i+1)) + ! Simple averaging for testing + cfd%solution%q_face_left(j) = q(i-1) + cfd%solution%q_face_right(j) = q(i) + end do + end subroutine first_order_reconstruct + + subroutine eno_reconstruct(this, q, cfd) + class(EnoReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + type(ComputationalDomainType), pointer :: domain + + integer :: i, j, m, k1, k2, r1, r2 + integer :: nghosts, ist, ied + + nghosts = cfd%domain%nghosts + ist = cfd%domain%ist + ied = cfd%domain%ied + + ! 1. 差商计算 (dd[1,:] = q) + this%dd(1, :) = q(:) + do m = 2, this%spatial_order + do j = 1, cfd%domain%ntcells - m + 1 + this%dd(m, j) = this%dd(m-1, j+1) - this%dd(m-1, j) + end do + end do + + ! 2. 选择 smoothest stencil + do i = ist - 1, ied ! Python: range(ist-1, ied+1) → ied+1-1 = ied + this%lmc(i) = i + do m = 2, this%spatial_order + if ( abs(this%dd(m, this%lmc(i) - 1) ) < abs(this%dd(m, this%lmc(i)))) then + this%lmc(i) = this%lmc(i) - 1 + end if + end do + end do + + associate ( & + q_face_left => cfd%solution%q_face_left, & + q_face_right => cfd%solution%q_face_right & + ) + ! 这里可以直接使用 q_face_left 和 q_face_right + ! 3. 重构界面值 + do i = ist, ied + j = i - ist + 1 ! 1-based index for interfaces + k1 = this%lmc(i - 1) + k2 = this%lmc(i) + r1 = (i - 1) - k1 + 1 + r2 = i - k2 + 1 + q_face_left(j) = 0.0 + q_face_right(j) = 0.0 + do m = 1, this%spatial_order + q_face_left(j) = q_face_left(j) + q(k1 + m - 1) * this%coef(r1 + 1, m) + q_face_right(j) = q_face_right(j) + q(k2 + m - 1) * this%coef(r2, m) + end do + end do + end associate + + end subroutine eno_reconstruct + + ! WENO-3 nonlinear weights for left-biased stencil + function wc3L(v1, v2, v3) result(f) + real(dp), intent(in) :: v1, v2, v3 + real(dp) :: f, s0, s1, d0, d1, c0, c1, w0, w1, q0, q1 + + ! Smoothness indicators + s0 = (v3 - v2)**2 + s1 = (v2 - v1)**2 + + ! Nonlinear weights + d0 = 2.0_dp/3.0_dp + d1 = 1.0_dp/3.0_dp + + c0 = d0 / ((eps_weno + s0)**2) + c1 = d1 / ((eps_weno + s1)**2) + + w0 = c0 / (c0 + c1) + w1 = c1 / (c0 + c1) + + ! Candidate stencils + q0 = 0.5_dp * v2 + 0.5_dp * v3 + q1 = -0.5_dp * v1 + 1.5_dp * v2 + + ! Reconstructed value + f = w0 * q0 + w1 * q1 + end function wc3L + + ! WENO-3 nonlinear weights for right-biased stencil + function wc3R(v1, v2, v3) result(f) + real(dp), intent(in) :: v1, v2, v3 + real(dp) :: f, s0, s1, d0, d1, c0, c1, w0, w1, q0, q1 + + ! Smoothness indicators + s0 = (v2 - v1)**2 + s1 = (v3 - v2)**2 + + ! Nonlinear weights + d0 = 2.0_dp/3.0_dp + d1 = 1.0_dp/3.0_dp + + c0 = d0 / ((eps_weno + s0)**2) + c1 = d1 / ((eps_weno + s1)**2) + + w0 = c0 / (c0 + c1) + w1 = c1 / (c0 + c1) + + ! Candidate stencils + q0 = 0.5_dp * v1 + 0.5_dp * v2 + q1 = 1.5_dp * v2 - 0.5_dp * v3 + + ! Reconstructed value + f = w0 * q0 + w1 * q1 + end function wc3R + + ! WENO-3 reconstruction for left interface + subroutine weno3L_periodic(cfd, u, qL) + type(CfdType), intent(in) :: cfd + real(dp), intent(in) :: u(:) + real(dp), intent(out) :: qL(:) + + integer :: i, j, nghosts, ist, ied + + nghosts = cfd%domain%nghosts + ist = cfd%domain%ist + ied = cfd%domain%ied + + do i = ist-1, ied-1 + j = i - (ist - 1) + 1 + qL(j) = wc3L(u(i-1), u(i), u(i+1)) + end do + + end subroutine weno3L_periodic + + ! WENO-3 reconstruction for right interface + subroutine weno3R_periodic(cfd, u, qR) + type(CfdType), intent(in) :: cfd + real(dp), intent(in) :: u(:) + real(dp), intent(out) :: qR(:) + + integer :: i, j, nghosts, ist, ied + + nghosts = cfd%domain%nghosts + ist = cfd%domain%ist + ied = cfd%domain%ied + + do i = ist, ied + j = i - ist + 1 + qR(j) = wc3R(u(i-1), u(i), u(i+1)) + end do + + end subroutine weno3R_periodic + + ! WENO reconstruction + subroutine weno_reconstruct(this, q, cfd) + class(WenoReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + + call weno3L_periodic(cfd, q, cfd%solution%q_face_left) + call weno3R_periodic(cfd, q, cfd%solution%q_face_right) + end subroutine weno_reconstruct + + ! General reconstruction wrapper + subroutine reconstruction(q, cfd) + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + + select type(rec => cfd%reconstructor) + type is (EnoReconstructorType) + call rec%reconstruct(q, cfd) + type is (WenoReconstructorType) + call rec%reconstruct(q, cfd) + class default + error stop "Unknown reconstructor type" + end select + end subroutine reconstruction + + ! =================================================================== + ! Flux Functions + ! =================================================================== + + ! Rusanov flux + subroutine rusanov_flux(q_face_left, q_face_right, flux, cfd) + real(dp), intent(in) :: q_face_left(:), q_face_right(:) + real(dp), intent(out) :: flux(:) + type(CfdType), intent(in) :: cfd + + integer :: i + real(dp) :: u_L, u_R, F_L, F_R, c_L, c_R, Smax + + c_L = cfd%config%wave_speed + c_R = cfd%config%wave_speed + + do i = 1, cfd%domain%mesh%nnodes + u_L = q_face_left(i) + u_R = q_face_right(i) + F_L = c_L * u_L + F_R = c_R * u_R + Smax = max(abs(c_L), abs(c_R)) + flux(i) = 0.5_dp * (F_L + F_R) - 0.5_dp * Smax * (u_R - u_L) + end do + end subroutine rusanov_flux + + ! Engquist-Osher flux + subroutine engquist_osher_flux(q_face_left, q_face_right, flux, cfd) + real(dp), intent(in) :: q_face_left(:), q_face_right(:) + real(dp), intent(out) :: flux(:) + type(CfdType), intent(in) :: cfd + + integer :: i + real(dp) :: c, cp, cm, u_L, u_R + + c = cfd%config%wave_speed + + do i = 1, cfd%domain%mesh%nnodes + cp = 0.5_dp * (c + abs(c)) + cm = 0.5_dp * (c - abs(c)) + u_L = q_face_left(i) + u_R = q_face_right(i) + flux(i) = cp * u_L + cm * u_R + end do + end subroutine engquist_osher_flux + + ! Inviscid flux selection + subroutine inviscid_flux(q_face_left, q_face_right, flux, cfd) + real(dp), intent(in) :: q_face_left(:), q_face_right(:) + real(dp), intent(out) :: flux(:) + type(CfdType), intent(in) :: cfd + + if (cfd%config%flux_type == 0) then + call rusanov_flux(q_face_left, q_face_right, flux, cfd) + else + call engquist_osher_flux(q_face_left, q_face_right, flux, cfd) + end if + end subroutine inviscid_flux + + ! =================================================================== + ! Residual Computation + ! =================================================================== + + ! Compute residual (flux divergence) - CORRECTED VERSION + subroutine residual(q, cfd) + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + + integer :: i + + ! Apply boundary conditions first + call boundary(cfd%solution%u, cfd) + + ! Reconstruction + call reconstruction(q, cfd) + + ! Compute fluxes + call inviscid_flux(cfd%solution%q_face_left, cfd%solution%q_face_right, & + cfd%solution%flux, cfd) + + ! Compute residual - corrected indexing + do i = 1, cfd%domain%mesh%ncells + cfd%solution%res(i) = -(cfd%solution%flux(i+1) - cfd%solution%flux(i)) / & + cfd%domain%mesh%dx + end do + end subroutine residual + + ! =================================================================== + ! Time Integration + ! =================================================================== + + ! Update old field + subroutine update_oldfield(qn, q, n) + real(dp), intent(out) :: qn(:) + real(dp), intent(in) :: q(:) + integer, intent(in) :: n + + qn(1:n) = q(1:n) + end subroutine update_oldfield + + ! 1st-order Runge-Kutta (Euler) - SIMPLIFIED + subroutine runge_kutta_1(cfd) + type(CfdType), intent(inout) :: cfd + + integer :: i, j + real(dp) :: dt + + dt = cfd%config%dt + + ! Apply boundary conditions + call boundary(cfd%solution%u, cfd) + + ! Compute residual + call residual(cfd%solution%u, cfd) + + ! Update solution + do i = cfd%domain%ist, cfd%domain%ied - 1 + j = i - cfd%domain%ist + 1 + cfd%solution%u(i) = cfd%solution%u(i) + dt * cfd%solution%res(j) + end do + + ! Apply boundary conditions again + call boundary(cfd%solution%u, cfd) + + ! Save old solution + call update_oldfield(cfd%solution%un, cfd%solution%u, cfd%domain%ntcells) + end subroutine runge_kutta_1 + + ! 2nd-order Runge-Kutta (Heun) + subroutine runge_kutta_2(cfd) + type(CfdType), intent(inout) :: cfd + + integer :: i, j + real(dp) :: dt + + dt = cfd%config%dt + + ! Stage 1 + call boundary(cfd%solution%u, cfd) + call residual(cfd%solution%u, cfd) + + do i = cfd%domain%ist, cfd%domain%ied - 1 + j = i - cfd%domain%ist + 1 + cfd%solution%u(i) = cfd%solution%u(i) + dt * cfd%solution%res(j) + end do + call boundary(cfd%solution%u, cfd) + + ! Stage 2 + call residual(cfd%solution%u, cfd) + do i = cfd%domain%ist, cfd%domain%ied - 1 + j = i - cfd%domain%ist + 1 + cfd%solution%u(i) = 0.5_dp * cfd%solution%un(i) + & + 0.5_dp * cfd%solution%u(i) + & + 0.5_dp * dt * cfd%solution%res(j) + end do + call boundary(cfd%solution%u, cfd) + + call update_oldfield(cfd%solution%un, cfd%solution%u, cfd%domain%ntcells) + end subroutine runge_kutta_2 + + ! Runge-Kutta selection + subroutine runge_kutta(cfd) + type(CfdType), intent(inout) :: cfd + + select case(cfd%config%rk_order) + case(1) + call runge_kutta_1(cfd) + case(2) + call runge_kutta_2(cfd) + case default + call runge_kutta_1(cfd) + end select + end subroutine runge_kutta + + ! =================================================================== + ! Simulation Driver + ! =================================================================== + + ! Run simulation to final time + function run_simulation(cfd, final_time) result(u_result) + type(CfdType), intent(inout) :: cfd + real(dp), intent(in) :: final_time + real(dp), allocatable :: u_result(:) + + real(dp) :: t, dt, dt_old + integer :: step, max_steps + + allocate(u_result(cfd%domain%mesh%ncells)) + + t = 0.0_dp + dt_old = cfd%config%dt + dt = dt_old + max_steps = 10000 ! Safety limit + + print *, "Starting time integration..." + print *, " Final time: ", final_time + print *, " Time step: ", dt + print *, " CFL number: ", cfd%config%cfl + + step = 0 + do while (t < final_time - 1.0e-12_dp .and. step < max_steps) + step = step + 1 + + if (t + dt > final_time) then + dt = final_time - t + end if + + cfd%config%dt = dt + call runge_kutta(cfd) + t = t + dt + + ! Progress report + if (mod(step, 100) == 0) then + print *, " Step ", step, ", Time = ", t + end if + end do + + if (step >= max_steps) then + print *, "Warning: Reached maximum number of steps (", max_steps, ")" + end if + + cfd%config%dt = dt_old + + print *, "Time integration completed:" + print *, " Total steps: ", step + print *, " Final time: ", t + + ! Extract physical solution (without ghost cells) + u_result = cfd%solution%u(cfd%domain%ist:cfd%domain%ied) + end function run_simulation + + ! =================================================================== + ! Main Analysis Function + ! =================================================================== + + ! Perform ENO-WENO comparative analysis + subroutine performEnoWenoAnalysis() + type(CfdConfigType) :: config_eno3, config_weno3 + type(MeshType) :: mesh + type(ComputationalDomainType) :: domain_eno3, domain_weno3 + type(CfdType) :: cfd_eno3, cfd_weno3 + real(dp), allocatable :: u_eno(:), u_weno(:), u_analytical(:) + real(dp), allocatable :: xcc(:) + integer :: i, ncells, iunit + + ! Initialize mesh + call mesh%init() + ncells = mesh%ncells + allocate(xcc(ncells)) + xcc = mesh%xcc + + print *, "==========================================" + print *, "Mesh parameters:" + print *, " ncells = ", ncells + print *, " dx = ", mesh%dx + print *, " L = ", mesh%L + print *, "==========================================" + + ! Configure ENO3 - using simple 2nd order for stability + config_eno3%recon_scheme = "eno" + config_eno3%spatial_order = 2 ! Use 2nd order for stability + config_eno3%flux_type = 0 + config_eno3%rk_order = 1 + config_eno3%wave_speed = 1.0_dp + config_eno3%final_time = 0.625_dp + config_eno3%cfl = 0.5_dp + config_eno3%dt = 0.001_dp ! Small time step + + ! Configure WENO3 + config_weno3%recon_scheme = "weno" + config_weno3%spatial_order = 3 + config_weno3%flux_type = 0 + config_weno3%rk_order = 1 + config_weno3%wave_speed = 1.0_dp + config_weno3%final_time = 0.625_dp + config_weno3%cfl = 0.3_dp ! More strict CFL for WENO + config_weno3%dt = 0.0005_dp ! Even smaller time step + + ! Create domains + call domain_eno3%init(mesh, config_eno3) + call domain_weno3%init(mesh, config_weno3) + + ! Create CFD solvers + call cfd_eno3%init(config_eno3, domain_eno3) + call cfd_weno3%init(config_weno3, domain_weno3) + + ! Allocate arrays + allocate(u_eno(ncells), u_weno(ncells), u_analytical(ncells)) + + ! Run ENO simulation + print *, "==========================================" + print *, "Running ENO simulation..." + print *, " Scheme: ENO", config_eno3%spatial_order + print *, " Time step: ", config_eno3%dt + print *, "==========================================" + + call init_field(cfd_eno3) + u_eno = run_simulation(cfd_eno3, config_eno3%final_time) + + ! Run WENO simulation + print *, "==========================================" + print *, "Running WENO simulation..." + print *, " Scheme: WENO", config_weno3%spatial_order + print *, " Time step: ", config_weno3%dt + print *, "==========================================" + + call init_field(cfd_weno3) + u_weno = run_simulation(cfd_weno3, config_weno3%final_time) + + ! Compute analytical solution + print *, "Computing analytical solution..." + do i = 1, ncells + u_analytical(i) = analytical_solution(xcc(i), config_weno3%final_time, & + config_weno3%wave_speed, mesh%L) + end do + + ! Write results to files + print *, "Writing results to files..." + + ! Write ENO results + open(newunit=iunit, file='eno_results.txt', status='replace') + write(iunit, '(A)') '# x, u (ENO)' + do i = 1, ncells + write(iunit, '(2F12.6)') xcc(i), u_eno(i) + end do + close(iunit) + + ! Write WENO results + open(newunit=iunit, file='weno_results.txt', status='replace') + write(iunit, '(A)') '# x, u (WENO)' + do i = 1, ncells + write(iunit, '(2F12.6)') xcc(i), u_weno(i) + end do + close(iunit) + + ! Write analytical results + open(newunit=iunit, file='analytical_results.txt', status='replace') + write(iunit, '(A)') '# x, u (Analytical)' + do i = 1, ncells + write(iunit, '(2F12.6)') xcc(i), u_analytical(i) + end do + close(iunit) + + ! Print some statistics + print *, "==========================================" + print *, "Simulation statistics:" + print *, " ENO min/max: ", minval(u_eno), maxval(u_eno) + print *, " WENO min/max: ", minval(u_weno), maxval(u_weno) + print *, " Analytical min/max: ", minval(u_analytical), maxval(u_analytical) + print *, "==========================================" + + print *, "Simulation completed successfully!" + print *, "Results written to:" + print *, " eno_results.txt" + print *, " weno_results.txt" + print *, " analytical_results.txt" + print *, "" + print *, "To generate the comparison plot, run:" + print *, " python postprocess.py" + print *, "==========================================" + + deallocate(u_eno, u_weno, u_analytical, xcc) + end subroutine performEnoWenoAnalysis + +end module cfd_solver + +! =================================================================== +! Main Program +! =================================================================== +program main + use cfd_solver + implicit none + + print *, "==========================================" + print *, "OneFLOW-CFD Solver for 1D Convection" + print *, "ENO vs WENO Comparison" + print *, "==========================================" + + call performEnoWenoAnalysis() + + print *, "Program finished successfully!" + +end program main \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/cfd/01d/postprocess.py b/example/1d-linear-convection/weno3/fortran/cfd/01d/postprocess.py new file mode 100644 index 000000000..489212dd9 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/cfd/01d/postprocess.py @@ -0,0 +1,52 @@ +import numpy as np +import matplotlib.pyplot as plt + +def read_results(filename): + """Read results from Fortran output file""" + try: + data = np.loadtxt(filename, comments='#') + return data[:, 0], data[:, 1] + except Exception as e: + print(f"Error reading {filename}: {e}") + return np.array([]), np.array([]) + +def main(): + print("Reading Fortran output files...") + + # Read all data files + x_eno, u_eno = read_results('eno_results.txt') + x_weno, u_weno = read_results('weno_results.txt') + x_analytical, u_analytical = read_results('analytical_results.txt') + + # Check if we have data + if len(x_eno) == 0 or len(x_weno) == 0 or len(x_analytical) == 0: + print("Error: Could not read all data files.") + print("Make sure to run the Fortran program first.") + return + + # Create plot + plt.figure(figsize=(10, 6)) + + # Plot results + plt.plot(x_eno, u_eno, 'bo-', linewidth=1, markersize=3, + markerfacecolor='none', label='ENO3') + plt.plot(x_weno, u_weno, 'gs-', linewidth=1, markersize=3, + markerfacecolor='none', label='WENO3') + plt.plot(x_analytical, u_analytical, 'r-', linewidth=2, label='Analytical') + + # Customize plot + plt.title('1D Convection: ENO3 vs WENO3 (t=0.625)') + plt.xlabel('x') + plt.ylabel('u') + plt.legend() + plt.grid(True, alpha=0.3) + + # Save and show + plt.tight_layout() + plt.savefig('comparison.png', dpi=150) + plt.show() + + print("Plot saved as comparison.png") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/cfd/01e/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/cfd/01e/CMakeLists.txt new file mode 100644 index 000000000..653a6751e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/cfd/01e/CMakeLists.txt @@ -0,0 +1,22 @@ +# CMakeLists.txt +cmake_minimum_required(VERSION 4.2.1) # 更高版本更好支持Fortran + +project(OneFLOW_CFD LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +# 按依赖顺序显式列出源文件(解决 Intel + VS 编译顺序问题) +set(SOURCES + cfd_solver.f90 +) + +add_executable(oneflow_cfd ${SOURCES}) + +# 包含模块目录(解决 .mod 未找到) +target_include_directories(oneflow_cfd PRIVATE ${CMAKE_Fortran_MODULE_DIRECTORY}) diff --git a/example/1d-linear-convection/weno3/fortran/cfd/01e/cfd_solver.f90 b/example/1d-linear-convection/weno3/fortran/cfd/01e/cfd_solver.f90 new file mode 100644 index 000000000..0c608e851 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/cfd/01e/cfd_solver.f90 @@ -0,0 +1,941 @@ +! OneFLOW-CFD Solver for 1D Convection Equation +! ENO/WENO Reconstruction Comparison - Single File Implementation + +module cfd_solver + use, intrinsic :: iso_fortran_env, only: dp => real64 + implicit none + + private + + ! =================================================================== + ! Forward Type Declarations + ! =================================================================== + type, public :: CfdConfigType + character(len=10) :: recon_scheme = "eno" + integer :: flux_type = 0 + integer :: rk_order = 1 + integer :: spatial_order = 3 + real(dp) :: wave_speed = 1.0_dp + real(dp) :: final_time = 0.625_dp + real(dp) :: dt = 0.001_dp ! 减小时间步长 + real(dp) :: cfl = 0.5_dp ! CFL数 + end type CfdConfigType + + type, public :: MeshType + real(dp) :: xmin = 0.0_dp + real(dp) :: xmax = 2.0_dp + integer :: ncells = 40 + integer :: nnodes = 0 + integer :: nx = 0 + real(dp) :: L = 0.0_dp + real(dp) :: dx = 0.0_dp + real(dp), allocatable :: x(:), xcc(:) + contains + procedure :: init => mesh_init + end type MeshType + + type, public :: ComputationalDomainType + type(MeshType) :: mesh + type(CfdConfigType) :: config + integer :: nghosts = 0 + integer :: ist = 0 + integer :: ied = 0 + integer :: ntcells = 0 + contains + procedure :: init => domain_init + end type ComputationalDomainType + + type, public :: SolutionType + type(ComputationalDomainType) :: domain + real(dp), allocatable :: q_face_left(:), q_face_right(:) + real(dp), allocatable :: flux(:), res(:) + real(dp), allocatable :: u(:), un(:) + contains + procedure :: init => solution_init + end type SolutionType + + ! Main CFD solver class + type, public :: CfdType + type(CfdConfigType) :: config + type(ComputationalDomainType) :: domain + type(SolutionType) :: solution + class(*), allocatable :: reconstructor + contains + procedure :: init => cfd_init + end type CfdType + + ! Abstract reconstructor base class + type, abstract, public :: ReconstructorType + contains + procedure(reconstruct_interface), deferred, pass :: reconstruct + end type ReconstructorType + + ! Define abstract interface + abstract interface + subroutine reconstruct_interface(this, q, cfd) + import :: ReconstructorType, CfdType, dp + class(ReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + end subroutine reconstruct_interface + end interface + + ! ENO reconstructor + type, extends(ReconstructorType), public :: EnoReconstructorType + integer :: spatial_order + integer :: ntcells + integer, allocatable :: lmc(:) + real(dp), allocatable :: coef(:,:) + real(dp), allocatable :: dd(:,:) + contains + procedure :: reconstruct => eno_reconstruct + procedure :: init => eno_init + end type EnoReconstructorType + + ! WENO reconstructor + type, extends(ReconstructorType), public :: WenoReconstructorType + contains + procedure :: reconstruct => weno_reconstruct + end type WenoReconstructorType + + ! =================================================================== + ! Public Procedures + ! =================================================================== + public :: run_simulation, init_field, analytical_solution, performEnoWenoAnalysis + + ! =================================================================== + ! Module Variables + ! =================================================================== + real(dp), parameter :: eps_weno = 1.0e-6_dp + +contains + + ! =================================================================== + ! Initialization Methods + ! =================================================================== + + ! Mesh initialization + subroutine mesh_init(this) + class(MeshType), intent(inout) :: this + integer :: i + + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, dp) + + allocate(this%x(this%nnodes), this%xcc(this%ncells)) + + ! Node coordinates + do i = 1, this%nnodes + this%x(i) = this%xmin + (i-1) * this%dx + end do + + ! Cell center coordinates + do i = 1, this%ncells + this%xcc(i) = 0.5_dp * (this%x(i) + this%x(i+1)) + end do + end subroutine mesh_init + + ! Domain initialization + subroutine domain_init(this, mesh, config) + class(ComputationalDomainType), intent(inout) :: this + type(MeshType), intent(in) :: mesh + type(CfdConfigType), intent(in) :: config + + this%mesh = mesh + this%config = config + + ! Calculate ghost cells + if (trim(config%recon_scheme) == "eno") then + this%nghosts = config%spatial_order + else if (trim(config%recon_scheme) == "weno") then + this%nghosts = config%spatial_order / 2 + 1 + else + error stop "Unknown reconstruction scheme" + end if + + this%ist = this%nghosts + 1 + this%ied = this%ist + mesh%ncells - 1 + this%ntcells = mesh%ncells + 2 * this%nghosts + + print *, "Domain initialized:" + print *, " mesh.ncells = ", mesh%ncells + print *, " spatial_order = ", config%spatial_order + print *, " nghosts = ", this%nghosts + print *, " ist = ", this%ist, ", ied = ", this%ied + print *, " dx = ", mesh%dx + end subroutine domain_init + + ! Solution initialization + subroutine solution_init(this, domain) + class(SolutionType), intent(inout) :: this + type(ComputationalDomainType), intent(in) :: domain + + this%domain = domain + + allocate(this%q_face_left(domain%mesh%nnodes)) + allocate(this%q_face_right(domain%mesh%nnodes)) + allocate(this%flux(domain%mesh%nnodes)) + allocate(this%res(domain%mesh%ncells)) + allocate(this%u(domain%ntcells)) + allocate(this%un(domain%ntcells)) + + this%q_face_left = 0.0_dp + this%q_face_right = 0.0_dp + this%flux = 0.0_dp + this%res = 0.0_dp + this%u = 0.0_dp + this%un = 0.0_dp + end subroutine solution_init + + ! ENO reconstructor initialization + subroutine eno_init(this, spatial_order, ntcells) + class(EnoReconstructorType), intent(inout) :: this + integer, intent(in) :: spatial_order + integer, intent(in) :: ntcells + + this%spatial_order = spatial_order + this%ntcells = ntcells + + allocate(this%lmc(ntcells)) + allocate(this%coef(spatial_order+1, spatial_order)) + allocate(this%dd(spatial_order, ntcells)) + + this%lmc = 0 + this%coef = 0.0_dp + this%dd = 0.0_dp + + ! Initialize coefficients + call init_coef(spatial_order, this%coef) + end subroutine eno_init + + ! CFD solver initialization + subroutine cfd_init(this, config, domain) + class(CfdType), intent(inout) :: this + type(CfdConfigType), intent(in) :: config + type(ComputationalDomainType), intent(in) :: domain + + this%config = config + this%domain = domain + call this%solution%init(domain) + + ! Create reconstructor based on scheme + if (trim(config%recon_scheme) == "eno") then + allocate(EnoReconstructorType :: this%reconstructor) + select type(rec => this%reconstructor) + type is (EnoReconstructorType) + call rec%init(config%spatial_order, domain%ntcells) + end select + else if (trim(config%recon_scheme) == "weno") then + allocate(WenoReconstructorType :: this%reconstructor) + else + error stop "Unknown reconstruction scheme" + end if + + ! Adjust time step based on CFL condition + call calculate_dt(this) + end subroutine cfd_init + + ! Calculate time step based on CFL condition + subroutine calculate_dt(cfd) + type(CfdType), intent(inout) :: cfd + + real(dp) :: dt_cfl + + ! CFL condition: dt <= CFL * dx / |wave_speed| + dt_cfl = cfd%config%cfl * cfd%domain%mesh%dx / abs(cfd%config%wave_speed) + + if (cfd%config%dt > dt_cfl) then + print *, "Adjusting time step for stability:" + print *, " Original dt = ", cfd%config%dt + print *, " CFL dt = ", dt_cfl + cfd%config%dt = dt_cfl + print *, " Using dt = ", cfd%config%dt + end if + end subroutine calculate_dt + + ! =================================================================== + ! Initial Conditions and Analytical Solution + ! =================================================================== + + ! Initial condition: step function + function initial_condition(x) result(u0) + real(dp), intent(in) :: x + real(dp) :: u0 + + if (0.5_dp <= x .and. x <= 1.0_dp) then + u0 = 2.0_dp + else + u0 = 1.0_dp + end if + end function initial_condition + + ! Analytical solution with periodic BC + function analytical_solution(x, t, a, L) result(u) + real(dp), intent(in) :: x, t, a, L + real(dp) :: u, x_shifted + + x_shifted = mod(x - a * t + L, L) + u = initial_condition(x_shifted) + end function analytical_solution + + ! Initialize field with step function + subroutine init_field(cfd) + type(CfdType), intent(inout) :: cfd + integer :: i, j + + do i = 1, cfd%domain%mesh%ncells + if (0.5_dp <= cfd%domain%mesh%xcc(i) .and. cfd%domain%mesh%xcc(i) <= 1.0_dp) then + cfd%solution%u(cfd%domain%ist + i - 1) = 2.0_dp + else + cfd%solution%u(cfd%domain%ist + i - 1) = 1.0_dp + end if + end do + + call boundary(cfd%solution%u, cfd) + call update_oldfield(cfd%solution%un, cfd%solution%u, cfd%domain%ntcells) + end subroutine init_field + + ! =================================================================== + ! Boundary Conditions + ! =================================================================== + + ! Periodic boundary conditions - CORRECTED VERSION + subroutine periodic_boundary(u, cfd) + real(dp), intent(inout) :: u(:) + type(CfdType), intent(in) :: cfd + integer :: i + + ! Copy interior cells to ghost cells + ! Left ghost cells = right interior cells + do i = 1, cfd%domain%nghosts + u(cfd%domain%ist - i) = u(cfd%domain%ied - cfd%domain%nghosts + i - 1) + end do + + ! Right ghost cells = left interior cells + do i = 1, cfd%domain%nghosts + u(cfd%domain%ied + i) = u(cfd%domain%ist + i - 1) + end do + end subroutine periodic_boundary + + ! Boundary condition wrapper + subroutine boundary(u, cfd) + real(dp), intent(inout) :: u(:) + type(CfdType), intent(in) :: cfd + + call periodic_boundary(u, cfd) + end subroutine boundary + + ! =================================================================== + ! Reconstruction Methods + ! =================================================================== + + ! Initialize ENO/WENO coefficients + subroutine init_coef(spatial_order, coef) + integer, intent(in) :: spatial_order + real(dp), intent(out) :: coef(:,:) + + coef = 0.0_dp + + select case(spatial_order) + case(1) + coef(1,1) = 1.0_dp + coef(2,1) = 1.0_dp + + case(2) + coef(1,1:2) = [ 3.0_dp/2.0_dp, -1.0_dp/2.0_dp ] + coef(2,1:2) = [ 1.0_dp/2.0_dp, 1.0_dp/2.0_dp ] + coef(3,1:2) = [ -1.0_dp/2.0_dp, 3.0_dp/2.0_dp ] + + case(3) + coef(1,1:3) = [ 11.0_dp/6.0_dp, -7.0_dp/6.0_dp, 1.0_dp/3.0_dp ] + coef(2,1:3) = [ 1.0_dp/3.0_dp, 5.0_dp/6.0_dp, -1.0_dp/6.0_dp ] + coef(3,1:3) = [ -1.0_dp/6.0_dp, 5.0_dp/6.0_dp, 1.0_dp/3.0_dp ] + coef(4,1:3) = [ 1.0_dp/3.0_dp, -7.0_dp/6.0_dp, 11.0_dp/6.0_dp ] + + case default + error stop "Unsupported spatial order" + end select + end subroutine init_coef + + ! ENO reconstruction - SIMPLIFIED VERSION + subroutine first_order_reconstruct(this, q, cfd) + class(EnoReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + + integer :: i, j + integer :: nghosts, ist, ied + + nghosts = cfd%domain%nghosts + ist = cfd%domain%ist + ied = cfd%domain%ied + + ! For now, use simple 2nd order reconstruction + do i = ist, ied + j = i - ist + 1 ! 1-based index for interfaces + + ! Simple averaging for testing + !cfd%solution%q_face_left(j) = 0.5_dp * (q(i-1) + q(i)) + !cfd%solution%q_face_right(j) = 0.5_dp * (q(i) + q(i+1)) + ! Simple averaging for testing + cfd%solution%q_face_left(j) = q(i-1) + cfd%solution%q_face_right(j) = q(i) + end do + end subroutine first_order_reconstruct + + subroutine eno_reconstruct(this, q, cfd) + class(EnoReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + type(ComputationalDomainType), pointer :: domain + + integer :: i, j, m, k1, k2, r1, r2 + integer :: nghosts, ist, ied + + nghosts = cfd%domain%nghosts + ist = cfd%domain%ist + ied = cfd%domain%ied + + ! 1. 差商计算 (dd[1,:] = q) + this%dd(1, :) = q(:) + do m = 2, this%spatial_order + do j = 1, cfd%domain%ntcells - m + 1 + this%dd(m, j) = this%dd(m-1, j+1) - this%dd(m-1, j) + end do + end do + + ! 2. 选择 smoothest stencil + do i = ist - 1, ied ! Python: range(ist-1, ied+1) → ied+1-1 = ied + this%lmc(i) = i + do m = 2, this%spatial_order + if ( abs(this%dd(m, this%lmc(i) - 1) ) < abs(this%dd(m, this%lmc(i)))) then + this%lmc(i) = this%lmc(i) - 1 + end if + end do + end do + + associate ( & + q_face_left => cfd%solution%q_face_left, & + q_face_right => cfd%solution%q_face_right & + ) + ! 这里可以直接使用 q_face_left 和 q_face_right + ! 3. 重构界面值 + do i = ist, ied + j = i - ist + 1 ! 1-based index for interfaces + k1 = this%lmc(i - 1) + k2 = this%lmc(i) + r1 = (i - 1) - k1 + 1 + r2 = i - k2 + 1 + q_face_left(j) = 0.0 + q_face_right(j) = 0.0 + do m = 1, this%spatial_order + q_face_left(j) = q_face_left(j) + q(k1 + m - 1) * this%coef(r1 + 1, m) + q_face_right(j) = q_face_right(j) + q(k2 + m - 1) * this%coef(r2, m) + end do + end do + end associate + + end subroutine eno_reconstruct + + ! WENO-3 nonlinear weights for left-biased stencil + function wc3L(v1, v2, v3) result(f) + real(dp), intent(in) :: v1, v2, v3 + real(dp) :: f, s0, s1, d0, d1, c0, c1, w0, w1, q0, q1 + + ! Smoothness indicators + s0 = (v3 - v2)**2 + s1 = (v2 - v1)**2 + + ! Nonlinear weights + d0 = 2.0_dp/3.0_dp + d1 = 1.0_dp/3.0_dp + + c0 = d0 / ((eps_weno + s0)**2) + c1 = d1 / ((eps_weno + s1)**2) + + w0 = c0 / (c0 + c1) + w1 = c1 / (c0 + c1) + + ! Candidate stencils + q0 = 0.5_dp * v2 + 0.5_dp * v3 + q1 = -0.5_dp * v1 + 1.5_dp * v2 + + ! Reconstructed value + f = w0 * q0 + w1 * q1 + end function wc3L + + ! WENO-3 nonlinear weights for right-biased stencil + function wc3R(v1, v2, v3) result(f) + real(dp), intent(in) :: v1, v2, v3 + real(dp) :: f, s0, s1, d0, d1, c0, c1, w0, w1, q0, q1 + + ! Smoothness indicators + s0 = (v2 - v1)**2 + s1 = (v3 - v2)**2 + + ! Nonlinear weights + d0 = 2.0_dp/3.0_dp + d1 = 1.0_dp/3.0_dp + + c0 = d0 / ((eps_weno + s0)**2) + c1 = d1 / ((eps_weno + s1)**2) + + w0 = c0 / (c0 + c1) + w1 = c1 / (c0 + c1) + + ! Candidate stencils + q0 = 0.5_dp * v1 + 0.5_dp * v2 + q1 = 1.5_dp * v2 - 0.5_dp * v3 + + ! Reconstructed value + f = w0 * q0 + w1 * q1 + end function wc3R + + ! WENO-3 reconstruction for left interface + subroutine weno3L_periodic(cfd, u, qL) + type(CfdType), intent(in) :: cfd + real(dp), intent(in) :: u(:) + real(dp), intent(out) :: qL(:) + + integer :: i, j, nghosts, ist, ied + + nghosts = cfd%domain%nghosts + ist = cfd%domain%ist + ied = cfd%domain%ied + + do i = ist-1, ied-1 + j = i - (ist - 1) + 1 + qL(j) = wc3L(u(i-1), u(i), u(i+1)) + end do + + end subroutine weno3L_periodic + + ! WENO-3 reconstruction for right interface + subroutine weno3R_periodic(cfd, u, qR) + type(CfdType), intent(in) :: cfd + real(dp), intent(in) :: u(:) + real(dp), intent(out) :: qR(:) + + integer :: i, j, nghosts, ist, ied + + nghosts = cfd%domain%nghosts + ist = cfd%domain%ist + ied = cfd%domain%ied + + do i = ist, ied + j = i - ist + 1 + qR(j) = wc3R(u(i-1), u(i), u(i+1)) + end do + + end subroutine weno3R_periodic + + ! WENO reconstruction + subroutine weno_reconstruct(this, q, cfd) + class(WenoReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + + call weno3L_periodic(cfd, q, cfd%solution%q_face_left) + call weno3R_periodic(cfd, q, cfd%solution%q_face_right) + end subroutine weno_reconstruct + + ! General reconstruction wrapper + subroutine reconstruction(q, cfd) + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + + select type(rec => cfd%reconstructor) + type is (EnoReconstructorType) + call rec%reconstruct(q, cfd) + type is (WenoReconstructorType) + call rec%reconstruct(q, cfd) + class default + error stop "Unknown reconstructor type" + end select + end subroutine reconstruction + + ! =================================================================== + ! Flux Functions + ! =================================================================== + + ! Rusanov flux + subroutine rusanov_flux(q_face_left, q_face_right, flux, cfd) + real(dp), intent(in) :: q_face_left(:), q_face_right(:) + real(dp), intent(out) :: flux(:) + type(CfdType), intent(in) :: cfd + + integer :: i + real(dp) :: u_L, u_R, F_L, F_R, c_L, c_R, Smax + + c_L = cfd%config%wave_speed + c_R = cfd%config%wave_speed + + do i = 1, cfd%domain%mesh%nnodes + u_L = q_face_left(i) + u_R = q_face_right(i) + F_L = c_L * u_L + F_R = c_R * u_R + Smax = max(abs(c_L), abs(c_R)) + flux(i) = 0.5_dp * (F_L + F_R) - 0.5_dp * Smax * (u_R - u_L) + end do + end subroutine rusanov_flux + + ! Engquist-Osher flux + subroutine engquist_osher_flux(q_face_left, q_face_right, flux, cfd) + real(dp), intent(in) :: q_face_left(:), q_face_right(:) + real(dp), intent(out) :: flux(:) + type(CfdType), intent(in) :: cfd + + integer :: i + real(dp) :: c, cp, cm, u_L, u_R + + c = cfd%config%wave_speed + + do i = 1, cfd%domain%mesh%nnodes + cp = 0.5_dp * (c + abs(c)) + cm = 0.5_dp * (c - abs(c)) + u_L = q_face_left(i) + u_R = q_face_right(i) + flux(i) = cp * u_L + cm * u_R + end do + end subroutine engquist_osher_flux + + ! Inviscid flux selection + subroutine inviscid_flux(q_face_left, q_face_right, flux, cfd) + real(dp), intent(in) :: q_face_left(:), q_face_right(:) + real(dp), intent(out) :: flux(:) + type(CfdType), intent(in) :: cfd + + if (cfd%config%flux_type == 0) then + call rusanov_flux(q_face_left, q_face_right, flux, cfd) + else + call engquist_osher_flux(q_face_left, q_face_right, flux, cfd) + end if + end subroutine inviscid_flux + + ! =================================================================== + ! Residual Computation + ! =================================================================== + + ! Compute residual (flux divergence) - CORRECTED VERSION + subroutine residual(q, cfd) + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + + integer :: i + + ! Apply boundary conditions first + call boundary(cfd%solution%u, cfd) + + ! Reconstruction + call reconstruction(q, cfd) + + ! Compute fluxes + call inviscid_flux(cfd%solution%q_face_left, cfd%solution%q_face_right, & + cfd%solution%flux, cfd) + + ! Compute residual - corrected indexing + do i = 1, cfd%domain%mesh%ncells + cfd%solution%res(i) = -(cfd%solution%flux(i+1) - cfd%solution%flux(i)) / & + cfd%domain%mesh%dx + end do + end subroutine residual + + ! =================================================================== + ! Time Integration + ! =================================================================== + + ! Update old field + subroutine update_oldfield(qn, q, n) + real(dp), intent(out) :: qn(:) + real(dp), intent(in) :: q(:) + integer, intent(in) :: n + + qn(1:n) = q(1:n) + end subroutine update_oldfield + + ! 1st-order Runge-Kutta (Euler) - SIMPLIFIED + subroutine runge_kutta_1(cfd) + type(CfdType), intent(inout) :: cfd + + integer :: i, j + real(dp) :: dt + + dt = cfd%config%dt + + ! Apply boundary conditions + call boundary(cfd%solution%u, cfd) + + ! Compute residual + call residual(cfd%solution%u, cfd) + + ! Update solution + do i = cfd%domain%ist, cfd%domain%ied - 1 + j = i - cfd%domain%ist + 1 + cfd%solution%u(i) = cfd%solution%u(i) + dt * cfd%solution%res(j) + end do + + ! Apply boundary conditions again + call boundary(cfd%solution%u, cfd) + + ! Save old solution + call update_oldfield(cfd%solution%un, cfd%solution%u, cfd%domain%ntcells) + end subroutine runge_kutta_1 + + ! 2nd-order Runge-Kutta (Heun) + subroutine runge_kutta_2(cfd) + type(CfdType), intent(inout) :: cfd + + integer :: i, j + real(dp) :: dt + + dt = cfd%config%dt + + ! Stage 1 + call boundary(cfd%solution%u, cfd) + call residual(cfd%solution%u, cfd) + + do i = cfd%domain%ist, cfd%domain%ied - 1 + j = i - cfd%domain%ist + 1 + cfd%solution%u(i) = cfd%solution%u(i) + dt * cfd%solution%res(j) + end do + call boundary(cfd%solution%u, cfd) + + ! Stage 2 + call residual(cfd%solution%u, cfd) + do i = cfd%domain%ist, cfd%domain%ied - 1 + j = i - cfd%domain%ist + 1 + cfd%solution%u(i) = 0.5_dp * cfd%solution%un(i) + & + 0.5_dp * cfd%solution%u(i) + & + 0.5_dp * dt * cfd%solution%res(j) + end do + call boundary(cfd%solution%u, cfd) + + call update_oldfield(cfd%solution%un, cfd%solution%u, cfd%domain%ntcells) + end subroutine runge_kutta_2 + + ! Runge-Kutta selection + subroutine runge_kutta(cfd) + type(CfdType), intent(inout) :: cfd + + select case(cfd%config%rk_order) + case(1) + call runge_kutta_1(cfd) + case(2) + call runge_kutta_2(cfd) + case default + call runge_kutta_1(cfd) + end select + end subroutine runge_kutta + + ! =================================================================== + ! Simulation Driver + ! =================================================================== + + ! Run simulation to final time + function run_simulation(cfd, final_time) result(u_result) + type(CfdType), intent(inout) :: cfd + real(dp), intent(in) :: final_time + real(dp), allocatable :: u_result(:) + + real(dp) :: t, dt, dt_old + integer :: step, max_steps + + allocate(u_result(cfd%domain%mesh%ncells)) + + t = 0.0_dp + dt_old = cfd%config%dt + dt = dt_old + max_steps = 10000 ! Safety limit + + print *, "Starting time integration..." + print *, " Final time: ", final_time + print *, " Time step: ", dt + print *, " CFL number: ", cfd%config%cfl + + step = 0 + do while (t < final_time - 1.0e-12_dp .and. step < max_steps) + step = step + 1 + + if (t + dt > final_time) then + dt = final_time - t + end if + + cfd%config%dt = dt + call runge_kutta(cfd) + t = t + dt + + ! Progress report + if (mod(step, 100) == 0) then + print *, " Step ", step, ", Time = ", t + end if + end do + + if (step >= max_steps) then + print *, "Warning: Reached maximum number of steps (", max_steps, ")" + end if + + cfd%config%dt = dt_old + + print *, "Time integration completed:" + print *, " Total steps: ", step + print *, " Final time: ", t + + ! Extract physical solution (without ghost cells) + u_result = cfd%solution%u(cfd%domain%ist:cfd%domain%ied) + end function run_simulation + + ! =================================================================== + ! Main Analysis Function + ! =================================================================== + + ! Perform ENO-WENO comparative analysis + subroutine performEnoWenoAnalysis() + type(CfdConfigType) :: config_eno3, config_weno3 + type(MeshType) :: mesh + type(ComputationalDomainType) :: domain_eno3, domain_weno3 + type(CfdType) :: cfd_eno3, cfd_weno3 + real(dp), allocatable :: u_eno(:), u_weno(:), u_analytical(:) + real(dp), allocatable :: xcc(:) + integer :: i, ncells, iunit + + ! Initialize mesh + call mesh%init() + ncells = mesh%ncells + allocate(xcc(ncells)) + xcc = mesh%xcc + + print *, "==========================================" + print *, "Mesh parameters:" + print *, " ncells = ", ncells + print *, " dx = ", mesh%dx + print *, " L = ", mesh%L + print *, "==========================================" + + ! Configure ENO3 - using simple 2nd order for stability + config_eno3%recon_scheme = "eno" + config_eno3%spatial_order = 2 ! Use 2nd order for stability + config_eno3%flux_type = 0 + config_eno3%rk_order = 1 + config_eno3%wave_speed = 1.0_dp + config_eno3%final_time = 0.625_dp + config_eno3%cfl = 0.5_dp + config_eno3%dt = 0.001_dp ! Small time step + + ! Configure WENO3 + config_weno3%recon_scheme = "weno" + config_weno3%spatial_order = 3 + config_weno3%flux_type = 0 + config_weno3%rk_order = 1 + config_weno3%wave_speed = 1.0_dp + config_weno3%final_time = 0.625_dp + config_weno3%cfl = 0.3_dp ! More strict CFL for WENO + config_weno3%dt = 0.0005_dp ! Even smaller time step + + ! Create domains + call domain_eno3%init(mesh, config_eno3) + call domain_weno3%init(mesh, config_weno3) + + ! Create CFD solvers + call cfd_eno3%init(config_eno3, domain_eno3) + call cfd_weno3%init(config_weno3, domain_weno3) + + ! Allocate arrays + allocate(u_eno(ncells), u_weno(ncells), u_analytical(ncells)) + + ! Run ENO simulation + print *, "==========================================" + print *, "Running ENO simulation..." + print *, " Scheme: ENO", config_eno3%spatial_order + print *, " Time step: ", config_eno3%dt + print *, "==========================================" + + call init_field(cfd_eno3) + u_eno = run_simulation(cfd_eno3, config_eno3%final_time) + + ! Run WENO simulation + print *, "==========================================" + print *, "Running WENO simulation..." + print *, " Scheme: WENO", config_weno3%spatial_order + print *, " Time step: ", config_weno3%dt + print *, "==========================================" + + call init_field(cfd_weno3) + u_weno = run_simulation(cfd_weno3, config_weno3%final_time) + + ! Compute analytical solution + print *, "Computing analytical solution..." + do i = 1, ncells + u_analytical(i) = analytical_solution(xcc(i), config_weno3%final_time, & + config_weno3%wave_speed, mesh%L) + end do + + ! Write results to files + print *, "Writing results to files..." + + ! Write ENO results + open(newunit=iunit, file='eno_results.txt', status='replace') + write(iunit, '(A)') '# x, u (ENO)' + do i = 1, ncells + write(iunit, '(2F12.6)') xcc(i), u_eno(i) + end do + close(iunit) + + ! Write WENO results + open(newunit=iunit, file='weno_results.txt', status='replace') + write(iunit, '(A)') '# x, u (WENO)' + do i = 1, ncells + write(iunit, '(2F12.6)') xcc(i), u_weno(i) + end do + close(iunit) + + ! Write analytical results + open(newunit=iunit, file='analytical_results.txt', status='replace') + write(iunit, '(A)') '# x, u (Analytical)' + do i = 1, ncells + write(iunit, '(2F12.6)') xcc(i), u_analytical(i) + end do + close(iunit) + + ! Print some statistics + print *, "==========================================" + print *, "Simulation statistics:" + print *, " ENO min/max: ", minval(u_eno), maxval(u_eno) + print *, " WENO min/max: ", minval(u_weno), maxval(u_weno) + print *, " Analytical min/max: ", minval(u_analytical), maxval(u_analytical) + print *, "==========================================" + + print *, "Simulation completed successfully!" + print *, "Results written to:" + print *, " eno_results.txt" + print *, " weno_results.txt" + print *, " analytical_results.txt" + print *, "" + print *, "To generate the comparison plot, run:" + print *, " python postprocess.py" + print *, "==========================================" + + deallocate(u_eno, u_weno, u_analytical, xcc) + end subroutine performEnoWenoAnalysis + +end module cfd_solver + +! =================================================================== +! Main Program +! =================================================================== +program main + use cfd_solver + implicit none + + print *, "==========================================" + print *, "OneFLOW-CFD Solver for 1D Convection" + print *, "ENO vs WENO Comparison" + print *, "==========================================" + + call performEnoWenoAnalysis() + + print *, "Program finished successfully!" + +end program main \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/cfd/01e/postprocess.py b/example/1d-linear-convection/weno3/fortran/cfd/01e/postprocess.py new file mode 100644 index 000000000..489212dd9 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/cfd/01e/postprocess.py @@ -0,0 +1,52 @@ +import numpy as np +import matplotlib.pyplot as plt + +def read_results(filename): + """Read results from Fortran output file""" + try: + data = np.loadtxt(filename, comments='#') + return data[:, 0], data[:, 1] + except Exception as e: + print(f"Error reading {filename}: {e}") + return np.array([]), np.array([]) + +def main(): + print("Reading Fortran output files...") + + # Read all data files + x_eno, u_eno = read_results('eno_results.txt') + x_weno, u_weno = read_results('weno_results.txt') + x_analytical, u_analytical = read_results('analytical_results.txt') + + # Check if we have data + if len(x_eno) == 0 or len(x_weno) == 0 or len(x_analytical) == 0: + print("Error: Could not read all data files.") + print("Make sure to run the Fortran program first.") + return + + # Create plot + plt.figure(figsize=(10, 6)) + + # Plot results + plt.plot(x_eno, u_eno, 'bo-', linewidth=1, markersize=3, + markerfacecolor='none', label='ENO3') + plt.plot(x_weno, u_weno, 'gs-', linewidth=1, markersize=3, + markerfacecolor='none', label='WENO3') + plt.plot(x_analytical, u_analytical, 'r-', linewidth=2, label='Analytical') + + # Customize plot + plt.title('1D Convection: ENO3 vs WENO3 (t=0.625)') + plt.xlabel('x') + plt.ylabel('u') + plt.legend() + plt.grid(True, alpha=0.3) + + # Save and show + plt.tight_layout() + plt.savefig('comparison.png', dpi=150) + plt.show() + + print("Plot saved as comparison.png") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/cfd/01f/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/cfd/01f/CMakeLists.txt new file mode 100644 index 000000000..653a6751e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/cfd/01f/CMakeLists.txt @@ -0,0 +1,22 @@ +# CMakeLists.txt +cmake_minimum_required(VERSION 4.2.1) # 更高版本更好支持Fortran + +project(OneFLOW_CFD LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +# 按依赖顺序显式列出源文件(解决 Intel + VS 编译顺序问题) +set(SOURCES + cfd_solver.f90 +) + +add_executable(oneflow_cfd ${SOURCES}) + +# 包含模块目录(解决 .mod 未找到) +target_include_directories(oneflow_cfd PRIVATE ${CMAKE_Fortran_MODULE_DIRECTORY}) diff --git a/example/1d-linear-convection/weno3/fortran/cfd/01f/README.md b/example/1d-linear-convection/weno3/fortran/cfd/01f/README.md new file mode 100644 index 000000000..95888ac2e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/cfd/01f/README.md @@ -0,0 +1,26 @@ +# OneFLOW-CFD: 1D Convection Solver + +A Fortran implementation of 1D linear convection equation solver with ENO/WENO reconstruction schemes comparison. + +## Features + +- **Numerical Schemes**: ENO3 and WENO3 reconstruction +- **Time Integration**: Runge-Kutta methods (RK1, RK2) +- **Flux Schemes**: Rusanov and Engquist-Osher fluxes +- **Boundary Conditions**: Periodic boundary conditions +- **Visualization**: Python-based plotting and analysis +- **Automation**: Complete build-run-plot pipeline + +## Quick Start + +### Prerequisites +- Fortran compiler (Intel ifx/ifort, GNU gfortran, or LLVM flang) +- CMake 3.10+ +- Python 3.8+ with NumPy and Matplotlib + +### Installation + +1. Clone or download the project: + ```bash + git clone + cd OneFLOW-CFD-Fortran \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/cfd/01f/cfd_solver.f90 b/example/1d-linear-convection/weno3/fortran/cfd/01f/cfd_solver.f90 new file mode 100644 index 000000000..34cf0c42c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/cfd/01f/cfd_solver.f90 @@ -0,0 +1,943 @@ +! OneFLOW-CFD Solver for 1D Convection Equation +! ENO/WENO Reconstruction Comparison - Single File Implementation + +module cfd_solver + use, intrinsic :: iso_fortran_env, only: dp => real64 + implicit none + + private + + ! =================================================================== + ! Forward Type Declarations + ! =================================================================== + type, public :: CfdConfigType + character(len=10) :: recon_scheme = "eno" + integer :: flux_type = 0 + integer :: rk_order = 1 + integer :: spatial_order = 3 + real(dp) :: wave_speed = 1.0_dp + real(dp) :: final_time = 0.625_dp + real(dp) :: dt = 0.025_dp ! 减小时间步长 + real(dp) :: cfl = 0.5_dp ! CFL数 + end type CfdConfigType + + type, public :: MeshType + real(dp) :: xmin = 0.0_dp + real(dp) :: xmax = 2.0_dp + integer :: ncells = 40 + integer :: nnodes = 0 + integer :: nx = 0 + real(dp) :: L = 0.0_dp + real(dp) :: dx = 0.0_dp + real(dp), allocatable :: x(:), xcc(:) + contains + procedure :: init => mesh_init + end type MeshType + + type, public :: ComputationalDomainType + type(MeshType) :: mesh + type(CfdConfigType) :: config + integer :: nghosts = 0 + integer :: ist = 0 + integer :: ied = 0 + integer :: ntcells = 0 + contains + procedure :: init => domain_init + end type ComputationalDomainType + + type, public :: SolutionType + type(ComputationalDomainType) :: domain + real(dp), allocatable :: q_face_left(:), q_face_right(:) + real(dp), allocatable :: flux(:), res(:) + real(dp), allocatable :: u(:), un(:) + contains + procedure :: init => solution_init + end type SolutionType + + ! Main CFD solver class + type, public :: CfdType + type(CfdConfigType) :: config + type(ComputationalDomainType) :: domain + type(SolutionType) :: solution + class(*), allocatable :: reconstructor + contains + procedure :: init => cfd_init + end type CfdType + + ! Abstract reconstructor base class + type, abstract, public :: ReconstructorType + contains + procedure(reconstruct_interface), deferred, pass :: reconstruct + end type ReconstructorType + + ! Define abstract interface + abstract interface + subroutine reconstruct_interface(this, q, cfd) + import :: ReconstructorType, CfdType, dp + class(ReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + end subroutine reconstruct_interface + end interface + + ! ENO reconstructor + type, extends(ReconstructorType), public :: EnoReconstructorType + integer :: spatial_order + integer :: ntcells + integer, allocatable :: lmc(:) + real(dp), allocatable :: coef(:,:) + real(dp), allocatable :: dd(:,:) + contains + procedure :: reconstruct => eno_reconstruct + procedure :: init => eno_init + end type EnoReconstructorType + + ! WENO reconstructor + type, extends(ReconstructorType), public :: WenoReconstructorType + contains + procedure :: reconstruct => weno_reconstruct + end type WenoReconstructorType + + ! =================================================================== + ! Public Procedures + ! =================================================================== + public :: run_simulation, init_field, analytical_solution, performEnoWenoAnalysis + + ! =================================================================== + ! Module Variables + ! =================================================================== + real(dp), parameter :: eps_weno = 1.0e-6_dp + +contains + + ! =================================================================== + ! Initialization Methods + ! =================================================================== + + ! Mesh initialization + subroutine mesh_init(this) + class(MeshType), intent(inout) :: this + integer :: i + + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, dp) + + allocate(this%x(this%nnodes), this%xcc(this%ncells)) + + ! Node coordinates + do i = 1, this%nnodes + this%x(i) = this%xmin + (i-1) * this%dx + end do + + ! Cell center coordinates + do i = 1, this%ncells + this%xcc(i) = 0.5_dp * (this%x(i) + this%x(i+1)) + end do + end subroutine mesh_init + + ! Domain initialization + subroutine domain_init(this, mesh, config) + class(ComputationalDomainType), intent(inout) :: this + type(MeshType), intent(in) :: mesh + type(CfdConfigType), intent(in) :: config + + this%mesh = mesh + this%config = config + + ! Calculate ghost cells + if (trim(config%recon_scheme) == "eno") then + this%nghosts = config%spatial_order + else if (trim(config%recon_scheme) == "weno") then + this%nghosts = config%spatial_order / 2 + 1 + else + error stop "Unknown reconstruction scheme" + end if + + this%ist = this%nghosts + 1 + this%ied = this%ist + mesh%ncells - 1 + this%ntcells = mesh%ncells + 2 * this%nghosts + + print *, "Domain initialized:" + print *, " mesh.ncells = ", mesh%ncells + print *, " spatial_order = ", config%spatial_order + print *, " nghosts = ", this%nghosts + print *, " ist = ", this%ist, ", ied = ", this%ied + print *, " dx = ", mesh%dx + end subroutine domain_init + + ! Solution initialization + subroutine solution_init(this, domain) + class(SolutionType), intent(inout) :: this + type(ComputationalDomainType), intent(in) :: domain + + this%domain = domain + + allocate(this%q_face_left(domain%mesh%nnodes)) + allocate(this%q_face_right(domain%mesh%nnodes)) + allocate(this%flux(domain%mesh%nnodes)) + allocate(this%res(domain%mesh%ncells)) + allocate(this%u(domain%ntcells)) + allocate(this%un(domain%ntcells)) + + this%q_face_left = 0.0_dp + this%q_face_right = 0.0_dp + this%flux = 0.0_dp + this%res = 0.0_dp + this%u = 0.0_dp + this%un = 0.0_dp + end subroutine solution_init + + ! ENO reconstructor initialization + subroutine eno_init(this, spatial_order, ntcells) + class(EnoReconstructorType), intent(inout) :: this + integer, intent(in) :: spatial_order + integer, intent(in) :: ntcells + + this%spatial_order = spatial_order + this%ntcells = ntcells + + allocate(this%lmc(ntcells)) + allocate(this%coef(spatial_order+1, spatial_order)) + allocate(this%dd(spatial_order, ntcells)) + + this%lmc = 0 + this%coef = 0.0_dp + this%dd = 0.0_dp + + ! Initialize coefficients + call init_coef(spatial_order, this%coef) + end subroutine eno_init + + ! CFD solver initialization + subroutine cfd_init(this, config, domain) + class(CfdType), intent(inout) :: this + type(CfdConfigType), intent(in) :: config + type(ComputationalDomainType), intent(in) :: domain + + print *, "CFD solver initialization:" + + this%config = config + this%domain = domain + call this%solution%init(domain) + + ! Create reconstructor based on scheme + if (trim(config%recon_scheme) == "eno") then + allocate(EnoReconstructorType :: this%reconstructor) + select type(rec => this%reconstructor) + type is (EnoReconstructorType) + call rec%init(config%spatial_order, domain%ntcells) + end select + else if (trim(config%recon_scheme) == "weno") then + allocate(WenoReconstructorType :: this%reconstructor) + else + error stop "Unknown reconstruction scheme" + end if + + ! Adjust time step based on CFL condition + call calculate_dt(this) + end subroutine cfd_init + + ! Calculate time step based on CFL condition + subroutine calculate_dt(cfd) + type(CfdType), intent(inout) :: cfd + + real(dp) :: dt_cfl + + ! CFL condition: dt <= CFL * dx / |wave_speed| + dt_cfl = cfd%config%cfl * cfd%domain%mesh%dx / abs(cfd%config%wave_speed) + + print *, " cfd%config%dt = ", cfd%config%dt + print *, " dt_cfl = ", dt_cfl + + if (cfd%config%dt > dt_cfl) then + print *, "Adjusting time step for stability:" + print *, " Original dt = ", cfd%config%dt + print *, " CFL dt = ", dt_cfl + cfd%config%dt = dt_cfl + print *, " Using dt = ", cfd%config%dt + end if + + print *, " calculate_dt cfd%config%dt = ", cfd%config%dt + end subroutine calculate_dt + + ! =================================================================== + ! Initial Conditions and Analytical Solution + ! =================================================================== + + ! Initial condition: step function + function initial_condition(x) result(u0) + real(dp), intent(in) :: x + real(dp) :: u0 + + if (0.5_dp <= x .and. x <= 1.0_dp) then + u0 = 2.0_dp + else + u0 = 1.0_dp + end if + end function initial_condition + + ! Analytical solution with periodic BC + function analytical_solution(x, t, a, L) result(u) + real(dp), intent(in) :: x, t, a, L + real(dp) :: u, x_shifted + + x_shifted = mod(x - a * t + L, L) + u = initial_condition(x_shifted) + end function analytical_solution + + ! Initialize field with step function + subroutine init_field(cfd) + type(CfdType), intent(inout) :: cfd + integer :: i, j + + do i = 1, cfd%domain%mesh%ncells + if (0.5_dp <= cfd%domain%mesh%xcc(i) .and. cfd%domain%mesh%xcc(i) <= 1.0_dp) then + cfd%solution%u(cfd%domain%ist + i - 1) = 2.0_dp + else + cfd%solution%u(cfd%domain%ist + i - 1) = 1.0_dp + end if + end do + + call boundary(cfd%solution%u, cfd) + call update_oldfield(cfd%solution%un, cfd%solution%u, cfd%domain%ntcells) + end subroutine init_field + + ! =================================================================== + ! Boundary Conditions + ! =================================================================== + + ! Periodic boundary conditions - CORRECTED VERSION + subroutine periodic_boundary(u, cfd) + real(dp), intent(inout) :: u(:) + type(CfdType), intent(in) :: cfd + integer :: i + + ! Copy interior cells to ghost cells + ! Left ghost cells = right interior cells + do i = 1, cfd%domain%nghosts + u(cfd%domain%ist - i) = u(cfd%domain%ied - cfd%domain%nghosts + i - 1) + end do + + ! Right ghost cells = left interior cells + do i = 1, cfd%domain%nghosts + u(cfd%domain%ied + i) = u(cfd%domain%ist + i - 1) + end do + end subroutine periodic_boundary + + ! Boundary condition wrapper + subroutine boundary(u, cfd) + real(dp), intent(inout) :: u(:) + type(CfdType), intent(in) :: cfd + + call periodic_boundary(u, cfd) + end subroutine boundary + + ! =================================================================== + ! Reconstruction Methods + ! =================================================================== + + ! Initialize ENO/WENO coefficients + subroutine init_coef(spatial_order, coef) + integer, intent(in) :: spatial_order + real(dp), intent(out) :: coef(:,:) + + coef = 0.0_dp + + select case(spatial_order) + case(1) + coef(1,1) = 1.0_dp + coef(2,1) = 1.0_dp + + case(2) + coef(1,1:2) = [ 3.0_dp/2.0_dp, -1.0_dp/2.0_dp ] + coef(2,1:2) = [ 1.0_dp/2.0_dp, 1.0_dp/2.0_dp ] + coef(3,1:2) = [ -1.0_dp/2.0_dp, 3.0_dp/2.0_dp ] + + case(3) + coef(1,1:3) = [ 11.0_dp/6.0_dp, -7.0_dp/6.0_dp, 1.0_dp/3.0_dp ] + coef(2,1:3) = [ 1.0_dp/3.0_dp, 5.0_dp/6.0_dp, -1.0_dp/6.0_dp ] + coef(3,1:3) = [ -1.0_dp/6.0_dp, 5.0_dp/6.0_dp, 1.0_dp/3.0_dp ] + coef(4,1:3) = [ 1.0_dp/3.0_dp, -7.0_dp/6.0_dp, 11.0_dp/6.0_dp ] + + case default + error stop "Unsupported spatial order" + end select + end subroutine init_coef + + subroutine first_order_reconstruct(this, q, cfd) + class(EnoReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + + integer :: i, j + integer :: nghosts, ist, ied + + nghosts = cfd%domain%nghosts + ist = cfd%domain%ist + ied = cfd%domain%ied + + ! For now, use simple 2nd order reconstruction + do i = ist, ied + j = i - ist + 1 ! 1-based index for interfaces + + cfd%solution%q_face_left(j) = q(i-1) + cfd%solution%q_face_right(j) = q(i) + end do + end subroutine first_order_reconstruct + + subroutine eno_reconstruct(this, q, cfd) + class(EnoReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + type(ComputationalDomainType), pointer :: domain + + integer :: i, j, m, k1, k2, r1, r2 + integer :: nghosts, ist, ied + + nghosts = cfd%domain%nghosts + ist = cfd%domain%ist + ied = cfd%domain%ied + + ! 1. 差商计算 (dd[1,:] = q) + this%dd(1, :) = q(:) + do m = 2, this%spatial_order + do j = 1, cfd%domain%ntcells - m + 1 + this%dd(m, j) = this%dd(m-1, j+1) - this%dd(m-1, j) + end do + end do + + ! 2. 选择 smoothest stencil + do i = ist - 1, ied ! Python: range(ist-1, ied+1) → ied+1-1 = ied + this%lmc(i) = i + do m = 2, this%spatial_order + if ( abs(this%dd(m, this%lmc(i) - 1) ) < abs(this%dd(m, this%lmc(i)))) then + this%lmc(i) = this%lmc(i) - 1 + end if + end do + end do + + associate ( & + q_face_left => cfd%solution%q_face_left, & + q_face_right => cfd%solution%q_face_right & + ) + ! 这里可以直接使用 q_face_left 和 q_face_right + ! 3. 重构界面值 + do i = ist, ied + j = i - ist + 1 ! 1-based index for interfaces + k1 = this%lmc(i - 1) + k2 = this%lmc(i) + r1 = (i - 1) - k1 + 1 + r2 = i - k2 + 1 + q_face_left(j) = 0.0 + q_face_right(j) = 0.0 + do m = 1, this%spatial_order + q_face_left(j) = q_face_left(j) + q(k1 + m - 1) * this%coef(r1 + 1, m) + q_face_right(j) = q_face_right(j) + q(k2 + m - 1) * this%coef(r2, m) + end do + end do + end associate + + end subroutine eno_reconstruct + + ! WENO-3 nonlinear weights for left-biased stencil + function wc3L(v1, v2, v3) result(f) + real(dp), intent(in) :: v1, v2, v3 + real(dp) :: f, s0, s1, d0, d1, c0, c1, w0, w1, q0, q1 + + ! Smoothness indicators + s0 = (v3 - v2)**2 + s1 = (v2 - v1)**2 + + ! Nonlinear weights + d0 = 2.0_dp/3.0_dp + d1 = 1.0_dp/3.0_dp + + c0 = d0 / ((eps_weno + s0)**2) + c1 = d1 / ((eps_weno + s1)**2) + + w0 = c0 / (c0 + c1) + w1 = c1 / (c0 + c1) + + ! Candidate stencils + q0 = 0.5_dp * v2 + 0.5_dp * v3 + q1 = -0.5_dp * v1 + 1.5_dp * v2 + + ! Reconstructed value + f = w0 * q0 + w1 * q1 + end function wc3L + + ! WENO-3 nonlinear weights for right-biased stencil + function wc3R(v1, v2, v3) result(f) + real(dp), intent(in) :: v1, v2, v3 + real(dp) :: f, s0, s1, d0, d1, c0, c1, w0, w1, q0, q1 + + ! Smoothness indicators + s0 = (v2 - v1)**2 + s1 = (v3 - v2)**2 + + ! Nonlinear weights + d0 = 2.0_dp/3.0_dp + d1 = 1.0_dp/3.0_dp + + c0 = d0 / ((eps_weno + s0)**2) + c1 = d1 / ((eps_weno + s1)**2) + + w0 = c0 / (c0 + c1) + w1 = c1 / (c0 + c1) + + ! Candidate stencils + q0 = 0.5_dp * v1 + 0.5_dp * v2 + q1 = 1.5_dp * v2 - 0.5_dp * v3 + + ! Reconstructed value + f = w0 * q0 + w1 * q1 + end function wc3R + + ! WENO-3 reconstruction for left interface + subroutine weno3L_periodic(cfd, u, qL) + type(CfdType), intent(in) :: cfd + real(dp), intent(in) :: u(:) + real(dp), intent(out) :: qL(:) + + integer :: i, j, nghosts, ist, ied + + nghosts = cfd%domain%nghosts + ist = cfd%domain%ist + ied = cfd%domain%ied + + do i = ist-1, ied-1 + j = i - (ist - 1) + 1 + qL(j) = wc3L(u(i-1), u(i), u(i+1)) + end do + + end subroutine weno3L_periodic + + ! WENO-3 reconstruction for right interface + subroutine weno3R_periodic(cfd, u, qR) + type(CfdType), intent(in) :: cfd + real(dp), intent(in) :: u(:) + real(dp), intent(out) :: qR(:) + + integer :: i, j, nghosts, ist, ied + + nghosts = cfd%domain%nghosts + ist = cfd%domain%ist + ied = cfd%domain%ied + + do i = ist, ied + j = i - ist + 1 + qR(j) = wc3R(u(i-1), u(i), u(i+1)) + end do + + end subroutine weno3R_periodic + + ! WENO reconstruction + subroutine weno_reconstruct(this, q, cfd) + class(WenoReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + + call weno3L_periodic(cfd, q, cfd%solution%q_face_left) + call weno3R_periodic(cfd, q, cfd%solution%q_face_right) + end subroutine weno_reconstruct + + ! General reconstruction wrapper + subroutine reconstruction(q, cfd) + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + + select type(rec => cfd%reconstructor) + type is (EnoReconstructorType) + call rec%reconstruct(q, cfd) + type is (WenoReconstructorType) + call rec%reconstruct(q, cfd) + class default + error stop "Unknown reconstructor type" + end select + end subroutine reconstruction + + ! =================================================================== + ! Flux Functions + ! =================================================================== + + ! Rusanov flux + subroutine rusanov_flux(q_face_left, q_face_right, flux, cfd) + real(dp), intent(in) :: q_face_left(:), q_face_right(:) + real(dp), intent(out) :: flux(:) + type(CfdType), intent(in) :: cfd + + integer :: i + real(dp) :: u_L, u_R, F_L, F_R, c_L, c_R, Smax + + c_L = cfd%config%wave_speed + c_R = cfd%config%wave_speed + + do i = 1, cfd%domain%mesh%nnodes + u_L = q_face_left(i) + u_R = q_face_right(i) + F_L = c_L * u_L + F_R = c_R * u_R + Smax = max(abs(c_L), abs(c_R)) + flux(i) = 0.5_dp * (F_L + F_R) - 0.5_dp * Smax * (u_R - u_L) + end do + end subroutine rusanov_flux + + ! Engquist-Osher flux + subroutine engquist_osher_flux(q_face_left, q_face_right, flux, cfd) + real(dp), intent(in) :: q_face_left(:), q_face_right(:) + real(dp), intent(out) :: flux(:) + type(CfdType), intent(in) :: cfd + + integer :: i + real(dp) :: c, cp, cm, u_L, u_R + + c = cfd%config%wave_speed + + do i = 1, cfd%domain%mesh%nnodes + cp = 0.5_dp * (c + abs(c)) + cm = 0.5_dp * (c - abs(c)) + u_L = q_face_left(i) + u_R = q_face_right(i) + flux(i) = cp * u_L + cm * u_R + end do + end subroutine engquist_osher_flux + + ! Inviscid flux selection + subroutine inviscid_flux(q_face_left, q_face_right, flux, cfd) + real(dp), intent(in) :: q_face_left(:), q_face_right(:) + real(dp), intent(out) :: flux(:) + type(CfdType), intent(in) :: cfd + + if (cfd%config%flux_type == 0) then + call rusanov_flux(q_face_left, q_face_right, flux, cfd) + else + call engquist_osher_flux(q_face_left, q_face_right, flux, cfd) + end if + end subroutine inviscid_flux + + ! =================================================================== + ! Residual Computation + ! =================================================================== + + ! Compute residual (flux divergence) - CORRECTED VERSION + subroutine residual(q, cfd) + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + + integer :: i + + ! Apply boundary conditions first + call boundary(cfd%solution%u, cfd) + + ! Reconstruction + call reconstruction(q, cfd) + + ! Compute fluxes + call inviscid_flux(cfd%solution%q_face_left, cfd%solution%q_face_right, & + cfd%solution%flux, cfd) + + ! Compute residual - corrected indexing + do i = 1, cfd%domain%mesh%ncells + cfd%solution%res(i) = -(cfd%solution%flux(i+1) - cfd%solution%flux(i)) / & + cfd%domain%mesh%dx + end do + end subroutine residual + + ! =================================================================== + ! Time Integration + ! =================================================================== + + ! Update old field + subroutine update_oldfield(qn, q, n) + real(dp), intent(out) :: qn(:) + real(dp), intent(in) :: q(:) + integer, intent(in) :: n + + qn(1:n) = q(1:n) + end subroutine update_oldfield + + ! 1st-order Runge-Kutta (Euler) - SIMPLIFIED + subroutine runge_kutta_1(cfd) + type(CfdType), intent(inout) :: cfd + + integer :: i, j + real(dp) :: dt + + dt = cfd%config%dt + + ! Apply boundary conditions + call boundary(cfd%solution%u, cfd) + + ! Compute residual + call residual(cfd%solution%u, cfd) + + ! Update solution + do i = cfd%domain%ist, cfd%domain%ied - 1 + j = i - cfd%domain%ist + 1 + cfd%solution%u(i) = cfd%solution%u(i) + dt * cfd%solution%res(j) + end do + + ! Apply boundary conditions again + call boundary(cfd%solution%u, cfd) + + ! Save old solution + call update_oldfield(cfd%solution%un, cfd%solution%u, cfd%domain%ntcells) + end subroutine runge_kutta_1 + + ! 2nd-order Runge-Kutta (Heun) + subroutine runge_kutta_2(cfd) + type(CfdType), intent(inout) :: cfd + + integer :: i, j + real(dp) :: dt + + dt = cfd%config%dt + + ! Stage 1 + call boundary(cfd%solution%u, cfd) + call residual(cfd%solution%u, cfd) + + do i = cfd%domain%ist, cfd%domain%ied - 1 + j = i - cfd%domain%ist + 1 + cfd%solution%u(i) = cfd%solution%u(i) + dt * cfd%solution%res(j) + end do + call boundary(cfd%solution%u, cfd) + + ! Stage 2 + call residual(cfd%solution%u, cfd) + do i = cfd%domain%ist, cfd%domain%ied - 1 + j = i - cfd%domain%ist + 1 + cfd%solution%u(i) = 0.5_dp * cfd%solution%un(i) + & + 0.5_dp * cfd%solution%u(i) + & + 0.5_dp * dt * cfd%solution%res(j) + end do + call boundary(cfd%solution%u, cfd) + + call update_oldfield(cfd%solution%un, cfd%solution%u, cfd%domain%ntcells) + end subroutine runge_kutta_2 + + ! Runge-Kutta selection + subroutine runge_kutta(cfd) + type(CfdType), intent(inout) :: cfd + + select case(cfd%config%rk_order) + case(1) + call runge_kutta_1(cfd) + case(2) + call runge_kutta_2(cfd) + case default + call runge_kutta_1(cfd) + end select + end subroutine runge_kutta + + ! =================================================================== + ! Simulation Driver + ! =================================================================== + + ! Run simulation to final time + function run_simulation(cfd, final_time) result(u_result) + type(CfdType), intent(inout) :: cfd + real(dp), intent(in) :: final_time + real(dp), allocatable :: u_result(:) + + real(dp) :: t, dt, dt_old + integer :: step, max_steps + + allocate(u_result(cfd%domain%mesh%ncells)) + + t = 0.0_dp + dt_old = cfd%config%dt + dt = dt_old + max_steps = 10000 ! Safety limit + + print *, "Starting time integration..." + print *, " Final time: ", final_time + print *, " Time step: ", dt + print *, " CFL number: ", cfd%config%cfl + + step = 0 + do while (t < final_time - 1.0e-12_dp .and. step < max_steps) + step = step + 1 + + if (t + dt > final_time) then + dt = final_time - t + end if + + cfd%config%dt = dt + call runge_kutta(cfd) + t = t + dt + + ! Progress report + if (mod(step, 100) == 0) then + print *, " Step ", step, ", Time = ", t + end if + end do + + if (step >= max_steps) then + print *, "Warning: Reached maximum number of steps (", max_steps, ")" + end if + + cfd%config%dt = dt_old + + print *, "Time integration completed:" + print *, " Total steps: ", step + print *, " Final time: ", t + + ! Extract physical solution (without ghost cells) + u_result = cfd%solution%u(cfd%domain%ist:cfd%domain%ied) + end function run_simulation + + ! =================================================================== + ! Main Analysis Function + ! =================================================================== + + ! Perform ENO-WENO comparative analysis + subroutine performEnoWenoAnalysis() + type(CfdConfigType) :: config_eno3, config_weno3 + type(MeshType) :: mesh + type(ComputationalDomainType) :: domain_eno3, domain_weno3 + type(CfdType) :: cfd_eno3, cfd_weno3 + real(dp), allocatable :: u_eno(:), u_weno(:), u_analytical(:) + real(dp), allocatable :: xcc(:) + integer :: i, ncells, iunit + + ! Initialize mesh + call mesh%init() + ncells = mesh%ncells + allocate(xcc(ncells)) + xcc = mesh%xcc + + print *, "==========================================" + print *, "Mesh parameters:" + print *, " ncells = ", ncells + print *, " dx = ", mesh%dx + print *, " L = ", mesh%L + print *, "==========================================" + + ! Configure ENO3 - using simple 2nd order for stability + config_eno3%recon_scheme = "eno" + config_eno3%spatial_order = 3 + config_eno3%flux_type = 0 + config_eno3%rk_order = 1 + config_eno3%wave_speed = 1.0_dp + config_eno3%final_time = 0.625_dp + config_eno3%cfl = 1.0_dp + config_eno3%dt = 0.0025_dp + + ! Configure WENO3 + config_weno3%recon_scheme = "weno" + config_weno3%spatial_order = 3 + config_weno3%flux_type = 0 + config_weno3%rk_order = 1 + config_weno3%wave_speed = 1.0_dp + config_weno3%final_time = 0.625_dp + config_weno3%cfl = 1.0_dp + config_weno3%dt = 0.0025_dp + + ! Create domains + call domain_eno3%init(mesh, config_eno3) + call domain_weno3%init(mesh, config_weno3) + + ! Create CFD solvers + call cfd_eno3%init(config_eno3, domain_eno3) + call cfd_weno3%init(config_weno3, domain_weno3) + + ! Allocate arrays + allocate(u_eno(ncells), u_weno(ncells), u_analytical(ncells)) + + ! Run ENO simulation + print *, "==========================================" + print *, "Running ENO simulation..." + print *, " Scheme: ENO", config_eno3%spatial_order + print *, " Time step: ", config_eno3%dt + print *, "==========================================" + + call init_field(cfd_eno3) + u_eno = run_simulation(cfd_eno3, config_eno3%final_time) + + ! Run WENO simulation + print *, "==========================================" + print *, "Running WENO simulation..." + print *, " Scheme: WENO", config_weno3%spatial_order + print *, " Time step: ", config_weno3%dt + print *, "==========================================" + + call init_field(cfd_weno3) + u_weno = run_simulation(cfd_weno3, config_weno3%final_time) + + ! Compute analytical solution + print *, "Computing analytical solution..." + do i = 1, ncells + u_analytical(i) = analytical_solution(xcc(i), config_weno3%final_time, & + config_weno3%wave_speed, mesh%L) + end do + + ! Write results to files + print *, "Writing results to files..." + + ! Write ENO results + open(newunit=iunit, file='eno_results.txt', status='replace') + write(iunit, '(A)') '# x, u (ENO)' + do i = 1, ncells + write(iunit, '(2F12.6)') xcc(i), u_eno(i) + end do + close(iunit) + + ! Write WENO results + open(newunit=iunit, file='weno_results.txt', status='replace') + write(iunit, '(A)') '# x, u (WENO)' + do i = 1, ncells + write(iunit, '(2F12.6)') xcc(i), u_weno(i) + end do + close(iunit) + + ! Write analytical results + open(newunit=iunit, file='analytical_results.txt', status='replace') + write(iunit, '(A)') '# x, u (Analytical)' + do i = 1, ncells + write(iunit, '(2F12.6)') xcc(i), u_analytical(i) + end do + close(iunit) + + ! Print some statistics + print *, "==========================================" + print *, "Simulation statistics:" + print *, " ENO min/max: ", minval(u_eno), maxval(u_eno) + print *, " WENO min/max: ", minval(u_weno), maxval(u_weno) + print *, " Analytical min/max: ", minval(u_analytical), maxval(u_analytical) + print *, "==========================================" + + print *, "Simulation completed successfully!" + print *, "Results written to:" + print *, " eno_results.txt" + print *, " weno_results.txt" + print *, " analytical_results.txt" + print *, "" + print *, "To generate the comparison plot, run:" + print *, " python postprocess.py" + print *, "==========================================" + + deallocate(u_eno, u_weno, u_analytical, xcc) + end subroutine performEnoWenoAnalysis + +end module cfd_solver + +! =================================================================== +! Main Program +! =================================================================== +program main + use cfd_solver + implicit none + + print *, "==========================================" + print *, "OneFLOW-CFD Solver for 1D Convection" + print *, "ENO vs WENO Comparison" + print *, "==========================================" + + call performEnoWenoAnalysis() + + print *, "Program finished successfully!" + +end program main \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/cfd/01f/postprocess.py b/example/1d-linear-convection/weno3/fortran/cfd/01f/postprocess.py new file mode 100644 index 000000000..489212dd9 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/cfd/01f/postprocess.py @@ -0,0 +1,52 @@ +import numpy as np +import matplotlib.pyplot as plt + +def read_results(filename): + """Read results from Fortran output file""" + try: + data = np.loadtxt(filename, comments='#') + return data[:, 0], data[:, 1] + except Exception as e: + print(f"Error reading {filename}: {e}") + return np.array([]), np.array([]) + +def main(): + print("Reading Fortran output files...") + + # Read all data files + x_eno, u_eno = read_results('eno_results.txt') + x_weno, u_weno = read_results('weno_results.txt') + x_analytical, u_analytical = read_results('analytical_results.txt') + + # Check if we have data + if len(x_eno) == 0 or len(x_weno) == 0 or len(x_analytical) == 0: + print("Error: Could not read all data files.") + print("Make sure to run the Fortran program first.") + return + + # Create plot + plt.figure(figsize=(10, 6)) + + # Plot results + plt.plot(x_eno, u_eno, 'bo-', linewidth=1, markersize=3, + markerfacecolor='none', label='ENO3') + plt.plot(x_weno, u_weno, 'gs-', linewidth=1, markersize=3, + markerfacecolor='none', label='WENO3') + plt.plot(x_analytical, u_analytical, 'r-', linewidth=2, label='Analytical') + + # Customize plot + plt.title('1D Convection: ENO3 vs WENO3 (t=0.625)') + plt.xlabel('x') + plt.ylabel('u') + plt.legend() + plt.grid(True, alpha=0.3) + + # Save and show + plt.tight_layout() + plt.savefig('comparison.png', dpi=150) + plt.show() + + print("Plot saved as comparison.png") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/cfd/01f/run_all.py b/example/1d-linear-convection/weno3/fortran/cfd/01f/run_all.py new file mode 100644 index 000000000..44bf7c893 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/cfd/01f/run_all.py @@ -0,0 +1,482 @@ +#!/usr/bin/env python3 +""" +OneFLOW-CFD Complete Automation System +Author: Your Name +Date: 2024 +Description: Automated build, run, and visualization pipeline +""" + +import os +import sys +import subprocess +import shutil +import time +from pathlib import Path +import argparse + +class CFDAutomation: + """自动化CFD求解器的完整流程""" + + def __init__(self, build_type="Release", verbose=False): + self.project_root = Path.cwd() + self.build_dir = self.project_root / "build" + self.bin_dir = None + self.executable = None + self.build_type = build_type + self.verbose = verbose + self.results_dir = self.project_root / "results" + # 初始化combined_env,避免属性不存在报错 + self.combined_env = os.environ.copy() + + def print_banner(self): + """打印漂亮的横幅""" + print("\n" + "="*70) + print(" OneFLOW-CFD Automation System") + print(" 1D Convection: ENO3 vs WENO3 Comparison") + print("="*70 + "\n") + + def print_step(self, step_num, description): + """打印步骤信息""" + print(f"[{step_num}/7] {description}") + print("-" * 50) + + def check_environment(self): + """检查运行环境""" + self.print_step(2, "Checking Environment") # 调整步骤号,匹配后续流程 + + # 检查Python + try: + import numpy, matplotlib + print("✓ NumPy and Matplotlib are available") + except ImportError as e: + print(f"✗ Missing Python packages: {e}") + print("Installing required packages...") + try: + subprocess.run([sys.executable, "-m", "pip", "install", "numpy", "matplotlib", "-q"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + shell=True) + print("✓ Packages installed") + except Exception as install_e: + print(f"✗ Package installation failed: {install_e}") + sys.exit(1) + + # 检查Fortran编译器 + compilers = ["ifx", "ifort", "gfortran", "flang"] + found = False + for compiler in compilers: + try: + result = subprocess.run([compiler, "--version"], + env=self.combined_env, + capture_output=True, + text=True, + encoding='utf-8', # 替换为utf-8,避免gbk解码失败 + errors='ignore', # 忽略无效字符,兜底报错 + shell=True) + if result.returncode == 0: + print(f"✓ Found Fortran compiler: {compiler}") + found = True + break + except (FileNotFoundError, Exception): + continue + + if not found: + print("⚠ Warning: No Fortran compiler found in PATH") + print(" On Windows, please run from Intel oneAPI command prompt") + print(" On Linux/Mac, install: ifx, gfortran, or flang") + + # 检查CMake + try: + subprocess.run(["cmake", "--version"], + env=self.combined_env, + capture_output=True, + text=True, + encoding='utf-8', # 替换为utf-8 + errors='ignore', # 兜底无效字符 + shell=True) + print("✓ CMake is available") + except FileNotFoundError: + print("✗ CMake not found. Please install CMake 3.10+") + sys.exit(1) + + def setup_intel_environment(self): + """一次性提取setvars.bat配置后的所有环境变量(仅执行一次)""" + self.print_step(1, "Setting up Intel oneAPI Environment") # 明确步骤1 + + cmd_command = r'cmd.exe /C ""C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && set"' + + try: + result = subprocess.run( + cmd_command, + capture_output=True, + text=True, + encoding='gbk', # 保留gbk解析cmd输出 + errors='ignore', # 新增:忽略无法解码的字符,避免报错 + shell=True, + timeout=30 + ) + except Exception as e: + raise Exception(f"提取oneAPI环境失败:{str(e)}") + + if result.returncode != 0: + raise Exception(f"setvars.bat执行失败:{result.stderr}") + + # 解析环境变量(忽略非有效行) + env_dict = {} + for line in result.stdout.splitlines(): + line = line.strip() + if '=' in line and not line.startswith('='): + try: + key, value = line.split('=', 1) + env_dict[key.strip()] = value.strip() + except: + continue # 忽略分割失败的行,避免中断解析 + + oneapi_env = env_dict + + # 合并环境变量(系统原有环境 + oneAPI环境) + self.combined_env = os.environ.copy() + self.combined_env.update(oneapi_env) + + print("✓ Intel oneAPI environment loaded successfully") + return oneapi_env + + def configure_cmake(self): + """配置CMake""" + self.print_step(3, "Configuring with CMake") + + # 清理旧的构建目录 + if self.build_dir.exists(): + print("Cleaning previous build...") + shutil.rmtree(self.build_dir) + + # 创建构建目录 + self.build_dir.mkdir(exist_ok=True) + + # CMake命令 + cmake_cmd = ["cmake", "-S", str(self.project_root), "-B", str(self.build_dir)] + + # 平台特定选项 + if sys.platform == "win32": + cmake_cmd.extend(["-G", "Visual Studio 17 2022", "-A", "x64"]) + cmake_cmd.extend(["-T", "fortran=ifx"]) + else: + cmake_cmd.extend(["-DCMAKE_BUILD_TYPE=" + self.build_type]) + + print(f"Running: {' '.join(cmake_cmd)}") + + try: + # 核心修改:指定编码为utf-8,添加errors='ignore' + result = subprocess.run(cmake_cmd, check=True, + capture_output=not self.verbose, + env=self.combined_env, + text=True, + encoding='utf-8', + errors='ignore', + shell=True) + if self.verbose and result.stdout: + print(result.stdout) + print("✓ CMake configuration successful") + except subprocess.CalledProcessError as e: + print(f"✗ CMake configuration failed:") + if e.stderr: + print(e.stderr) + sys.exit(1) + + def build_project(self): + """构建项目""" + self.print_step(4, "Building Project") + + build_cmd = ["cmake", "--build", str(self.build_dir), "--config", self.build_type] + + if self.verbose: + build_cmd.append("--verbose") + + print(f"Running: {' '.join(build_cmd)}") + + try: + # 核心修改:指定编码为utf-8,添加errors='ignore',解决UnicodeDecodeError + result = subprocess.run(build_cmd, check=True, + capture_output=not self.verbose, + env=self.combined_env, + text=True, + encoding='utf-8', + errors='ignore', + shell=True) + if self.verbose and result.stdout: + print(result.stdout) + print("✓ Build successful") + + # 查找可执行文件 + self._find_executable() + + except subprocess.CalledProcessError as e: + print(f"✗ Build failed:") + if e.stderr: + print(e.stderr) + sys.exit(1) + + def _find_executable(self): + """查找生成的可执行文件""" + search_paths = [] + + if sys.platform == "win32": + search_paths = [ + self.build_dir / "bin" / self.build_type / "oneflow_cfd.exe", + self.build_dir / self.build_type / "oneflow_cfd.exe", + ] + else: + search_paths = [ + self.build_dir / "oneflow_cfd", + self.build_dir / "bin" / "oneflow_cfd", + ] + + for path in search_paths: + if path.exists(): + self.executable = path + self.bin_dir = path.parent + print(f"✓ Found executable: {self.executable}") + return + + print("⚠ Could not find executable automatically") + print(" Searched in:", [str(p) for p in search_paths]) + + def run_simulation(self): + """运行CFD模拟""" + self.print_step(5, "Running CFD Simulation") + + if not self.executable or not self.executable.exists(): + print("✗ Executable not found!") + return False + + # 确保postprocess.py在运行目录 + postprocess_src = self.project_root / "postprocess.py" + if postprocess_src.exists(): + shutil.copy2(postprocess_src, self.bin_dir / "postprocess.py") + + # 运行可执行文件 + print(f"Running: {self.executable.name}") + print("-" * 50) + + try: + original_dir = os.getcwd() + os.chdir(self.bin_dir) + + result = subprocess.run([str(self.executable)], + check=True, + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + env=self.combined_env, + shell=True) + + # 打印输出 + if result.stdout: + lines = result.stdout.strip().split('\n') + for line in lines: + if any(keyword in line.lower() for keyword in + ['error', 'warning', 'failed', 'success']): + if 'error' in line.lower() or 'failed' in line.lower(): + print(f"✗ {line}") + elif 'warning' in line.lower(): + print(f"⚠ {line}") + else: + print(f"✓ {line}") + else: + print(f" {line}") + + if result.stderr: + print("\nStderr output:") + print(result.stderr) + + print("-" * 50) + print("✓ Simulation completed") + + os.chdir(original_dir) + return True + + except subprocess.CalledProcessError as e: + print(f"✗ Simulation failed with exit code {e.returncode}") + if e.stdout: + print("Output:", e.stdout) + os.chdir(original_dir) + return False + + def generate_visualization(self): + """生成可视化结果""" + self.print_step(6, "Generating Visualization") + + original_dir = os.getcwd() + os.chdir(self.bin_dir) + + postprocess_script = self.bin_dir / "postprocess.py" + if not postprocess_script.exists(): + print("✗ postprocess.py not found in output directory") + os.chdir(original_dir) + return False + + print(f"Running: python {postprocess_script.name}") + + try: + result = subprocess.run([sys.executable, str(postprocess_script)], + check=True, + capture_output=True, + text=True, + encoding='utf-8', # 新增:指定utf-8编码 + errors='ignore', # 新增:忽略无效字符 + env=self.combined_env, + shell=True) + + if result.stdout and self.verbose: + print(result.stdout) + + print("✓ Visualization generated") + os.chdir(original_dir) + return True + + except subprocess.CalledProcessError as e: + print(f"✗ Visualization failed: {e}") + os.chdir(original_dir) + return False + + def organize_results(self): + """整理结果文件""" + self.print_step(7, "Organizing Results") + + # 创建结果目录 + self.results_dir.mkdir(exist_ok=True) + + # 复制结果文件 + result_patterns = ["*results.txt", "comparison.png", "*.dat"] + + for pattern in result_patterns: + for result_file in self.bin_dir.glob(pattern): + if result_file.is_file(): + dest = self.results_dir / result_file.name + # 避免同名文件覆盖提示,直接复制 + shutil.copy2(result_file, dest) + print(f"✓ Copied: {result_file.name}") + + # 创建结果摘要 + self._create_summary() + + print(f"\n✓ Results organized in: {self.results_dir}") + + def _create_summary(self): + """创建结果摘要""" + summary_file = self.results_dir / "simulation_summary.txt" + + with open(summary_file, 'w', encoding='utf-8') as f: # 指定utf-8写入,避免中文乱码 + f.write("OneFLOW-CFD Simulation Summary\n") + f.write("=" * 50 + "\n") + f.write(f"Date: {time.strftime('%Y-%m-%d %H:%M:%S')}\n") + f.write(f"Build Type: {self.build_type}\n") + f.write(f"Platform: {sys.platform}\n") + f.write("\nFiles Generated:\n") + + for file in sorted(self.results_dir.glob("*")): + if file.is_file(): + size_kb = file.stat().st_size / 1024 + f.write(f" - {file.name:20} {size_kb:6.1f} KB\n") + + def run_complete_pipeline(self): + """运行完整管道""" + self.print_banner() + + steps = [ + self.setup_intel_environment, + self.check_environment, + self.configure_cmake, + self.build_project, + self.run_simulation, + self.generate_visualization, + self.organize_results, + ] + + for i, step in enumerate(steps, 1): + try: + step() + except Exception as e: + print(f"\n✗ Step {i} failed: {e}") + print("\nDebug information:") + print(f" Project root: {self.project_root}") + print(f" Build dir: {self.build_dir}") + print(f" Executable: {self.executable}") + sys.exit(1) + + self._print_completion_message() + + def _print_completion_message(self): + """打印完成信息""" + print("\n" + "="*70) + print(" OneFLOW-CFD Automation Complete!") + print("="*70) + print("\nGenerated Files:") + print(" Results Directory: results/") + + for file in sorted(self.results_dir.glob("*")): + if file.is_file(): + print(f" - {file.name}") + + print("\nTo view the plot:") + plot_file = self.results_dir / "comparison.png" + if plot_file.exists(): + if sys.platform == "win32": + print(f" start results/comparison.png") + elif sys.platform == "darwin": + print(f" open results/comparison.png") + else: + print(f" xdg-open results/comparison.png") + + print("\n" + "="*70) + +def main(): + """主函数""" + parser = argparse.ArgumentParser( + description="OneFLOW-CFD Automation System", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + %(prog)s # Run complete pipeline + %(prog)s --verbose # Verbose output + %(prog)s --build Debug # Debug build + %(prog)s --clean # Clean before building + """ + ) + + parser.add_argument("--build", choices=["Release", "Debug", "RelWithDebInfo"], + default="Release", help="Build type") + parser.add_argument("--verbose", "-v", action="store_true", + help="Verbose output") + parser.add_argument("--clean", "-c", action="store_true", + help="Clean build directory before building") + parser.add_argument("--no-vis", action="store_true", + help="Skip visualization") + parser.add_argument("--no-run", action="store_true", + help="Skip running simulation") + + args = parser.parse_args() + + # 创建自动化实例 + automator = CFDAutomation(build_type=args.build, verbose=args.verbose) + + # 清理选项 + if args.clean and automator.build_dir.exists(): + print("Cleaning build directory...") + shutil.rmtree(automator.build_dir) + + # 运行管道 + try: + automator.run_complete_pipeline() + except KeyboardInterrupt: + print("\n\n⚠ Process interrupted by user") + sys.exit(130) + except Exception as e: + print(f"\n✗ Automation failed: {e}") + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/cfd/01g/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/cfd/01g/CMakeLists.txt new file mode 100644 index 000000000..25ed1aacb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/cfd/01g/CMakeLists.txt @@ -0,0 +1,20 @@ +# CMakeLists.txt +cmake_minimum_required(VERSION 4.2.1) + +project(OneFLOW_CFD LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +set(SOURCES + cfd_solver.f90 +) + +add_executable(oneflow_cfd ${SOURCES}) + +target_include_directories(oneflow_cfd PRIVATE ${CMAKE_Fortran_MODULE_DIRECTORY}) diff --git a/example/1d-linear-convection/weno3/fortran/cfd/01g/README.md b/example/1d-linear-convection/weno3/fortran/cfd/01g/README.md new file mode 100644 index 000000000..95888ac2e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/cfd/01g/README.md @@ -0,0 +1,26 @@ +# OneFLOW-CFD: 1D Convection Solver + +A Fortran implementation of 1D linear convection equation solver with ENO/WENO reconstruction schemes comparison. + +## Features + +- **Numerical Schemes**: ENO3 and WENO3 reconstruction +- **Time Integration**: Runge-Kutta methods (RK1, RK2) +- **Flux Schemes**: Rusanov and Engquist-Osher fluxes +- **Boundary Conditions**: Periodic boundary conditions +- **Visualization**: Python-based plotting and analysis +- **Automation**: Complete build-run-plot pipeline + +## Quick Start + +### Prerequisites +- Fortran compiler (Intel ifx/ifort, GNU gfortran, or LLVM flang) +- CMake 3.10+ +- Python 3.8+ with NumPy and Matplotlib + +### Installation + +1. Clone or download the project: + ```bash + git clone + cd OneFLOW-CFD-Fortran \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/cfd/01g/cfd_solver.f90 b/example/1d-linear-convection/weno3/fortran/cfd/01g/cfd_solver.f90 new file mode 100644 index 000000000..909e5acec --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/cfd/01g/cfd_solver.f90 @@ -0,0 +1,937 @@ +! OneFLOW-CFD Solver for 1D Convection Equation +! ENO/WENO Reconstruction Comparison - Single File Implementation + +module cfd_solver + use, intrinsic :: iso_fortran_env, only: dp => real64 + implicit none + + private + + ! =================================================================== + ! Forward Type Declarations + ! =================================================================== + type, public :: CfdConfigType + character(len=10) :: recon_scheme = "eno" + integer :: flux_type = 0 + integer :: rk_order = 1 + integer :: spatial_order = 3 + real(dp) :: wave_speed = 1.0_dp + real(dp) :: final_time = 0.625_dp + real(dp) :: dt = 0.025_dp ! 减小时间步长 + real(dp) :: cfl = 0.5_dp ! CFL数 + end type CfdConfigType + + type, public :: MeshType + real(dp) :: xmin = 0.0_dp + real(dp) :: xmax = 2.0_dp + integer :: ncells = 40 + integer :: nnodes = 0 + integer :: nx = 0 + real(dp) :: L = 0.0_dp + real(dp) :: dx = 0.0_dp + real(dp), allocatable :: x(:), xcc(:) + contains + procedure :: init => mesh_init + end type MeshType + + type, public :: ComputationalDomainType + type(MeshType) :: mesh + type(CfdConfigType) :: config + integer :: nghosts = 0 + integer :: ist = 0 + integer :: ied = 0 + integer :: ntcells = 0 + contains + procedure :: init => domain_init + end type ComputationalDomainType + + type, public :: SolutionType + type(ComputationalDomainType) :: domain + real(dp), allocatable :: q_face_left(:), q_face_right(:) + real(dp), allocatable :: flux(:), res(:) + real(dp), allocatable :: u(:), un(:) + contains + procedure :: init => solution_init + end type SolutionType + + ! Main CFD solver class + type, public :: CfdType + type(CfdConfigType) :: config + type(ComputationalDomainType) :: domain + type(SolutionType) :: solution + class(*), allocatable :: reconstructor + contains + procedure :: init => cfd_init + end type CfdType + + ! Abstract reconstructor base class + type, abstract, public :: ReconstructorType + contains + procedure(reconstruct_interface), deferred, pass :: reconstruct + end type ReconstructorType + + ! Define abstract interface + abstract interface + subroutine reconstruct_interface(this, q, cfd) + import :: ReconstructorType, CfdType, dp + class(ReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + end subroutine reconstruct_interface + end interface + + ! ENO reconstructor + type, extends(ReconstructorType), public :: EnoReconstructorType + integer :: spatial_order + integer :: ntcells + integer, allocatable :: lmc(:) + real(dp), allocatable :: coef(:,:) + real(dp), allocatable :: dd(:,:) + contains + procedure :: reconstruct => eno_reconstruct + procedure :: init => eno_init + end type EnoReconstructorType + + ! WENO reconstructor + type, extends(ReconstructorType), public :: WenoReconstructorType + contains + procedure :: reconstruct => weno_reconstruct + end type WenoReconstructorType + + ! =================================================================== + ! Public Procedures + ! =================================================================== + public :: run_simulation, init_field, analytical_solution, performEnoWenoAnalysis + + ! =================================================================== + ! Module Variables + ! =================================================================== + real(dp), parameter :: eps_weno = 1.0e-6_dp + +contains + + ! =================================================================== + ! Initialization Methods + ! =================================================================== + + ! Mesh initialization + subroutine mesh_init(this) + class(MeshType), intent(inout) :: this + integer :: i + + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, dp) + + allocate(this%x(this%nnodes), this%xcc(this%ncells)) + + ! Node coordinates + do i = 1, this%nnodes + this%x(i) = this%xmin + (i-1) * this%dx + end do + + ! Cell center coordinates + do i = 1, this%ncells + this%xcc(i) = 0.5_dp * (this%x(i) + this%x(i+1)) + end do + end subroutine mesh_init + + ! Domain initialization + subroutine domain_init(this, mesh, config) + class(ComputationalDomainType), intent(inout) :: this + type(MeshType), intent(in) :: mesh + type(CfdConfigType), intent(in) :: config + + this%mesh = mesh + this%config = config + + ! Calculate ghost cells + if (trim(config%recon_scheme) == "eno") then + this%nghosts = config%spatial_order + else if (trim(config%recon_scheme) == "weno") then + this%nghosts = config%spatial_order / 2 + 1 + else + error stop "Unknown reconstruction scheme" + end if + + this%ist = this%nghosts + 1 + this%ied = this%ist + mesh%ncells - 1 + this%ntcells = mesh%ncells + 2 * this%nghosts + + print *, "Domain initialized:" + print *, " mesh.ncells = ", mesh%ncells + print *, " spatial_order = ", config%spatial_order + print *, " nghosts = ", this%nghosts + print *, " ist = ", this%ist, ", ied = ", this%ied + print *, " dx = ", mesh%dx + end subroutine domain_init + + ! Solution initialization + subroutine solution_init(this, domain) + class(SolutionType), intent(inout) :: this + type(ComputationalDomainType), intent(in) :: domain + + this%domain = domain + + allocate(this%q_face_left(domain%mesh%nnodes)) + allocate(this%q_face_right(domain%mesh%nnodes)) + allocate(this%flux(domain%mesh%nnodes)) + allocate(this%res(domain%mesh%ncells)) + allocate(this%u(domain%ntcells)) + allocate(this%un(domain%ntcells)) + + this%q_face_left = 0.0_dp + this%q_face_right = 0.0_dp + this%flux = 0.0_dp + this%res = 0.0_dp + this%u = 0.0_dp + this%un = 0.0_dp + end subroutine solution_init + + ! ENO reconstructor initialization + subroutine eno_init(this, spatial_order, ntcells) + class(EnoReconstructorType), intent(inout) :: this + integer, intent(in) :: spatial_order + integer, intent(in) :: ntcells + + this%spatial_order = spatial_order + this%ntcells = ntcells + + allocate(this%lmc(ntcells)) + allocate(this%coef(spatial_order+1, spatial_order)) + allocate(this%dd(spatial_order, ntcells)) + + this%lmc = 0 + this%coef = 0.0_dp + this%dd = 0.0_dp + + ! Initialize coefficients + call init_coef(spatial_order, this%coef) + end subroutine eno_init + + ! CFD solver initialization + subroutine cfd_init(this, config, domain) + class(CfdType), intent(inout) :: this + type(CfdConfigType), intent(in) :: config + type(ComputationalDomainType), intent(in) :: domain + + this%config = config + this%domain = domain + call this%solution%init(domain) + + ! Create reconstructor based on scheme + if (trim(config%recon_scheme) == "eno") then + allocate(EnoReconstructorType :: this%reconstructor) + select type(rec => this%reconstructor) + type is (EnoReconstructorType) + call rec%init(config%spatial_order, domain%ntcells) + end select + else if (trim(config%recon_scheme) == "weno") then + allocate(WenoReconstructorType :: this%reconstructor) + else + error stop "Unknown reconstruction scheme" + end if + + ! Adjust time step based on CFL condition + call calculate_dt(this) + end subroutine cfd_init + + ! Calculate time step based on CFL condition + subroutine calculate_dt(cfd) + type(CfdType), intent(inout) :: cfd + + real(dp) :: dt_cfl + + ! CFL condition: dt <= CFL * dx / |wave_speed| + dt_cfl = cfd%config%cfl * cfd%domain%mesh%dx / abs(cfd%config%wave_speed) + + if (cfd%config%dt > dt_cfl) then + print *, "Adjusting time step for stability:" + print *, " Original dt = ", cfd%config%dt + print *, " CFL dt = ", dt_cfl + cfd%config%dt = dt_cfl + print *, " Using dt = ", cfd%config%dt + end if + + end subroutine calculate_dt + + ! =================================================================== + ! Initial Conditions and Analytical Solution + ! =================================================================== + + ! Initial condition: step function + function initial_condition(x) result(u0) + real(dp), intent(in) :: x + real(dp) :: u0 + + if (0.5_dp <= x .and. x <= 1.0_dp) then + u0 = 2.0_dp + else + u0 = 1.0_dp + end if + end function initial_condition + + ! Analytical solution with periodic BC + function analytical_solution(x, t, a, L) result(u) + real(dp), intent(in) :: x, t, a, L + real(dp) :: u, x_shifted + + x_shifted = mod(x - a * t + L, L) + u = initial_condition(x_shifted) + end function analytical_solution + + ! Initialize field with step function + subroutine init_field(cfd) + type(CfdType), intent(inout) :: cfd + integer :: i, j + + do i = 1, cfd%domain%mesh%ncells + if (0.5_dp <= cfd%domain%mesh%xcc(i) .and. cfd%domain%mesh%xcc(i) <= 1.0_dp) then + cfd%solution%u(cfd%domain%ist + i - 1) = 2.0_dp + else + cfd%solution%u(cfd%domain%ist + i - 1) = 1.0_dp + end if + end do + + call boundary(cfd%solution%u, cfd) + call update_oldfield(cfd%solution%un, cfd%solution%u, cfd%domain%ntcells) + end subroutine init_field + + ! =================================================================== + ! Boundary Conditions + ! =================================================================== + + ! Periodic boundary conditions - CORRECTED VERSION + subroutine periodic_boundary(u, cfd) + real(dp), intent(inout) :: u(:) + type(CfdType), intent(in) :: cfd + integer :: i + + ! Copy interior cells to ghost cells + ! Left ghost cells = right interior cells + do i = 1, cfd%domain%nghosts + u(cfd%domain%ist - i) = u(cfd%domain%ied - cfd%domain%nghosts + i - 1) + end do + + ! Right ghost cells = left interior cells + do i = 1, cfd%domain%nghosts + u(cfd%domain%ied + i) = u(cfd%domain%ist + i - 1) + end do + end subroutine periodic_boundary + + ! Boundary condition wrapper + subroutine boundary(u, cfd) + real(dp), intent(inout) :: u(:) + type(CfdType), intent(in) :: cfd + + call periodic_boundary(u, cfd) + end subroutine boundary + + ! =================================================================== + ! Reconstruction Methods + ! =================================================================== + + ! Initialize ENO/WENO coefficients + subroutine init_coef(spatial_order, coef) + integer, intent(in) :: spatial_order + real(dp), intent(out) :: coef(:,:) + + coef = 0.0_dp + + select case(spatial_order) + case(1) + coef(1,1) = 1.0_dp + coef(2,1) = 1.0_dp + + case(2) + coef(1,1:2) = [ 3.0_dp/2.0_dp, -1.0_dp/2.0_dp ] + coef(2,1:2) = [ 1.0_dp/2.0_dp, 1.0_dp/2.0_dp ] + coef(3,1:2) = [ -1.0_dp/2.0_dp, 3.0_dp/2.0_dp ] + + case(3) + coef(1,1:3) = [ 11.0_dp/6.0_dp, -7.0_dp/6.0_dp, 1.0_dp/3.0_dp ] + coef(2,1:3) = [ 1.0_dp/3.0_dp, 5.0_dp/6.0_dp, -1.0_dp/6.0_dp ] + coef(3,1:3) = [ -1.0_dp/6.0_dp, 5.0_dp/6.0_dp, 1.0_dp/3.0_dp ] + coef(4,1:3) = [ 1.0_dp/3.0_dp, -7.0_dp/6.0_dp, 11.0_dp/6.0_dp ] + + case default + error stop "Unsupported spatial order" + end select + end subroutine init_coef + + subroutine first_order_reconstruct(this, q, cfd) + class(EnoReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + + integer :: i, j + integer :: nghosts, ist, ied + + nghosts = cfd%domain%nghosts + ist = cfd%domain%ist + ied = cfd%domain%ied + + ! For now, use simple 2nd order reconstruction + do i = ist, ied + j = i - ist + 1 ! 1-based index for interfaces + + cfd%solution%q_face_left(j) = q(i-1) + cfd%solution%q_face_right(j) = q(i) + end do + end subroutine first_order_reconstruct + + subroutine eno_reconstruct(this, q, cfd) + class(EnoReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + type(ComputationalDomainType), pointer :: domain + + integer :: i, j, m, k1, k2, r1, r2 + integer :: nghosts, ist, ied + + nghosts = cfd%domain%nghosts + ist = cfd%domain%ist + ied = cfd%domain%ied + + ! 1. 差商计算 (dd[1,:] = q) + this%dd(1, :) = q(:) + do m = 2, this%spatial_order + do j = 1, cfd%domain%ntcells - m + 1 + this%dd(m, j) = this%dd(m-1, j+1) - this%dd(m-1, j) + end do + end do + + ! 2. 选择 smoothest stencil + do i = ist - 1, ied ! Python: range(ist-1, ied+1) → ied+1-1 = ied + this%lmc(i) = i + do m = 2, this%spatial_order + if ( abs(this%dd(m, this%lmc(i) - 1) ) < abs(this%dd(m, this%lmc(i)))) then + this%lmc(i) = this%lmc(i) - 1 + end if + end do + end do + + associate ( & + q_face_left => cfd%solution%q_face_left, & + q_face_right => cfd%solution%q_face_right & + ) + ! 这里可以直接使用 q_face_left 和 q_face_right + ! 3. 重构界面值 + do i = ist, ied + j = i - ist + 1 ! 1-based index for interfaces + k1 = this%lmc(i - 1) + k2 = this%lmc(i) + r1 = (i - 1) - k1 + 1 + r2 = i - k2 + 1 + q_face_left(j) = 0.0 + q_face_right(j) = 0.0 + do m = 1, this%spatial_order + q_face_left(j) = q_face_left(j) + q(k1 + m - 1) * this%coef(r1 + 1, m) + q_face_right(j) = q_face_right(j) + q(k2 + m - 1) * this%coef(r2, m) + end do + end do + end associate + + end subroutine eno_reconstruct + + ! WENO-3 nonlinear weights for left-biased stencil + function wc3L(v1, v2, v3) result(f) + real(dp), intent(in) :: v1, v2, v3 + real(dp) :: f, s0, s1, d0, d1, c0, c1, w0, w1, q0, q1 + + ! Smoothness indicators + s0 = (v3 - v2)**2 + s1 = (v2 - v1)**2 + + ! Nonlinear weights + d0 = 2.0_dp/3.0_dp + d1 = 1.0_dp/3.0_dp + + c0 = d0 / ((eps_weno + s0)**2) + c1 = d1 / ((eps_weno + s1)**2) + + w0 = c0 / (c0 + c1) + w1 = c1 / (c0 + c1) + + ! Candidate stencils + q0 = 0.5_dp * v2 + 0.5_dp * v3 + q1 = -0.5_dp * v1 + 1.5_dp * v2 + + ! Reconstructed value + f = w0 * q0 + w1 * q1 + end function wc3L + + ! WENO-3 nonlinear weights for right-biased stencil + function wc3R(v1, v2, v3) result(f) + real(dp), intent(in) :: v1, v2, v3 + real(dp) :: f, s0, s1, d0, d1, c0, c1, w0, w1, q0, q1 + + ! Smoothness indicators + s0 = (v2 - v1)**2 + s1 = (v3 - v2)**2 + + ! Nonlinear weights + d0 = 2.0_dp/3.0_dp + d1 = 1.0_dp/3.0_dp + + c0 = d0 / ((eps_weno + s0)**2) + c1 = d1 / ((eps_weno + s1)**2) + + w0 = c0 / (c0 + c1) + w1 = c1 / (c0 + c1) + + ! Candidate stencils + q0 = 0.5_dp * v1 + 0.5_dp * v2 + q1 = 1.5_dp * v2 - 0.5_dp * v3 + + ! Reconstructed value + f = w0 * q0 + w1 * q1 + end function wc3R + + ! WENO-3 reconstruction for left interface + subroutine weno3L_periodic(cfd, u, qL) + type(CfdType), intent(in) :: cfd + real(dp), intent(in) :: u(:) + real(dp), intent(out) :: qL(:) + + integer :: i, j, nghosts, ist, ied + + nghosts = cfd%domain%nghosts + ist = cfd%domain%ist + ied = cfd%domain%ied + + do i = ist-1, ied-1 + j = i - (ist - 1) + 1 + qL(j) = wc3L(u(i-1), u(i), u(i+1)) + end do + + end subroutine weno3L_periodic + + ! WENO-3 reconstruction for right interface + subroutine weno3R_periodic(cfd, u, qR) + type(CfdType), intent(in) :: cfd + real(dp), intent(in) :: u(:) + real(dp), intent(out) :: qR(:) + + integer :: i, j, nghosts, ist, ied + + nghosts = cfd%domain%nghosts + ist = cfd%domain%ist + ied = cfd%domain%ied + + do i = ist, ied + j = i - ist + 1 + qR(j) = wc3R(u(i-1), u(i), u(i+1)) + end do + + end subroutine weno3R_periodic + + ! WENO reconstruction + subroutine weno_reconstruct(this, q, cfd) + class(WenoReconstructorType), intent(inout) :: this + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + + call weno3L_periodic(cfd, q, cfd%solution%q_face_left) + call weno3R_periodic(cfd, q, cfd%solution%q_face_right) + end subroutine weno_reconstruct + + ! General reconstruction wrapper + subroutine reconstruction(q, cfd) + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + + select type(rec => cfd%reconstructor) + type is (EnoReconstructorType) + call rec%reconstruct(q, cfd) + type is (WenoReconstructorType) + call rec%reconstruct(q, cfd) + class default + error stop "Unknown reconstructor type" + end select + end subroutine reconstruction + + ! =================================================================== + ! Flux Functions + ! =================================================================== + + ! Rusanov flux + subroutine rusanov_flux(q_face_left, q_face_right, flux, cfd) + real(dp), intent(in) :: q_face_left(:), q_face_right(:) + real(dp), intent(out) :: flux(:) + type(CfdType), intent(in) :: cfd + + integer :: i + real(dp) :: u_L, u_R, F_L, F_R, c_L, c_R, Smax + + c_L = cfd%config%wave_speed + c_R = cfd%config%wave_speed + + do i = 1, cfd%domain%mesh%nnodes + u_L = q_face_left(i) + u_R = q_face_right(i) + F_L = c_L * u_L + F_R = c_R * u_R + Smax = max(abs(c_L), abs(c_R)) + flux(i) = 0.5_dp * (F_L + F_R) - 0.5_dp * Smax * (u_R - u_L) + end do + end subroutine rusanov_flux + + ! Engquist-Osher flux + subroutine engquist_osher_flux(q_face_left, q_face_right, flux, cfd) + real(dp), intent(in) :: q_face_left(:), q_face_right(:) + real(dp), intent(out) :: flux(:) + type(CfdType), intent(in) :: cfd + + integer :: i + real(dp) :: c, cp, cm, u_L, u_R + + c = cfd%config%wave_speed + + do i = 1, cfd%domain%mesh%nnodes + cp = 0.5_dp * (c + abs(c)) + cm = 0.5_dp * (c - abs(c)) + u_L = q_face_left(i) + u_R = q_face_right(i) + flux(i) = cp * u_L + cm * u_R + end do + end subroutine engquist_osher_flux + + ! Inviscid flux selection + subroutine inviscid_flux(q_face_left, q_face_right, flux, cfd) + real(dp), intent(in) :: q_face_left(:), q_face_right(:) + real(dp), intent(out) :: flux(:) + type(CfdType), intent(in) :: cfd + + if (cfd%config%flux_type == 0) then + call rusanov_flux(q_face_left, q_face_right, flux, cfd) + else + call engquist_osher_flux(q_face_left, q_face_right, flux, cfd) + end if + end subroutine inviscid_flux + + ! =================================================================== + ! Residual Computation + ! =================================================================== + + ! Compute residual (flux divergence) - CORRECTED VERSION + subroutine residual(q, cfd) + real(dp), intent(in) :: q(:) + type(CfdType), intent(inout) :: cfd + + integer :: i + + ! Apply boundary conditions first + call boundary(cfd%solution%u, cfd) + + ! Reconstruction + call reconstruction(q, cfd) + + ! Compute fluxes + call inviscid_flux(cfd%solution%q_face_left, cfd%solution%q_face_right, & + cfd%solution%flux, cfd) + + ! Compute residual - corrected indexing + do i = 1, cfd%domain%mesh%ncells + cfd%solution%res(i) = -(cfd%solution%flux(i+1) - cfd%solution%flux(i)) / & + cfd%domain%mesh%dx + end do + end subroutine residual + + ! =================================================================== + ! Time Integration + ! =================================================================== + + ! Update old field + subroutine update_oldfield(qn, q, n) + real(dp), intent(out) :: qn(:) + real(dp), intent(in) :: q(:) + integer, intent(in) :: n + + qn(1:n) = q(1:n) + end subroutine update_oldfield + + ! 1st-order Runge-Kutta (Euler) - SIMPLIFIED + subroutine runge_kutta_1(cfd) + type(CfdType), intent(inout) :: cfd + + integer :: i, j + real(dp) :: dt + + dt = cfd%config%dt + + ! Apply boundary conditions + call boundary(cfd%solution%u, cfd) + + ! Compute residual + call residual(cfd%solution%u, cfd) + + ! Update solution + do i = cfd%domain%ist, cfd%domain%ied - 1 + j = i - cfd%domain%ist + 1 + cfd%solution%u(i) = cfd%solution%u(i) + dt * cfd%solution%res(j) + end do + + ! Apply boundary conditions again + call boundary(cfd%solution%u, cfd) + + ! Save old solution + call update_oldfield(cfd%solution%un, cfd%solution%u, cfd%domain%ntcells) + end subroutine runge_kutta_1 + + ! 2nd-order Runge-Kutta (Heun) + subroutine runge_kutta_2(cfd) + type(CfdType), intent(inout) :: cfd + + integer :: i, j + real(dp) :: dt + + dt = cfd%config%dt + + ! Stage 1 + call boundary(cfd%solution%u, cfd) + call residual(cfd%solution%u, cfd) + + do i = cfd%domain%ist, cfd%domain%ied - 1 + j = i - cfd%domain%ist + 1 + cfd%solution%u(i) = cfd%solution%u(i) + dt * cfd%solution%res(j) + end do + call boundary(cfd%solution%u, cfd) + + ! Stage 2 + call residual(cfd%solution%u, cfd) + do i = cfd%domain%ist, cfd%domain%ied - 1 + j = i - cfd%domain%ist + 1 + cfd%solution%u(i) = 0.5_dp * cfd%solution%un(i) + & + 0.5_dp * cfd%solution%u(i) + & + 0.5_dp * dt * cfd%solution%res(j) + end do + call boundary(cfd%solution%u, cfd) + + call update_oldfield(cfd%solution%un, cfd%solution%u, cfd%domain%ntcells) + end subroutine runge_kutta_2 + + ! Runge-Kutta selection + subroutine runge_kutta(cfd) + type(CfdType), intent(inout) :: cfd + + select case(cfd%config%rk_order) + case(1) + call runge_kutta_1(cfd) + case(2) + call runge_kutta_2(cfd) + case default + call runge_kutta_1(cfd) + end select + end subroutine runge_kutta + + ! =================================================================== + ! Simulation Driver + ! =================================================================== + + ! Run simulation to final time + function run_simulation(cfd, final_time) result(u_result) + type(CfdType), intent(inout) :: cfd + real(dp), intent(in) :: final_time + real(dp), allocatable :: u_result(:) + + real(dp) :: t, dt, dt_old + integer :: step, max_steps + + allocate(u_result(cfd%domain%mesh%ncells)) + + t = 0.0_dp + dt_old = cfd%config%dt + dt = dt_old + max_steps = 10000 ! Safety limit + + print *, "Starting time integration..." + print *, " Final time: ", final_time + print *, " Time step: ", dt + print *, " CFL number: ", cfd%config%cfl + + step = 0 + do while (t < final_time - 1.0e-12_dp .and. step < max_steps) + step = step + 1 + + if (t + dt > final_time) then + dt = final_time - t + end if + + cfd%config%dt = dt + call runge_kutta(cfd) + t = t + dt + + ! Progress report + if (mod(step, 100) == 0) then + print *, " Step ", step, ", Time = ", t + end if + end do + + if (step >= max_steps) then + print *, "Warning: Reached maximum number of steps (", max_steps, ")" + end if + + cfd%config%dt = dt_old + + print *, "Time integration completed:" + print *, " Total steps: ", step + print *, " Final time: ", t + + ! Extract physical solution (without ghost cells) + u_result = cfd%solution%u(cfd%domain%ist:cfd%domain%ied) + end function run_simulation + + ! =================================================================== + ! Main Analysis Function + ! =================================================================== + + ! Perform ENO-WENO comparative analysis + subroutine performEnoWenoAnalysis() + type(CfdConfigType) :: config_eno3, config_weno3 + type(MeshType) :: mesh + type(ComputationalDomainType) :: domain_eno3, domain_weno3 + type(CfdType) :: cfd_eno3, cfd_weno3 + real(dp), allocatable :: u_eno(:), u_weno(:), u_analytical(:) + real(dp), allocatable :: xcc(:) + integer :: i, ncells, iunit + + ! Initialize mesh + call mesh%init() + ncells = mesh%ncells + allocate(xcc(ncells)) + xcc = mesh%xcc + + print *, "==========================================" + print *, "Mesh parameters:" + print *, " ncells = ", ncells + print *, " dx = ", mesh%dx + print *, " L = ", mesh%L + print *, "==========================================" + + ! Configure ENO3 - using simple 2nd order for stability + config_eno3%recon_scheme = "eno" + config_eno3%spatial_order = 3 + config_eno3%flux_type = 0 + config_eno3%rk_order = 1 + config_eno3%wave_speed = 1.0_dp + config_eno3%final_time = 0.625_dp + config_eno3%cfl = 1.0_dp + config_eno3%dt = 0.0025_dp + + ! Configure WENO3 + config_weno3%recon_scheme = "weno" + config_weno3%spatial_order = 3 + config_weno3%flux_type = 0 + config_weno3%rk_order = 1 + config_weno3%wave_speed = 1.0_dp + config_weno3%final_time = 0.625_dp + config_weno3%cfl = 1.0_dp + config_weno3%dt = 0.0025_dp + + ! Create domains + call domain_eno3%init(mesh, config_eno3) + call domain_weno3%init(mesh, config_weno3) + + ! Create CFD solvers + call cfd_eno3%init(config_eno3, domain_eno3) + call cfd_weno3%init(config_weno3, domain_weno3) + + ! Allocate arrays + allocate(u_eno(ncells), u_weno(ncells), u_analytical(ncells)) + + ! Run ENO simulation + print *, "==========================================" + print *, "Running ENO simulation..." + print *, " Scheme: ENO", config_eno3%spatial_order + print *, " Time step: ", config_eno3%dt + print *, "==========================================" + + call init_field(cfd_eno3) + u_eno = run_simulation(cfd_eno3, config_eno3%final_time) + + ! Run WENO simulation + print *, "==========================================" + print *, "Running WENO simulation..." + print *, " Scheme: WENO", config_weno3%spatial_order + print *, " Time step: ", config_weno3%dt + print *, "==========================================" + + call init_field(cfd_weno3) + u_weno = run_simulation(cfd_weno3, config_weno3%final_time) + + ! Compute analytical solution + print *, "Computing analytical solution..." + do i = 1, ncells + u_analytical(i) = analytical_solution(xcc(i), config_weno3%final_time, & + config_weno3%wave_speed, mesh%L) + end do + + ! Write results to files + print *, "Writing results to files..." + + ! Write ENO results + open(newunit=iunit, file='eno_results.txt', status='replace') + write(iunit, '(A)') '# x, u (ENO)' + do i = 1, ncells + write(iunit, '(2F12.6)') xcc(i), u_eno(i) + end do + close(iunit) + + ! Write WENO results + open(newunit=iunit, file='weno_results.txt', status='replace') + write(iunit, '(A)') '# x, u (WENO)' + do i = 1, ncells + write(iunit, '(2F12.6)') xcc(i), u_weno(i) + end do + close(iunit) + + ! Write analytical results + open(newunit=iunit, file='analytical_results.txt', status='replace') + write(iunit, '(A)') '# x, u (Analytical)' + do i = 1, ncells + write(iunit, '(2F12.6)') xcc(i), u_analytical(i) + end do + close(iunit) + + ! Print some statistics + print *, "==========================================" + print *, "Simulation statistics:" + print *, " ENO min/max: ", minval(u_eno), maxval(u_eno) + print *, " WENO min/max: ", minval(u_weno), maxval(u_weno) + print *, " Analytical min/max: ", minval(u_analytical), maxval(u_analytical) + print *, "==========================================" + + print *, "Simulation completed successfully!" + print *, "Results written to:" + print *, " eno_results.txt" + print *, " weno_results.txt" + print *, " analytical_results.txt" + print *, "" + print *, "To generate the comparison plot, run:" + print *, " python postprocess.py" + print *, "==========================================" + + deallocate(u_eno, u_weno, u_analytical, xcc) + end subroutine performEnoWenoAnalysis + +end module cfd_solver + +! =================================================================== +! Main Program +! =================================================================== +program main + use cfd_solver + implicit none + + print *, "==========================================" + print *, "OneFLOW-CFD Solver for 1D Convection" + print *, "ENO vs WENO Comparison" + print *, "==========================================" + + call performEnoWenoAnalysis() + + print *, "Program finished successfully!" + +end program main \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/cfd/01g/postprocess.py b/example/1d-linear-convection/weno3/fortran/cfd/01g/postprocess.py new file mode 100644 index 000000000..129cb9adc --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/cfd/01g/postprocess.py @@ -0,0 +1,52 @@ +import numpy as np +import matplotlib.pyplot as plt + +def read_results(filename): + """Read results from Fortran output file""" + try: + data = np.loadtxt(filename, comments='#') + return data[:, 0], data[:, 1] + except Exception as e: + print(f"Error reading {filename}: {e}") + return np.array([]), np.array([]) + +def main(): + print("Reading Fortran output files...") + + # Read all data files + x_eno, u_eno = read_results('eno_results.txt') + x_weno, u_weno = read_results('weno_results.txt') + x_analytical, u_analytical = read_results('analytical_results.txt') + + # Check if we have data + if len(x_eno) == 0 or len(x_weno) == 0 or len(x_analytical) == 0: + print("Error: Could not read all data files.") + print("Make sure to run the Fortran program first.") + return + + # Create plot + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # Plot results + plt.plot(x_eno, u_eno, 'bo-', linewidth=1, markersize=5, + markerfacecolor='none', label='ENO3') + plt.plot(x_weno, u_weno, 'gs-', linewidth=1, markersize=5, + markerfacecolor='none', label='WENO3') + plt.plot(x_analytical, u_analytical, 'r-', linewidth=2, label='Analytical') + + # Customize plot + plt.title('1D Convection: ENO3 vs WENO3 (t=0.625)') + plt.xlabel('x') + plt.ylabel('u') + plt.legend() + plt.grid(True, alpha=0.3) + + # Save and show + plt.tight_layout() + plt.savefig('comparison.png', dpi=150) + plt.show() + + print("Plot saved as comparison.png") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/cfd/01g/run_all.py b/example/1d-linear-convection/weno3/fortran/cfd/01g/run_all.py new file mode 100644 index 000000000..44bf7c893 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/cfd/01g/run_all.py @@ -0,0 +1,482 @@ +#!/usr/bin/env python3 +""" +OneFLOW-CFD Complete Automation System +Author: Your Name +Date: 2024 +Description: Automated build, run, and visualization pipeline +""" + +import os +import sys +import subprocess +import shutil +import time +from pathlib import Path +import argparse + +class CFDAutomation: + """自动化CFD求解器的完整流程""" + + def __init__(self, build_type="Release", verbose=False): + self.project_root = Path.cwd() + self.build_dir = self.project_root / "build" + self.bin_dir = None + self.executable = None + self.build_type = build_type + self.verbose = verbose + self.results_dir = self.project_root / "results" + # 初始化combined_env,避免属性不存在报错 + self.combined_env = os.environ.copy() + + def print_banner(self): + """打印漂亮的横幅""" + print("\n" + "="*70) + print(" OneFLOW-CFD Automation System") + print(" 1D Convection: ENO3 vs WENO3 Comparison") + print("="*70 + "\n") + + def print_step(self, step_num, description): + """打印步骤信息""" + print(f"[{step_num}/7] {description}") + print("-" * 50) + + def check_environment(self): + """检查运行环境""" + self.print_step(2, "Checking Environment") # 调整步骤号,匹配后续流程 + + # 检查Python + try: + import numpy, matplotlib + print("✓ NumPy and Matplotlib are available") + except ImportError as e: + print(f"✗ Missing Python packages: {e}") + print("Installing required packages...") + try: + subprocess.run([sys.executable, "-m", "pip", "install", "numpy", "matplotlib", "-q"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + shell=True) + print("✓ Packages installed") + except Exception as install_e: + print(f"✗ Package installation failed: {install_e}") + sys.exit(1) + + # 检查Fortran编译器 + compilers = ["ifx", "ifort", "gfortran", "flang"] + found = False + for compiler in compilers: + try: + result = subprocess.run([compiler, "--version"], + env=self.combined_env, + capture_output=True, + text=True, + encoding='utf-8', # 替换为utf-8,避免gbk解码失败 + errors='ignore', # 忽略无效字符,兜底报错 + shell=True) + if result.returncode == 0: + print(f"✓ Found Fortran compiler: {compiler}") + found = True + break + except (FileNotFoundError, Exception): + continue + + if not found: + print("⚠ Warning: No Fortran compiler found in PATH") + print(" On Windows, please run from Intel oneAPI command prompt") + print(" On Linux/Mac, install: ifx, gfortran, or flang") + + # 检查CMake + try: + subprocess.run(["cmake", "--version"], + env=self.combined_env, + capture_output=True, + text=True, + encoding='utf-8', # 替换为utf-8 + errors='ignore', # 兜底无效字符 + shell=True) + print("✓ CMake is available") + except FileNotFoundError: + print("✗ CMake not found. Please install CMake 3.10+") + sys.exit(1) + + def setup_intel_environment(self): + """一次性提取setvars.bat配置后的所有环境变量(仅执行一次)""" + self.print_step(1, "Setting up Intel oneAPI Environment") # 明确步骤1 + + cmd_command = r'cmd.exe /C ""C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && set"' + + try: + result = subprocess.run( + cmd_command, + capture_output=True, + text=True, + encoding='gbk', # 保留gbk解析cmd输出 + errors='ignore', # 新增:忽略无法解码的字符,避免报错 + shell=True, + timeout=30 + ) + except Exception as e: + raise Exception(f"提取oneAPI环境失败:{str(e)}") + + if result.returncode != 0: + raise Exception(f"setvars.bat执行失败:{result.stderr}") + + # 解析环境变量(忽略非有效行) + env_dict = {} + for line in result.stdout.splitlines(): + line = line.strip() + if '=' in line and not line.startswith('='): + try: + key, value = line.split('=', 1) + env_dict[key.strip()] = value.strip() + except: + continue # 忽略分割失败的行,避免中断解析 + + oneapi_env = env_dict + + # 合并环境变量(系统原有环境 + oneAPI环境) + self.combined_env = os.environ.copy() + self.combined_env.update(oneapi_env) + + print("✓ Intel oneAPI environment loaded successfully") + return oneapi_env + + def configure_cmake(self): + """配置CMake""" + self.print_step(3, "Configuring with CMake") + + # 清理旧的构建目录 + if self.build_dir.exists(): + print("Cleaning previous build...") + shutil.rmtree(self.build_dir) + + # 创建构建目录 + self.build_dir.mkdir(exist_ok=True) + + # CMake命令 + cmake_cmd = ["cmake", "-S", str(self.project_root), "-B", str(self.build_dir)] + + # 平台特定选项 + if sys.platform == "win32": + cmake_cmd.extend(["-G", "Visual Studio 17 2022", "-A", "x64"]) + cmake_cmd.extend(["-T", "fortran=ifx"]) + else: + cmake_cmd.extend(["-DCMAKE_BUILD_TYPE=" + self.build_type]) + + print(f"Running: {' '.join(cmake_cmd)}") + + try: + # 核心修改:指定编码为utf-8,添加errors='ignore' + result = subprocess.run(cmake_cmd, check=True, + capture_output=not self.verbose, + env=self.combined_env, + text=True, + encoding='utf-8', + errors='ignore', + shell=True) + if self.verbose and result.stdout: + print(result.stdout) + print("✓ CMake configuration successful") + except subprocess.CalledProcessError as e: + print(f"✗ CMake configuration failed:") + if e.stderr: + print(e.stderr) + sys.exit(1) + + def build_project(self): + """构建项目""" + self.print_step(4, "Building Project") + + build_cmd = ["cmake", "--build", str(self.build_dir), "--config", self.build_type] + + if self.verbose: + build_cmd.append("--verbose") + + print(f"Running: {' '.join(build_cmd)}") + + try: + # 核心修改:指定编码为utf-8,添加errors='ignore',解决UnicodeDecodeError + result = subprocess.run(build_cmd, check=True, + capture_output=not self.verbose, + env=self.combined_env, + text=True, + encoding='utf-8', + errors='ignore', + shell=True) + if self.verbose and result.stdout: + print(result.stdout) + print("✓ Build successful") + + # 查找可执行文件 + self._find_executable() + + except subprocess.CalledProcessError as e: + print(f"✗ Build failed:") + if e.stderr: + print(e.stderr) + sys.exit(1) + + def _find_executable(self): + """查找生成的可执行文件""" + search_paths = [] + + if sys.platform == "win32": + search_paths = [ + self.build_dir / "bin" / self.build_type / "oneflow_cfd.exe", + self.build_dir / self.build_type / "oneflow_cfd.exe", + ] + else: + search_paths = [ + self.build_dir / "oneflow_cfd", + self.build_dir / "bin" / "oneflow_cfd", + ] + + for path in search_paths: + if path.exists(): + self.executable = path + self.bin_dir = path.parent + print(f"✓ Found executable: {self.executable}") + return + + print("⚠ Could not find executable automatically") + print(" Searched in:", [str(p) for p in search_paths]) + + def run_simulation(self): + """运行CFD模拟""" + self.print_step(5, "Running CFD Simulation") + + if not self.executable or not self.executable.exists(): + print("✗ Executable not found!") + return False + + # 确保postprocess.py在运行目录 + postprocess_src = self.project_root / "postprocess.py" + if postprocess_src.exists(): + shutil.copy2(postprocess_src, self.bin_dir / "postprocess.py") + + # 运行可执行文件 + print(f"Running: {self.executable.name}") + print("-" * 50) + + try: + original_dir = os.getcwd() + os.chdir(self.bin_dir) + + result = subprocess.run([str(self.executable)], + check=True, + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + env=self.combined_env, + shell=True) + + # 打印输出 + if result.stdout: + lines = result.stdout.strip().split('\n') + for line in lines: + if any(keyword in line.lower() for keyword in + ['error', 'warning', 'failed', 'success']): + if 'error' in line.lower() or 'failed' in line.lower(): + print(f"✗ {line}") + elif 'warning' in line.lower(): + print(f"⚠ {line}") + else: + print(f"✓ {line}") + else: + print(f" {line}") + + if result.stderr: + print("\nStderr output:") + print(result.stderr) + + print("-" * 50) + print("✓ Simulation completed") + + os.chdir(original_dir) + return True + + except subprocess.CalledProcessError as e: + print(f"✗ Simulation failed with exit code {e.returncode}") + if e.stdout: + print("Output:", e.stdout) + os.chdir(original_dir) + return False + + def generate_visualization(self): + """生成可视化结果""" + self.print_step(6, "Generating Visualization") + + original_dir = os.getcwd() + os.chdir(self.bin_dir) + + postprocess_script = self.bin_dir / "postprocess.py" + if not postprocess_script.exists(): + print("✗ postprocess.py not found in output directory") + os.chdir(original_dir) + return False + + print(f"Running: python {postprocess_script.name}") + + try: + result = subprocess.run([sys.executable, str(postprocess_script)], + check=True, + capture_output=True, + text=True, + encoding='utf-8', # 新增:指定utf-8编码 + errors='ignore', # 新增:忽略无效字符 + env=self.combined_env, + shell=True) + + if result.stdout and self.verbose: + print(result.stdout) + + print("✓ Visualization generated") + os.chdir(original_dir) + return True + + except subprocess.CalledProcessError as e: + print(f"✗ Visualization failed: {e}") + os.chdir(original_dir) + return False + + def organize_results(self): + """整理结果文件""" + self.print_step(7, "Organizing Results") + + # 创建结果目录 + self.results_dir.mkdir(exist_ok=True) + + # 复制结果文件 + result_patterns = ["*results.txt", "comparison.png", "*.dat"] + + for pattern in result_patterns: + for result_file in self.bin_dir.glob(pattern): + if result_file.is_file(): + dest = self.results_dir / result_file.name + # 避免同名文件覆盖提示,直接复制 + shutil.copy2(result_file, dest) + print(f"✓ Copied: {result_file.name}") + + # 创建结果摘要 + self._create_summary() + + print(f"\n✓ Results organized in: {self.results_dir}") + + def _create_summary(self): + """创建结果摘要""" + summary_file = self.results_dir / "simulation_summary.txt" + + with open(summary_file, 'w', encoding='utf-8') as f: # 指定utf-8写入,避免中文乱码 + f.write("OneFLOW-CFD Simulation Summary\n") + f.write("=" * 50 + "\n") + f.write(f"Date: {time.strftime('%Y-%m-%d %H:%M:%S')}\n") + f.write(f"Build Type: {self.build_type}\n") + f.write(f"Platform: {sys.platform}\n") + f.write("\nFiles Generated:\n") + + for file in sorted(self.results_dir.glob("*")): + if file.is_file(): + size_kb = file.stat().st_size / 1024 + f.write(f" - {file.name:20} {size_kb:6.1f} KB\n") + + def run_complete_pipeline(self): + """运行完整管道""" + self.print_banner() + + steps = [ + self.setup_intel_environment, + self.check_environment, + self.configure_cmake, + self.build_project, + self.run_simulation, + self.generate_visualization, + self.organize_results, + ] + + for i, step in enumerate(steps, 1): + try: + step() + except Exception as e: + print(f"\n✗ Step {i} failed: {e}") + print("\nDebug information:") + print(f" Project root: {self.project_root}") + print(f" Build dir: {self.build_dir}") + print(f" Executable: {self.executable}") + sys.exit(1) + + self._print_completion_message() + + def _print_completion_message(self): + """打印完成信息""" + print("\n" + "="*70) + print(" OneFLOW-CFD Automation Complete!") + print("="*70) + print("\nGenerated Files:") + print(" Results Directory: results/") + + for file in sorted(self.results_dir.glob("*")): + if file.is_file(): + print(f" - {file.name}") + + print("\nTo view the plot:") + plot_file = self.results_dir / "comparison.png" + if plot_file.exists(): + if sys.platform == "win32": + print(f" start results/comparison.png") + elif sys.platform == "darwin": + print(f" open results/comparison.png") + else: + print(f" xdg-open results/comparison.png") + + print("\n" + "="*70) + +def main(): + """主函数""" + parser = argparse.ArgumentParser( + description="OneFLOW-CFD Automation System", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + %(prog)s # Run complete pipeline + %(prog)s --verbose # Verbose output + %(prog)s --build Debug # Debug build + %(prog)s --clean # Clean before building + """ + ) + + parser.add_argument("--build", choices=["Release", "Debug", "RelWithDebInfo"], + default="Release", help="Build type") + parser.add_argument("--verbose", "-v", action="store_true", + help="Verbose output") + parser.add_argument("--clean", "-c", action="store_true", + help="Clean build directory before building") + parser.add_argument("--no-vis", action="store_true", + help="Skip visualization") + parser.add_argument("--no-run", action="store_true", + help="Skip running simulation") + + args = parser.parse_args() + + # 创建自动化实例 + automator = CFDAutomation(build_type=args.build, verbose=args.verbose) + + # 清理选项 + if args.clean and automator.build_dir.exists(): + print("Cleaning build directory...") + shutil.rmtree(automator.build_dir) + + # 运行管道 + try: + automator.run_complete_pipeline() + except KeyboardInterrupt: + print("\n\n⚠ Process interrupted by user") + sys.exit(130) + except Exception as e: + print(f"\n✗ Automation failed: {e}") + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/01/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/01/CMakeLists.txt new file mode 100644 index 000000000..2de8232fd --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/01/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.20) + +project ( testprj ) + +set ( PRJ_COMPILE_FEATURES ) +set ( PRJ_COMPILE_DEFINITIONS ) + +enable_language(Fortran) + +list ( APPEND PRJ_COMPILE_FEATURES cxx_std_20 ) + +add_executable( ${PROJECT_NAME} + main.f90 +) + +target_compile_features ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/01/main.f90 b/example/1d-linear-convection/weno3/fortran/grammar/01/main.f90 new file mode 100644 index 000000000..2089046da --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/01/main.f90 @@ -0,0 +1,3 @@ +program main +write(*,*) "haha" +end program main \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/01a/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/01a/CMakeLists.txt new file mode 100644 index 000000000..2de8232fd --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/01a/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.20) + +project ( testprj ) + +set ( PRJ_COMPILE_FEATURES ) +set ( PRJ_COMPILE_DEFINITIONS ) + +enable_language(Fortran) + +list ( APPEND PRJ_COMPILE_FEATURES cxx_std_20 ) + +add_executable( ${PROJECT_NAME} + main.f90 +) + +target_compile_features ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/01a/main.f90 b/example/1d-linear-convection/weno3/fortran/grammar/01a/main.f90 new file mode 100644 index 000000000..721966963 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/01a/main.f90 @@ -0,0 +1,22 @@ +program main_prog +implicit none + + call sub1 + call sub2 + +end program main_prog + + +subroutine sub1() +implicit none + + print*,"haha1" + +end subroutine sub1 + +subroutine sub2() +implicit none + + print*,"haha2" + +end subroutine sub2 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/01b/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/01b/CMakeLists.txt new file mode 100644 index 000000000..3dad6fe21 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/01b/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.20) + +project ( testprj ) + +set ( PRJ_COMPILE_FEATURES ) +set ( PRJ_COMPILE_DEFINITIONS ) + +enable_language(Fortran) + +list ( APPEND PRJ_COMPILE_FEATURES cxx_std_20 ) + +add_executable( ${PROJECT_NAME} + main.f90 + sub1.f90 + sub2.f90 +) + +target_compile_features ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/01b/main.f90 b/example/1d-linear-convection/weno3/fortran/grammar/01b/main.f90 new file mode 100644 index 000000000..77996d47d --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/01b/main.f90 @@ -0,0 +1,7 @@ +program main_prog +implicit none + + call sub1 + call sub2 + +end program main_prog \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/01b/sub1.f90 b/example/1d-linear-convection/weno3/fortran/grammar/01b/sub1.f90 new file mode 100644 index 000000000..d5f0f7048 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/01b/sub1.f90 @@ -0,0 +1,6 @@ +subroutine sub1() +implicit none + + print*,"haha1" + +end subroutine sub1 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/01b/sub2.f90 b/example/1d-linear-convection/weno3/fortran/grammar/01b/sub2.f90 new file mode 100644 index 000000000..a8fd6f8a5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/01b/sub2.f90 @@ -0,0 +1,6 @@ +subroutine sub2() +implicit none + + print*,"haha2" + +end subroutine sub2 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/01c/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/01c/CMakeLists.txt new file mode 100644 index 000000000..2de8232fd --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/01c/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.20) + +project ( testprj ) + +set ( PRJ_COMPILE_FEATURES ) +set ( PRJ_COMPILE_DEFINITIONS ) + +enable_language(Fortran) + +list ( APPEND PRJ_COMPILE_FEATURES cxx_std_20 ) + +add_executable( ${PROJECT_NAME} + main.f90 +) + +target_compile_features ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/01c/main.f90 b/example/1d-linear-convection/weno3/fortran/grammar/01c/main.f90 new file mode 100644 index 000000000..5d6281479 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/01c/main.f90 @@ -0,0 +1,33 @@ +module mymod1 +implicit none + + integer, parameter :: N = 1024 + +contains + subroutine show_N() + print*, "N = ", N + end subroutine show_N + +end module mymod1 + +module mymod2 +implicit none + + integer, parameter :: M = 256 + +contains + subroutine show_M() + print*, "M = ", M + end subroutine show_M + +end module mymod2 + +program main_prog +use mymod1 +use mymod2 +implicit none + + call show_N() + call show_M() + +end program main_prog \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/01d/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/01d/CMakeLists.txt new file mode 100644 index 000000000..f07960000 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/01d/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.20) + +project ( testprj ) + +set ( PRJ_COMPILE_FEATURES ) +set ( PRJ_COMPILE_DEFINITIONS ) + +enable_language(Fortran) + +list ( APPEND PRJ_COMPILE_FEATURES cxx_std_20 ) + +add_executable( ${PROJECT_NAME} + main.f90 + mymod1.f90 + mymod2.f90 +) + +target_compile_features ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/01d/main.f90 b/example/1d-linear-convection/weno3/fortran/grammar/01d/main.f90 new file mode 100644 index 000000000..5d9d514a0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/01d/main.f90 @@ -0,0 +1,9 @@ +program main_prog +use mymod1 +use mymod2 +implicit none + + call show_N() + call show_M() + +end program main_prog \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/01d/mymod1.f90 b/example/1d-linear-convection/weno3/fortran/grammar/01d/mymod1.f90 new file mode 100644 index 000000000..49f99c96a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/01d/mymod1.f90 @@ -0,0 +1,11 @@ +module mymod1 +implicit none + + integer, parameter :: N = 1024 + +contains + subroutine show_N() + print*, "N = ", N + end subroutine show_N + +end module mymod1 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/01d/mymod2.f90 b/example/1d-linear-convection/weno3/fortran/grammar/01d/mymod2.f90 new file mode 100644 index 000000000..b265eeb9d --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/01d/mymod2.f90 @@ -0,0 +1,11 @@ +module mymod2 +implicit none + + integer, parameter :: M = 256 + +contains + subroutine show_M() + print*, "M = ", M + end subroutine show_M + +end module mymod2 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/01e/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/01e/CMakeLists.txt new file mode 100644 index 000000000..b96ef5bbf --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/01e/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.20) + +project ( testprj ) + +set ( PRJ_COMPILE_FEATURES ) +set ( PRJ_COMPILE_DEFINITIONS ) + +enable_language(Fortran) + +list ( APPEND PRJ_COMPILE_FEATURES cxx_std_20 ) + +add_executable( ${PROJECT_NAME} + main.f90 + src/mymod1.f90 + src/mymod2.f90 +) + +target_compile_features ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/01e/main.f90 b/example/1d-linear-convection/weno3/fortran/grammar/01e/main.f90 new file mode 100644 index 000000000..5d9d514a0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/01e/main.f90 @@ -0,0 +1,9 @@ +program main_prog +use mymod1 +use mymod2 +implicit none + + call show_N() + call show_M() + +end program main_prog \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/01e/src/mymod1.f90 b/example/1d-linear-convection/weno3/fortran/grammar/01e/src/mymod1.f90 new file mode 100644 index 000000000..49f99c96a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/01e/src/mymod1.f90 @@ -0,0 +1,11 @@ +module mymod1 +implicit none + + integer, parameter :: N = 1024 + +contains + subroutine show_N() + print*, "N = ", N + end subroutine show_N + +end module mymod1 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/01e/src/mymod2.f90 b/example/1d-linear-convection/weno3/fortran/grammar/01e/src/mymod2.f90 new file mode 100644 index 000000000..b265eeb9d --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/01e/src/mymod2.f90 @@ -0,0 +1,11 @@ +module mymod2 +implicit none + + integer, parameter :: M = 256 + +contains + subroutine show_M() + print*, "M = ", M + end subroutine show_M + +end module mymod2 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/01f/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/01f/CMakeLists.txt new file mode 100644 index 000000000..b96ef5bbf --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/01f/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.20) + +project ( testprj ) + +set ( PRJ_COMPILE_FEATURES ) +set ( PRJ_COMPILE_DEFINITIONS ) + +enable_language(Fortran) + +list ( APPEND PRJ_COMPILE_FEATURES cxx_std_20 ) + +add_executable( ${PROJECT_NAME} + main.f90 + src/mymod1.f90 + src/mymod2.f90 +) + +target_compile_features ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/01f/main.f90 b/example/1d-linear-convection/weno3/fortran/grammar/01f/main.f90 new file mode 100644 index 000000000..5d9d514a0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/01f/main.f90 @@ -0,0 +1,9 @@ +program main_prog +use mymod1 +use mymod2 +implicit none + + call show_N() + call show_M() + +end program main_prog \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/01f/src/mymod1.f90 b/example/1d-linear-convection/weno3/fortran/grammar/01f/src/mymod1.f90 new file mode 100644 index 000000000..49f99c96a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/01f/src/mymod1.f90 @@ -0,0 +1,11 @@ +module mymod1 +implicit none + + integer, parameter :: N = 1024 + +contains + subroutine show_N() + print*, "N = ", N + end subroutine show_N + +end module mymod1 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/01f/src/mymod2.f90 b/example/1d-linear-convection/weno3/fortran/grammar/01f/src/mymod2.f90 new file mode 100644 index 000000000..b265eeb9d --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/01f/src/mymod2.f90 @@ -0,0 +1,11 @@ +module mymod2 +implicit none + + integer, parameter :: M = 256 + +contains + subroutine show_M() + print*, "M = ", M + end subroutine show_M + +end module mymod2 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/02/CMakeLists.txt new file mode 100644 index 000000000..bec06c79f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.20) + +project ( testprj ) + +set ( PRJ_COMPILE_FEATURES ) +set ( PRJ_COMPILE_DEFINITIONS ) + +enable_language(Fortran) + +list ( APPEND PRJ_COMPILE_FEATURES cxx_std_20 ) + +add_executable( ${PROJECT_NAME} + main.f90 + src/sub1.f90 + src/sub2.f90 +) + +target_compile_features ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02/main.f90 b/example/1d-linear-convection/weno3/fortran/grammar/02/main.f90 new file mode 100644 index 000000000..77996d47d --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02/main.f90 @@ -0,0 +1,7 @@ +program main_prog +implicit none + + call sub1 + call sub2 + +end program main_prog \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02/src/sub1.f90 b/example/1d-linear-convection/weno3/fortran/grammar/02/src/sub1.f90 new file mode 100644 index 000000000..d5f0f7048 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02/src/sub1.f90 @@ -0,0 +1,6 @@ +subroutine sub1() +implicit none + + print*,"haha1" + +end subroutine sub1 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02/src/sub2.f90 b/example/1d-linear-convection/weno3/fortran/grammar/02/src/sub2.f90 new file mode 100644 index 000000000..a8fd6f8a5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02/src/sub2.f90 @@ -0,0 +1,6 @@ +subroutine sub2() +implicit none + + print*,"haha2" + +end subroutine sub2 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02a/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/02a/CMakeLists.txt new file mode 100644 index 000000000..45cabcfbf --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02a/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.20) + +project ( testprj ) + +set ( PRJ_COMPILE_FEATURES ) +set ( PRJ_COMPILE_DEFINITIONS ) + +enable_language(Fortran) + +list ( APPEND PRJ_COMPILE_FEATURES cxx_std_20 ) + +add_executable( ${PROJECT_NAME} + test/main.f90 + src/sub1.f90 + src/sub2.f90 +) + +target_compile_features ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02a/src/sub1.f90 b/example/1d-linear-convection/weno3/fortran/grammar/02a/src/sub1.f90 new file mode 100644 index 000000000..d5f0f7048 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02a/src/sub1.f90 @@ -0,0 +1,6 @@ +subroutine sub1() +implicit none + + print*,"haha1" + +end subroutine sub1 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02a/src/sub2.f90 b/example/1d-linear-convection/weno3/fortran/grammar/02a/src/sub2.f90 new file mode 100644 index 000000000..a8fd6f8a5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02a/src/sub2.f90 @@ -0,0 +1,6 @@ +subroutine sub2() +implicit none + + print*,"haha2" + +end subroutine sub2 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02a/test/main.f90 b/example/1d-linear-convection/weno3/fortran/grammar/02a/test/main.f90 new file mode 100644 index 000000000..77996d47d --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02a/test/main.f90 @@ -0,0 +1,7 @@ +program main_prog +implicit none + + call sub1 + call sub2 + +end program main_prog \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02b/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/02b/CMakeLists.txt new file mode 100644 index 000000000..a7a946bfc --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02b/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.20) + +project ( testprj ) + +set ( PRJ_COMPILE_FEATURES ) +set ( PRJ_COMPILE_DEFINITIONS ) + +enable_language(Fortran) + +list ( APPEND PRJ_COMPILE_FEATURES cxx_std_20 ) + +#add_subdirectory(src) +add_subdirectory(tests) + + +#target_compile_features ( ${PROJECT_NAME} +# PRIVATE +# ${PRJ_COMPILE_FEATURES} +#) + +#target_compile_definitions ( ${PROJECT_NAME} +# PRIVATE +# ${PRJ_COMPILE_DEFINITIONS} +#) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02b/src/sub1.f90 b/example/1d-linear-convection/weno3/fortran/grammar/02b/src/sub1.f90 new file mode 100644 index 000000000..d5f0f7048 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02b/src/sub1.f90 @@ -0,0 +1,6 @@ +subroutine sub1() +implicit none + + print*,"haha1" + +end subroutine sub1 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02b/src/sub2.f90 b/example/1d-linear-convection/weno3/fortran/grammar/02b/src/sub2.f90 new file mode 100644 index 000000000..a8fd6f8a5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02b/src/sub2.f90 @@ -0,0 +1,6 @@ +subroutine sub2() +implicit none + + print*,"haha2" + +end subroutine sub2 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02b/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/02b/tests/CMakeLists.txt new file mode 100644 index 000000000..47caf1738 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02b/tests/CMakeLists.txt @@ -0,0 +1,16 @@ + +add_executable( ${PROJECT_NAME} + main.f90 + ../src/sub1.f90 + ../src/sub2.f90 +) + +target_compile_features ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02b/tests/main.f90 b/example/1d-linear-convection/weno3/fortran/grammar/02b/tests/main.f90 new file mode 100644 index 000000000..77996d47d --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02b/tests/main.f90 @@ -0,0 +1,7 @@ +program main_prog +implicit none + + call sub1 + call sub2 + +end program main_prog \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02c/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/02c/CMakeLists.txt new file mode 100644 index 000000000..af747beb5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02c/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.20) + +project ( testprj ) + +set ( PRJ_COMPILE_FEATURES ) +set ( PRJ_COMPILE_DEFINITIONS ) + +enable_language(Fortran) + +list ( APPEND PRJ_COMPILE_FEATURES cxx_std_20 ) + +add_subdirectory(tests) +add_subdirectory(test1) + + diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02c/src/sub1.f90 b/example/1d-linear-convection/weno3/fortran/grammar/02c/src/sub1.f90 new file mode 100644 index 000000000..d5f0f7048 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02c/src/sub1.f90 @@ -0,0 +1,6 @@ +subroutine sub1() +implicit none + + print*,"haha1" + +end subroutine sub1 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02c/src/sub2.f90 b/example/1d-linear-convection/weno3/fortran/grammar/02c/src/sub2.f90 new file mode 100644 index 000000000..a8fd6f8a5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02c/src/sub2.f90 @@ -0,0 +1,6 @@ +subroutine sub2() +implicit none + + print*,"haha2" + +end subroutine sub2 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02c/test1/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/02c/test1/CMakeLists.txt new file mode 100644 index 000000000..c3f2b959e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02c/test1/CMakeLists.txt @@ -0,0 +1,16 @@ + +add_executable( main1 + main1.f90 + ../src/sub1.f90 + ../src/sub2.f90 +) + +target_compile_features ( main1 + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( main1 + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02c/test1/main1.f90 b/example/1d-linear-convection/weno3/fortran/grammar/02c/test1/main1.f90 new file mode 100644 index 000000000..44866f7e3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02c/test1/main1.f90 @@ -0,0 +1,8 @@ +program main_prog1 +implicit none + + call sub1 + call sub2 + print*,"hahaha" + +end program main_prog1 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02c/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/02c/tests/CMakeLists.txt new file mode 100644 index 000000000..47caf1738 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02c/tests/CMakeLists.txt @@ -0,0 +1,16 @@ + +add_executable( ${PROJECT_NAME} + main.f90 + ../src/sub1.f90 + ../src/sub2.f90 +) + +target_compile_features ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02c/tests/main.f90 b/example/1d-linear-convection/weno3/fortran/grammar/02c/tests/main.f90 new file mode 100644 index 000000000..77996d47d --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02c/tests/main.f90 @@ -0,0 +1,7 @@ +program main_prog +implicit none + + call sub1 + call sub2 + +end program main_prog \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02d/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/02d/CMakeLists.txt new file mode 100644 index 000000000..9e8feea46 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02d/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.20) + +project ( testprj ) + +set ( PRJ_COMPILE_FEATURES ) +set ( PRJ_COMPILE_DEFINITIONS ) + +enable_language(Fortran) + +list ( APPEND PRJ_COMPILE_FEATURES cxx_std_20 ) + +add_subdirectory(src) +add_subdirectory(tests) + + + diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02d/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/02d/src/CMakeLists.txt new file mode 100644 index 000000000..86dc91bd9 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02d/src/CMakeLists.txt @@ -0,0 +1,18 @@ + +set(mylibname "mylib" ) + +add_library(${mylibname} STATIC + sub1.f90 + sub2.f90 +) + + +target_compile_features ( ${mylibname} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${mylibname} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02d/src/sub1.f90 b/example/1d-linear-convection/weno3/fortran/grammar/02d/src/sub1.f90 new file mode 100644 index 000000000..d5f0f7048 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02d/src/sub1.f90 @@ -0,0 +1,6 @@ +subroutine sub1() +implicit none + + print*,"haha1" + +end subroutine sub1 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02d/src/sub2.f90 b/example/1d-linear-convection/weno3/fortran/grammar/02d/src/sub2.f90 new file mode 100644 index 000000000..a8fd6f8a5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02d/src/sub2.f90 @@ -0,0 +1,6 @@ +subroutine sub2() +implicit none + + print*,"haha2" + +end subroutine sub2 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02d/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/02d/tests/CMakeLists.txt new file mode 100644 index 000000000..16e893092 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02d/tests/CMakeLists.txt @@ -0,0 +1,23 @@ + +message(STATUS "CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR}") +message(STATUS "CMAKE_BINARY_DIR/src/Debug/mylib.lib=${CMAKE_BINARY_DIR}/src/Debug/mylib.lib") + + +add_executable( ${PROJECT_NAME} + main.f90 +) + +target_compile_features ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) + +target_link_libraries( ${PROJECT_NAME} + PRIVATE + ${CMAKE_BINARY_DIR}/src/Debug/mylib.lib +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02d/tests/main.f90 b/example/1d-linear-convection/weno3/fortran/grammar/02d/tests/main.f90 new file mode 100644 index 000000000..77996d47d --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02d/tests/main.f90 @@ -0,0 +1,7 @@ +program main_prog +implicit none + + call sub1 + call sub2 + +end program main_prog \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02e/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/02e/CMakeLists.txt new file mode 100644 index 000000000..9e8feea46 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02e/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.20) + +project ( testprj ) + +set ( PRJ_COMPILE_FEATURES ) +set ( PRJ_COMPILE_DEFINITIONS ) + +enable_language(Fortran) + +list ( APPEND PRJ_COMPILE_FEATURES cxx_std_20 ) + +add_subdirectory(src) +add_subdirectory(tests) + + + diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02e/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/02e/src/CMakeLists.txt new file mode 100644 index 000000000..86dc91bd9 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02e/src/CMakeLists.txt @@ -0,0 +1,18 @@ + +set(mylibname "mylib" ) + +add_library(${mylibname} STATIC + sub1.f90 + sub2.f90 +) + + +target_compile_features ( ${mylibname} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${mylibname} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02e/src/sub1.f90 b/example/1d-linear-convection/weno3/fortran/grammar/02e/src/sub1.f90 new file mode 100644 index 000000000..d5f0f7048 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02e/src/sub1.f90 @@ -0,0 +1,6 @@ +subroutine sub1() +implicit none + + print*,"haha1" + +end subroutine sub1 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02e/src/sub2.f90 b/example/1d-linear-convection/weno3/fortran/grammar/02e/src/sub2.f90 new file mode 100644 index 000000000..a8fd6f8a5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02e/src/sub2.f90 @@ -0,0 +1,6 @@ +subroutine sub2() +implicit none + + print*,"haha2" + +end subroutine sub2 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02e/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/02e/tests/CMakeLists.txt new file mode 100644 index 000000000..516436144 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02e/tests/CMakeLists.txt @@ -0,0 +1,19 @@ + +add_executable( ${PROJECT_NAME} + main.f90 +) + +target_compile_features ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) + +target_link_libraries( ${PROJECT_NAME} + PRIVATE + mylib +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02e/tests/main.f90 b/example/1d-linear-convection/weno3/fortran/grammar/02e/tests/main.f90 new file mode 100644 index 000000000..77996d47d --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02e/tests/main.f90 @@ -0,0 +1,7 @@ +program main_prog +implicit none + + call sub1 + call sub2 + +end program main_prog \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02f/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/02f/CMakeLists.txt new file mode 100644 index 000000000..4bab2a8d3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02f/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.20) + +project ( testprj ) + +set ( PRJ_COMPILE_FEATURES ) +set ( PRJ_COMPILE_DEFINITIONS ) + +enable_language(Fortran) + +list ( APPEND PRJ_COMPILE_FEATURES cxx_std_20 ) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) + + + diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02f/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/02f/src/CMakeLists.txt new file mode 100644 index 000000000..86dc91bd9 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02f/src/CMakeLists.txt @@ -0,0 +1,18 @@ + +set(mylibname "mylib" ) + +add_library(${mylibname} STATIC + sub1.f90 + sub2.f90 +) + + +target_compile_features ( ${mylibname} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${mylibname} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02f/src/sub1.f90 b/example/1d-linear-convection/weno3/fortran/grammar/02f/src/sub1.f90 new file mode 100644 index 000000000..d5f0f7048 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02f/src/sub1.f90 @@ -0,0 +1,6 @@ +subroutine sub1() +implicit none + + print*,"haha1" + +end subroutine sub1 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02f/src/sub2.f90 b/example/1d-linear-convection/weno3/fortran/grammar/02f/src/sub2.f90 new file mode 100644 index 000000000..a8fd6f8a5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02f/src/sub2.f90 @@ -0,0 +1,6 @@ +subroutine sub2() +implicit none + + print*,"haha2" + +end subroutine sub2 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02f/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/02f/tests/CMakeLists.txt new file mode 100644 index 000000000..516436144 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02f/tests/CMakeLists.txt @@ -0,0 +1,19 @@ + +add_executable( ${PROJECT_NAME} + main.f90 +) + +target_compile_features ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) + +target_link_libraries( ${PROJECT_NAME} + PRIVATE + mylib +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02f/tests/main.f90 b/example/1d-linear-convection/weno3/fortran/grammar/02f/tests/main.f90 new file mode 100644 index 000000000..77996d47d --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02f/tests/main.f90 @@ -0,0 +1,7 @@ +program main_prog +implicit none + + call sub1 + call sub2 + +end program main_prog \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02g/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/02g/CMakeLists.txt new file mode 100644 index 000000000..44d1f7307 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02g/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.20) + +project ( testprj ) + +set ( PRJ_COMPILE_FEATURES ) +set ( PRJ_COMPILE_DEFINITIONS ) + +enable_language(Fortran) + +list ( APPEND PRJ_COMPILE_FEATURES cxx_std_20 ) + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) + + + diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02g/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/02g/src/CMakeLists.txt new file mode 100644 index 000000000..86dc91bd9 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02g/src/CMakeLists.txt @@ -0,0 +1,18 @@ + +set(mylibname "mylib" ) + +add_library(${mylibname} STATIC + sub1.f90 + sub2.f90 +) + + +target_compile_features ( ${mylibname} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${mylibname} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02g/src/sub1.f90 b/example/1d-linear-convection/weno3/fortran/grammar/02g/src/sub1.f90 new file mode 100644 index 000000000..d5f0f7048 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02g/src/sub1.f90 @@ -0,0 +1,6 @@ +subroutine sub1() +implicit none + + print*,"haha1" + +end subroutine sub1 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02g/src/sub2.f90 b/example/1d-linear-convection/weno3/fortran/grammar/02g/src/sub2.f90 new file mode 100644 index 000000000..a8fd6f8a5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02g/src/sub2.f90 @@ -0,0 +1,6 @@ +subroutine sub2() +implicit none + + print*,"haha2" + +end subroutine sub2 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02g/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/02g/tests/CMakeLists.txt new file mode 100644 index 000000000..3f951d2c2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02g/tests/CMakeLists.txt @@ -0,0 +1,24 @@ + +add_executable( ${PROJECT_NAME} + main.f90 +) + +target_include_directories( ${PROJECT_NAME} + PRIVATE + ${CMAKE_BINARY_DIR}/modules +) + +target_compile_features ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) + +target_link_libraries( ${PROJECT_NAME} + PRIVATE + mylib +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02g/tests/main.f90 b/example/1d-linear-convection/weno3/fortran/grammar/02g/tests/main.f90 new file mode 100644 index 000000000..77996d47d --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02g/tests/main.f90 @@ -0,0 +1,7 @@ +program main_prog +implicit none + + call sub1 + call sub2 + +end program main_prog \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02h/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/02h/CMakeLists.txt new file mode 100644 index 000000000..44d1f7307 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02h/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.20) + +project ( testprj ) + +set ( PRJ_COMPILE_FEATURES ) +set ( PRJ_COMPILE_DEFINITIONS ) + +enable_language(Fortran) + +list ( APPEND PRJ_COMPILE_FEATURES cxx_std_20 ) + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) + + + diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02h/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/02h/src/CMakeLists.txt new file mode 100644 index 000000000..e55436375 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02h/src/CMakeLists.txt @@ -0,0 +1,22 @@ + +set(mylibname "mylib" ) + +add_library(${mylibname} STATIC + sub1.f90 + sub2.f90 +) + +target_include_directories( ${mylibname} + PRIVATE + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +target_compile_features ( ${mylibname} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${mylibname} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02h/src/sub1.f90 b/example/1d-linear-convection/weno3/fortran/grammar/02h/src/sub1.f90 new file mode 100644 index 000000000..d5f0f7048 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02h/src/sub1.f90 @@ -0,0 +1,6 @@ +subroutine sub1() +implicit none + + print*,"haha1" + +end subroutine sub1 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02h/src/sub2.f90 b/example/1d-linear-convection/weno3/fortran/grammar/02h/src/sub2.f90 new file mode 100644 index 000000000..a8fd6f8a5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02h/src/sub2.f90 @@ -0,0 +1,6 @@ +subroutine sub2() +implicit none + + print*,"haha2" + +end subroutine sub2 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02h/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/02h/tests/CMakeLists.txt new file mode 100644 index 000000000..0e4eaabf0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02h/tests/CMakeLists.txt @@ -0,0 +1,24 @@ + +add_executable( ${PROJECT_NAME} + main.f90 +) + +target_include_directories( ${PROJECT_NAME} + PRIVATE + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +target_compile_features ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) + +target_link_libraries( ${PROJECT_NAME} + PRIVATE + mylib +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/02h/tests/main.f90 b/example/1d-linear-convection/weno3/fortran/grammar/02h/tests/main.f90 new file mode 100644 index 000000000..77996d47d --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/02h/tests/main.f90 @@ -0,0 +1,7 @@ +program main_prog +implicit none + + call sub1 + call sub2 + +end program main_prog \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/03/CMakeLists.txt new file mode 100644 index 000000000..39f49ea93 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 3.20) + +project ( testprj ) + +set ( PRJ_COMPILE_FEATURES ) +set ( PRJ_COMPILE_DEFINITIONS ) + +enable_language(Fortran) + +list ( APPEND PRJ_COMPILE_FEATURES cxx_std_20 ) + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_executable( ${PROJECT_NAME} + main.f90 +) + +target_include_directories( ${PROJECT_NAME} + PRIVATE + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +target_compile_features ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) + + + + diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03/main.f90 b/example/1d-linear-convection/weno3/fortran/grammar/03/main.f90 new file mode 100644 index 000000000..5d6281479 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03/main.f90 @@ -0,0 +1,33 @@ +module mymod1 +implicit none + + integer, parameter :: N = 1024 + +contains + subroutine show_N() + print*, "N = ", N + end subroutine show_N + +end module mymod1 + +module mymod2 +implicit none + + integer, parameter :: M = 256 + +contains + subroutine show_M() + print*, "M = ", M + end subroutine show_M + +end module mymod2 + +program main_prog +use mymod1 +use mymod2 +implicit none + + call show_N() + call show_M() + +end program main_prog \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03a/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/03a/CMakeLists.txt new file mode 100644 index 000000000..687388f8d --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03a/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.20) + +project ( testprj ) + +set ( PRJ_COMPILE_FEATURES ) +set ( PRJ_COMPILE_DEFINITIONS ) + +enable_language(Fortran) + +list ( APPEND PRJ_COMPILE_FEATURES cxx_std_20 ) + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_executable( ${PROJECT_NAME} + main.f90 + mymod1.f90 + mymod2.f90 +) + +target_include_directories( ${PROJECT_NAME} + PRIVATE + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +target_compile_features ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) + + + + diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03a/main.f90 b/example/1d-linear-convection/weno3/fortran/grammar/03a/main.f90 new file mode 100644 index 000000000..5d9d514a0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03a/main.f90 @@ -0,0 +1,9 @@ +program main_prog +use mymod1 +use mymod2 +implicit none + + call show_N() + call show_M() + +end program main_prog \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03a/mymod1.f90 b/example/1d-linear-convection/weno3/fortran/grammar/03a/mymod1.f90 new file mode 100644 index 000000000..49f99c96a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03a/mymod1.f90 @@ -0,0 +1,11 @@ +module mymod1 +implicit none + + integer, parameter :: N = 1024 + +contains + subroutine show_N() + print*, "N = ", N + end subroutine show_N + +end module mymod1 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03a/mymod2.f90 b/example/1d-linear-convection/weno3/fortran/grammar/03a/mymod2.f90 new file mode 100644 index 000000000..b265eeb9d --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03a/mymod2.f90 @@ -0,0 +1,11 @@ +module mymod2 +implicit none + + integer, parameter :: M = 256 + +contains + subroutine show_M() + print*, "M = ", M + end subroutine show_M + +end module mymod2 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03b/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/03b/CMakeLists.txt new file mode 100644 index 000000000..25ba498ce --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03b/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.20) + +project ( testprj ) + +set ( PRJ_COMPILE_FEATURES ) +set ( PRJ_COMPILE_DEFINITIONS ) + +enable_language(Fortran) + +list ( APPEND PRJ_COMPILE_FEATURES cxx_std_20 ) + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_executable( ${PROJECT_NAME} + main.f90 + src/mymod1.f90 + src/mymod2.f90 +) + +target_include_directories( ${PROJECT_NAME} + PRIVATE + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +target_compile_features ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) + + + + diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03b/main.f90 b/example/1d-linear-convection/weno3/fortran/grammar/03b/main.f90 new file mode 100644 index 000000000..5d9d514a0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03b/main.f90 @@ -0,0 +1,9 @@ +program main_prog +use mymod1 +use mymod2 +implicit none + + call show_N() + call show_M() + +end program main_prog \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03b/src/mymod1.f90 b/example/1d-linear-convection/weno3/fortran/grammar/03b/src/mymod1.f90 new file mode 100644 index 000000000..49f99c96a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03b/src/mymod1.f90 @@ -0,0 +1,11 @@ +module mymod1 +implicit none + + integer, parameter :: N = 1024 + +contains + subroutine show_N() + print*, "N = ", N + end subroutine show_N + +end module mymod1 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03b/src/mymod2.f90 b/example/1d-linear-convection/weno3/fortran/grammar/03b/src/mymod2.f90 new file mode 100644 index 000000000..b265eeb9d --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03b/src/mymod2.f90 @@ -0,0 +1,11 @@ +module mymod2 +implicit none + + integer, parameter :: M = 256 + +contains + subroutine show_M() + print*, "M = ", M + end subroutine show_M + +end module mymod2 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03c/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/03c/CMakeLists.txt new file mode 100644 index 000000000..477aa6e4c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03c/CMakeLists.txt @@ -0,0 +1,47 @@ +cmake_minimum_required(VERSION 3.20) + +project ( testprj ) + +set ( PRJ_COMPILE_FEATURES ) +set ( PRJ_COMPILE_DEFINITIONS ) +set ( PRJ_LIBRARIES ) + +enable_language(Fortran) + +list ( APPEND PRJ_COMPILE_FEATURES cxx_std_20 ) +list ( APPEND PRJ_LIBRARIES mylib ) + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) + +add_executable( ${PROJECT_NAME} + main.f90 +) + +target_include_directories( ${PROJECT_NAME} + PRIVATE + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +target_compile_features ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) + +target_link_libraries( ${PROJECT_NAME} + PRIVATE + ${PRJ_LIBRARIES} +) + + + + diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03c/main.f90 b/example/1d-linear-convection/weno3/fortran/grammar/03c/main.f90 new file mode 100644 index 000000000..5d9d514a0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03c/main.f90 @@ -0,0 +1,9 @@ +program main_prog +use mymod1 +use mymod2 +implicit none + + call show_N() + call show_M() + +end program main_prog \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03c/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/03c/src/CMakeLists.txt new file mode 100644 index 000000000..1962a8682 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03c/src/CMakeLists.txt @@ -0,0 +1,22 @@ + +set(mylibname "mylib" ) + +add_library(${mylibname} STATIC + mymod1.f90 + mymod2.f90 +) + +target_include_directories( ${mylibname} + PRIVATE + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +target_compile_features ( ${mylibname} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${mylibname} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03c/src/mymod1.f90 b/example/1d-linear-convection/weno3/fortran/grammar/03c/src/mymod1.f90 new file mode 100644 index 000000000..49f99c96a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03c/src/mymod1.f90 @@ -0,0 +1,11 @@ +module mymod1 +implicit none + + integer, parameter :: N = 1024 + +contains + subroutine show_N() + print*, "N = ", N + end subroutine show_N + +end module mymod1 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03c/src/mymod2.f90 b/example/1d-linear-convection/weno3/fortran/grammar/03c/src/mymod2.f90 new file mode 100644 index 000000000..b265eeb9d --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03c/src/mymod2.f90 @@ -0,0 +1,11 @@ +module mymod2 +implicit none + + integer, parameter :: M = 256 + +contains + subroutine show_M() + print*, "M = ", M + end subroutine show_M + +end module mymod2 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03d/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/03d/CMakeLists.txt new file mode 100644 index 000000000..97bcbb03c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03d/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.20) + +project ( testprj ) + +set ( PRJ_COMPILE_FEATURES ) +set ( PRJ_COMPILE_DEFINITIONS ) +set ( PRJ_LIBRARIES ) + +enable_language(Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +list ( APPEND PRJ_COMPILE_FEATURES cxx_std_20 ) +list ( APPEND PRJ_LIBRARIES mylib ) + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03d/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/03d/src/CMakeLists.txt new file mode 100644 index 000000000..afa331115 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03d/src/CMakeLists.txt @@ -0,0 +1,22 @@ + +set(mylibname "mylib" ) + +add_library(${mylibname} STATIC + mymod1.f90 + mymod2.f90 +) + +#target_include_directories( ${mylibname} +# PRIVATE +# ${CMAKE_Fortran_MODULE_DIRECTORY} +#) + +#target_compile_features ( ${mylibname} +# PRIVATE +# ${PRJ_COMPILE_FEATURES} +#) + +#target_compile_definitions ( ${mylibname} +# PRIVATE +# ${PRJ_COMPILE_DEFINITIONS} +#) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03d/src/mymod1.f90 b/example/1d-linear-convection/weno3/fortran/grammar/03d/src/mymod1.f90 new file mode 100644 index 000000000..49f99c96a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03d/src/mymod1.f90 @@ -0,0 +1,11 @@ +module mymod1 +implicit none + + integer, parameter :: N = 1024 + +contains + subroutine show_N() + print*, "N = ", N + end subroutine show_N + +end module mymod1 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03d/src/mymod2.f90 b/example/1d-linear-convection/weno3/fortran/grammar/03d/src/mymod2.f90 new file mode 100644 index 000000000..b265eeb9d --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03d/src/mymod2.f90 @@ -0,0 +1,11 @@ +module mymod2 +implicit none + + integer, parameter :: M = 256 + +contains + subroutine show_M() + print*, "M = ", M + end subroutine show_M + +end module mymod2 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03d/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/03d/tests/CMakeLists.txt new file mode 100644 index 000000000..7658d2ebb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03d/tests/CMakeLists.txt @@ -0,0 +1,23 @@ +add_executable( ${PROJECT_NAME} + main.f90 +) + +#target_include_directories( ${PROJECT_NAME} +# PRIVATE +# ${CMAKE_Fortran_MODULE_DIRECTORY} +#) + +target_compile_features ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) + +target_link_libraries( ${PROJECT_NAME} + PRIVATE + ${PRJ_LIBRARIES} +) diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03d/tests/main.f90 b/example/1d-linear-convection/weno3/fortran/grammar/03d/tests/main.f90 new file mode 100644 index 000000000..5d9d514a0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03d/tests/main.f90 @@ -0,0 +1,9 @@ +program main_prog +use mymod1 +use mymod2 +implicit none + + call show_N() + call show_M() + +end program main_prog \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03e/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/03e/CMakeLists.txt new file mode 100644 index 000000000..97bcbb03c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03e/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.20) + +project ( testprj ) + +set ( PRJ_COMPILE_FEATURES ) +set ( PRJ_COMPILE_DEFINITIONS ) +set ( PRJ_LIBRARIES ) + +enable_language(Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +list ( APPEND PRJ_COMPILE_FEATURES cxx_std_20 ) +list ( APPEND PRJ_LIBRARIES mylib ) + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03e/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/03e/src/CMakeLists.txt new file mode 100644 index 000000000..1983bbf53 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03e/src/CMakeLists.txt @@ -0,0 +1,21 @@ + +set(mylibname "mylib" ) + +add_library(${mylibname} STATIC + mymath.f90 +) + +#target_include_directories( ${mylibname} +# PRIVATE +# ${CMAKE_Fortran_MODULE_DIRECTORY} +#) + +#target_compile_features ( ${mylibname} +# PRIVATE +# ${PRJ_COMPILE_FEATURES} +#) + +#target_compile_definitions ( ${mylibname} +# PRIVATE +# ${PRJ_COMPILE_DEFINITIONS} +#) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03e/src/mymath.f90 b/example/1d-linear-convection/weno3/fortran/grammar/03e/src/mymath.f90 new file mode 100644 index 000000000..979187cf4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03e/src/mymath.f90 @@ -0,0 +1,10 @@ +module mymath_module + implicit none + integer, parameter :: N = 1024 + +contains + subroutine show_N() + print*, "N = ", N + end subroutine show_N + +end module mymath_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03e/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/03e/tests/CMakeLists.txt new file mode 100644 index 000000000..7658d2ebb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03e/tests/CMakeLists.txt @@ -0,0 +1,23 @@ +add_executable( ${PROJECT_NAME} + main.f90 +) + +#target_include_directories( ${PROJECT_NAME} +# PRIVATE +# ${CMAKE_Fortran_MODULE_DIRECTORY} +#) + +target_compile_features ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) + +target_link_libraries( ${PROJECT_NAME} + PRIVATE + ${PRJ_LIBRARIES} +) diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03e/tests/main.f90 b/example/1d-linear-convection/weno3/fortran/grammar/03e/tests/main.f90 new file mode 100644 index 000000000..ffc5b2b7e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03e/tests/main.f90 @@ -0,0 +1,33 @@ +! 测试程序:调用mymath库的函数,验证功能 +program test_mymath + use iso_fortran_env, only: real64 ! 显式引入,避免模块依赖缺失导致的real64未定义 + use mymath_module + implicit none + real(real64) :: a, b, sum_res, mul_res + + ! 测试数据 + a = 2.5_real64 + b = 4.0_real64 + + ! 调用库函数 + sum_res = add(a, b) + mul_res = multiply(a, b) + + ! 输出结果 + print *, "=== Testing mymath library ===" + print *, "a = ", a + print *, "b = ", b + print *, "a + b = ", sum_res + print *, "a * b = ", mul_res + print *, "=== Test completed ===" + + ! 简单验证(确保结果正确) + if (abs(sum_res - 6.5_real64) < 1e-10_real64 .and. & + abs(mul_res - 10.0_real64) < 1e-10_real64) then + print *, "✓ Test passed!" + else + print *, "✗ Test failed!" + stop 1 + end if + +end program test_mymath \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03f/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/03f/CMakeLists.txt new file mode 100644 index 000000000..97bcbb03c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03f/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.20) + +project ( testprj ) + +set ( PRJ_COMPILE_FEATURES ) +set ( PRJ_COMPILE_DEFINITIONS ) +set ( PRJ_LIBRARIES ) + +enable_language(Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +list ( APPEND PRJ_COMPILE_FEATURES cxx_std_20 ) +list ( APPEND PRJ_LIBRARIES mylib ) + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03f/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/03f/src/CMakeLists.txt new file mode 100644 index 000000000..1983bbf53 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03f/src/CMakeLists.txt @@ -0,0 +1,21 @@ + +set(mylibname "mylib" ) + +add_library(${mylibname} STATIC + mymath.f90 +) + +#target_include_directories( ${mylibname} +# PRIVATE +# ${CMAKE_Fortran_MODULE_DIRECTORY} +#) + +#target_compile_features ( ${mylibname} +# PRIVATE +# ${PRJ_COMPILE_FEATURES} +#) + +#target_compile_definitions ( ${mylibname} +# PRIVATE +# ${PRJ_COMPILE_DEFINITIONS} +#) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03f/src/mymath.f90 b/example/1d-linear-convection/weno3/fortran/grammar/03f/src/mymath.f90 new file mode 100644 index 000000000..11d02043a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03f/src/mymath.f90 @@ -0,0 +1,24 @@ +! 简单的数学库:提供加法和乘法函数 +module mymath_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + private + public :: add, multiply + +contains + + ! 实数加法 + function add(a, b) result(res) + real(real64), intent(in) :: a, b + real(real64) :: res + res = a + b + end function add + + ! 实数乘法 + function multiply(a, b) result(res) + real(real64), intent(in) :: a, b + real(real64) :: res + res = a * b + end function multiply + +end module mymath_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03f/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/grammar/03f/tests/CMakeLists.txt new file mode 100644 index 000000000..7658d2ebb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03f/tests/CMakeLists.txt @@ -0,0 +1,23 @@ +add_executable( ${PROJECT_NAME} + main.f90 +) + +#target_include_directories( ${PROJECT_NAME} +# PRIVATE +# ${CMAKE_Fortran_MODULE_DIRECTORY} +#) + +target_compile_features ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_FEATURES} +) + +target_compile_definitions ( ${PROJECT_NAME} + PRIVATE + ${PRJ_COMPILE_DEFINITIONS} +) + +target_link_libraries( ${PROJECT_NAME} + PRIVATE + ${PRJ_LIBRARIES} +) diff --git a/example/1d-linear-convection/weno3/fortran/grammar/03f/tests/main.f90 b/example/1d-linear-convection/weno3/fortran/grammar/03f/tests/main.f90 new file mode 100644 index 000000000..ffc5b2b7e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/grammar/03f/tests/main.f90 @@ -0,0 +1,33 @@ +! 测试程序:调用mymath库的函数,验证功能 +program test_mymath + use iso_fortran_env, only: real64 ! 显式引入,避免模块依赖缺失导致的real64未定义 + use mymath_module + implicit none + real(real64) :: a, b, sum_res, mul_res + + ! 测试数据 + a = 2.5_real64 + b = 4.0_real64 + + ! 调用库函数 + sum_res = add(a, b) + mul_res = multiply(a, b) + + ! 输出结果 + print *, "=== Testing mymath library ===" + print *, "a = ", a + print *, "b = ", b + print *, "a + b = ", sum_res + print *, "a * b = ", mul_res + print *, "=== Test completed ===" + + ! 简单验证(确保结果正确) + if (abs(sum_res - 6.5_real64) < 1e-10_real64 .and. & + abs(mul_res - 10.0_real64) < 1e-10_real64) then + print *, "✓ Test passed!" + else + print *, "✗ Test failed!" + stop 1 + end if + +end program test_mymath \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01/CMakeLists.txt new file mode 100644 index 000000000..665f48ace --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01/CMakeLists.txt @@ -0,0 +1,26 @@ +# 根目录CMakeLists.txt +#cmake_minimum_required(VERSION 3.12) +cmake_minimum_required(VERSION 4.2.1) +project(FortranRegistry VERSION 1.0.0 LANGUAGES Fortran) + +message(STATUS "CMAKE_Fortran_COMPILER=${CMAKE_Fortran_COMPILER}") +message(STATUS "CMAKE_Fortran_COMPILER_ID=${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "CMAKE_Fortran_COMPILER_VERSION=${CMAKE_Fortran_COMPILER_VERSION}") + +# 设置Fortran标准 +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +# 模块输出目录 +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) + +# 编译器选项 +if(CMAKE_Fortran_COMPILER_ID MATCHES "GNU") + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -Wall -Wextra") + set(CMAKE_Fortran_FLAGS_DEBUG "-g -O0 -fcheck=all -fbacktrace") + set(CMAKE_Fortran_FLAGS_RELEASE "-O3") +endif() + +# 添加子目录 +add_subdirectory(src) +add_subdirectory(tests) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01/src/CMakeLists.txt new file mode 100644 index 000000000..0ddd08d29 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01/src/CMakeLists.txt @@ -0,0 +1,19 @@ +# src/CMakeLists.txt +# 源代码目录的CMake配置 + +message(STATUS "配置源代码目录...") + +# 添加核心模块子目录 +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/core AND + EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/core/CMakeLists.txt) + add_subdirectory(core) +else() + message(WARNING "core目录或CMakeLists.txt不存在") +endif() + +# 可选:添加其他模块子目录 +# if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/infrastructure) +# add_subdirectory(infrastructure) +# endif() + +message(STATUS "源代码目录配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01/src/core/CMakeLists.txt new file mode 100644 index 000000000..9685b6fb4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01/src/core/CMakeLists.txt @@ -0,0 +1,16 @@ +# src/core/CMakeLists.txt +# 创建核心库 +add_library(core + registry.f90 +) + +# 设置模块输出目录 +target_include_directories(core PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 安装规则(可选) +install(TARGETS core + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/01/src/core/registry.f90 new file mode 100644 index 000000000..09aced0be --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01/src/core/registry.f90 @@ -0,0 +1,187 @@ +! src/core/registry.f90 +! Fortran注册系统模块 +module registry_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private ! 默认所有内容都是私有的 + + ! ==================== 1. 公开接口 ==================== + ! 只在这里声明一次哪些是公开的 + public :: wp ! 精度类型 + public :: component_info ! 组件信息类型 + public :: component_registry_type ! 注册表类型 + public :: component_registry ! 全局注册表实例 + public :: register_component ! 注册函数 + + ! ==================== 2. 类型定义 ==================== + + ! 组件信息类型 + type :: component_info + character(len=50) :: category = "" + character(len=50) :: name = "" + contains + procedure :: print => ci_print + end type component_info + + ! 注册表类型 + type :: component_registry_type + private + type(component_info), allocatable :: components(:) + integer :: count = 0 + integer :: capacity = 10 + logical :: verbose = .true. + contains + procedure :: register => cr_register + procedure :: get => cr_get + procedure :: list_all => cr_list_all + procedure :: size => cr_size + procedure :: clear => cr_clear + end type component_registry_type + + ! ==================== 3. 全局实例 ==================== + ! 这里只声明,不指定属性 + type(component_registry_type) :: component_registry + + ! ==================== 4. 接口定义 ==================== + + interface register_component + module procedure register_component_simple + end interface register_component + +contains + + ! ==================== 5. 组件信息方法 ==================== + + subroutine ci_print(this) + class(component_info), intent(in) :: this + print *, " [", trim(this%category), ".", trim(this%name), "]" + end subroutine ci_print + + ! ==================== 6. 注册表核心方法 ==================== + + ! 注册组件(简单版) + subroutine register_component_simple(category, name) + character(len=*), intent(in) :: category, name + call component_registry%register(category, name) + end subroutine register_component_simple + + ! 内部注册实现 + subroutine cr_register(this, category, name) + class(component_registry_type), intent(inout) :: this + character(len=*), intent(in) :: category, name + + type(component_info) :: new_component + type(component_info), allocatable :: temp(:) + integer :: i + + ! 检查是否已存在 + do i = 1, this%count + if (trim(this%components(i)%category) == trim(category) .and. & + trim(this%components(i)%name) == trim(name)) then + if (this%verbose) then + print *, "⚠️ 警告: 覆盖注册 ", trim(category), ".", trim(name) + end if + ! 更新现有组件 + this%components(i)%category = trim(category) + this%components(i)%name = trim(name) + return + end if + end do + + ! 创建新组件 + new_component%category = trim(category) + new_component%name = trim(name) + + ! 初始化数组(如果需要) + if (.not. allocated(this%components)) then + allocate(this%components(this%capacity)) + end if + + ! 扩展数组(如果需要) + if (this%count >= this%capacity) then + this%capacity = this%capacity * 2 + allocate(temp(this%capacity)) + temp(1:this%count) = this%components(1:this%count) + call move_alloc(temp, this%components) + end if + + ! 添加组件 + this%count = this%count + 1 + this%components(this%count) = new_component + + if (this%verbose) then + print *, "✅ 已注册: ", trim(category), ".", trim(name) + end if + end subroutine cr_register + + ! 获取组件 + function cr_get(this, category, name) result(info) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category, name + type(component_info) :: info + + integer :: i + + ! 初始化为空 + info%category = "" + info%name = "" + + ! 搜索组件 + do i = 1, this%count + if (trim(this%components(i)%category) == trim(category) .and. & + trim(this%components(i)%name) == trim(name)) then + info = this%components(i) + return + end if + end do + + if (this%verbose) then + print *, "❌ 未找到: ", trim(category), ".", trim(name) + end if + end function cr_get + + ! 列出所有组件 + subroutine cr_list_all(this) + class(component_registry_type), intent(in) :: this + integer :: i + + print *, "=== 注册表内容 (" // trim(str(this%count)) // " 个组件) ===" + if (this%count == 0) then + print *, " (空)" + return + end if + + do i = 1, this%count + call this%components(i)%print() + end do + end subroutine cr_list_all + + ! 获取组件数量 + integer function cr_size(this) + class(component_registry_type), intent(in) :: this + cr_size = this%count + end function cr_size + + ! 清空注册表 + subroutine cr_clear(this) + class(component_registry_type), intent(inout) :: this + if (allocated(this%components)) then + deallocate(this%components) + end if + this%count = 0 + this%capacity = 10 + print *, "🗑️ 注册表已清空" + end subroutine cr_clear + + ! ==================== 7. 工具函数 ==================== + + ! 整数转字符串 + function str(i) result(s) + integer, intent(in) :: i + character(len=20) :: s + write(s, '(I0)') i + s = trim(adjustl(s)) + end function str + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01/tests/CMakeLists.txt new file mode 100644 index 000000000..141f8045f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01/tests/CMakeLists.txt @@ -0,0 +1,15 @@ +# tests/CMakeLists.txt +# 创建测试程序 +add_executable(test_registry + test_registry.f90 +) + +# 链接核心库 +target_link_libraries(test_registry + core +) + +# 设置模块路径 +target_include_directories(test_registry PRIVATE + ${CMAKE_Fortran_MODULE_DIRECTORY} +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01/tests/test_registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/01/tests/test_registry.f90 new file mode 100644 index 000000000..9c770d76f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01/tests/test_registry.f90 @@ -0,0 +1,65 @@ +! tests/test_registry.f90 +program test_registry + use registry_module + implicit none + + type(component_info) :: info + integer :: initial_size + + print *, "=== Fortran注册系统测试 ===" + print *, "" + + ! 测试1: 基本注册 + print *, "测试1: 基本注册功能" + print *, "---------------------" + + call component_registry%list_all() + initial_size = component_registry%size() + print *, "初始大小: ", initial_size + + ! 注册一些组件 + call register_component("reconstructor", "eno") + call register_component("reconstructor", "weno3") + call register_component("reconstructor", "weno5") + call register_component("flux", "rusanov") + call register_component("flux", "engquist-osher") + call register_component("integrator", "rk1") + call register_component("integrator", "rk2") + call register_component("integrator", "rk3") + + print *, "注册后大小: ", component_registry%size() + call component_registry%list_all() + print *, "" + + ! 测试2: 重复注册 + print *, "测试2: 重复注册(应该显示警告)" + print *, "-------------------------------" + call register_component("reconstructor", "eno") + print *, "" + + ! 测试3: 获取组件 + print *, "测试3: 获取组件功能" + print *, "-------------------" + + ! 测试获取存在的组件 + info = component_registry%get("reconstructor", "weno3") + print *, "获取 weno3: " + call info%print() + + ! 测试获取不存在的组件 + info = component_registry%get("reconstructor", "non_existent") + print *, "获取 non_existent (应该为空): " + call info%print() + print *, "" + + ! 测试4: 清空功能 + print *, "测试4: 清空注册表" + print *, "-----------------" + call component_registry%clear() + print *, "清空后大小: ", component_registry%size() + call component_registry%list_all() + + print *, "" + print *, "=== 所有测试完成 ===" + +end program test_registry \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01a/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01a/CMakeLists.txt new file mode 100644 index 000000000..665f48ace --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01a/CMakeLists.txt @@ -0,0 +1,26 @@ +# 根目录CMakeLists.txt +#cmake_minimum_required(VERSION 3.12) +cmake_minimum_required(VERSION 4.2.1) +project(FortranRegistry VERSION 1.0.0 LANGUAGES Fortran) + +message(STATUS "CMAKE_Fortran_COMPILER=${CMAKE_Fortran_COMPILER}") +message(STATUS "CMAKE_Fortran_COMPILER_ID=${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "CMAKE_Fortran_COMPILER_VERSION=${CMAKE_Fortran_COMPILER_VERSION}") + +# 设置Fortran标准 +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +# 模块输出目录 +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) + +# 编译器选项 +if(CMAKE_Fortran_COMPILER_ID MATCHES "GNU") + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -Wall -Wextra") + set(CMAKE_Fortran_FLAGS_DEBUG "-g -O0 -fcheck=all -fbacktrace") + set(CMAKE_Fortran_FLAGS_RELEASE "-O3") +endif() + +# 添加子目录 +add_subdirectory(src) +add_subdirectory(tests) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01a/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01a/src/CMakeLists.txt new file mode 100644 index 000000000..0ddd08d29 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01a/src/CMakeLists.txt @@ -0,0 +1,19 @@ +# src/CMakeLists.txt +# 源代码目录的CMake配置 + +message(STATUS "配置源代码目录...") + +# 添加核心模块子目录 +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/core AND + EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/core/CMakeLists.txt) + add_subdirectory(core) +else() + message(WARNING "core目录或CMakeLists.txt不存在") +endif() + +# 可选:添加其他模块子目录 +# if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/infrastructure) +# add_subdirectory(infrastructure) +# endif() + +message(STATUS "源代码目录配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01a/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01a/src/core/CMakeLists.txt new file mode 100644 index 000000000..9685b6fb4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01a/src/core/CMakeLists.txt @@ -0,0 +1,16 @@ +# src/core/CMakeLists.txt +# 创建核心库 +add_library(core + registry.f90 +) + +# 设置模块输出目录 +target_include_directories(core PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 安装规则(可选) +install(TARGETS core + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01a/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/01a/src/core/registry.f90 new file mode 100644 index 000000000..1ffc879f3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01a/src/core/registry.f90 @@ -0,0 +1,241 @@ +! src/core/registry.f90 +module registry_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + + ! 公开接口 + public :: wp, component_info, component_registry_type, component_registry + public :: register_component, initialize_registry, cleanup_registry + + ! 类型定义 + type :: component_info + character(len=50) :: category = "" + character(len=50) :: name = "" + integer :: order = 0 + contains + procedure :: print => ci_print + end type component_info + + type :: component_registry_type + private + type(component_info), allocatable :: components(:) + integer :: count = 0 + integer :: capacity = 10 + logical :: verbose = .true. + logical :: initialized = .false. + contains + procedure :: register => cr_register + procedure :: get => cr_get + procedure :: list_all => cr_list_all + procedure :: size => cr_size + procedure :: clear => cr_clear + end type component_registry_type + + ! 全局实例 + type(component_registry_type) :: component_registry + + ! 接口 - 确保两个版本都公开 + interface register_component + module procedure register_component_simple + module procedure register_component_with_order + end interface register_component + +contains + + ! ==================== 公共接口实现 ==================== + + ! 初始化注册表 + subroutine initialize_registry(initial_capacity, verbose) + integer, optional, intent(in) :: initial_capacity + logical, optional, intent(in) :: verbose + + if (component_registry%initialized) then + print *, "[WARN] Registry already initialized" + return + end if + + if (present(initial_capacity)) then + component_registry%capacity = max(10, initial_capacity) + end if + + if (present(verbose)) then + component_registry%verbose = verbose + end if + + component_registry%initialized = .true. + print *, "[INIT] Registry initialized, capacity:", component_registry%capacity + end subroutine initialize_registry + + ! 清理注册表 + subroutine cleanup_registry + call component_registry%clear() + component_registry%initialized = .false. + print *, "[CLEANUP] Registry cleaned up" + end subroutine cleanup_registry + + ! 简单注册(无阶数) + subroutine register_component_simple(category, name) + character(len=*), intent(in) :: category, name + type(component_info) :: info + + info%category = trim(category) + info%name = trim(name) + info%order = 0 + + call component_registry%register(category, name, info) + end subroutine register_component_simple + + ! 带阶数的注册 + subroutine register_component_with_order(category, name, order) + character(len=*), intent(in) :: category, name + integer, intent(in) :: order + type(component_info) :: info + + info%category = trim(category) + info%name = trim(name) + info%order = order + + call component_registry%register(category, name, info) + end subroutine register_component_with_order + + ! ==================== 内部方法 ==================== + + subroutine ci_print(this) + class(component_info), intent(in) :: this + if (this%order > 0) then + print *, " [", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ")]" + else + print *, " [", trim(this%category), ".", trim(this%name), "]" + end if + end subroutine ci_print + + ! 内部注册实现 + subroutine cr_register(this, category, name, info) + class(component_registry_type), intent(inout) :: this + character(len=*), intent(in) :: category, name + type(component_info), intent(in) :: info + + type(component_info), allocatable :: temp(:) + integer :: i + + if (.not. this%initialized) then + print *, "[ERROR] Registry not initialized, call initialize_registry first" + return + end if + + ! 检查是否已存在 + do i = 1, this%count + if (trim(this%components(i)%category) == trim(category) .and. & + trim(this%components(i)%name) == trim(name)) then + if (this%verbose) then + print *, "[WARN] Overwriting: ", trim(category), ".", trim(name) + end if + this%components(i) = info + return + end if + end do + + ! 初始化数组 + if (.not. allocated(this%components)) then + allocate(this%components(this%capacity)) + end if + + ! 扩展数组 + if (this%count >= this%capacity) then + this%capacity = this%capacity * 2 + allocate(temp(this%capacity)) + temp(1:this%count) = this%components(1:this%count) + call move_alloc(temp, this%components) + if (this%verbose) then + print *, "[INFO] Registry expanded to:", this%capacity + end if + end if + + ! 添加组件 + this%count = this%count + 1 + this%components(this%count) = info + + if (this%verbose) then + print *, "[OK] Registered: ", trim(category), ".", trim(name) + end if + end subroutine cr_register + + function cr_get(this, category, name) result(info) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category, name + type(component_info) :: info + + integer :: i + + info%category = "" + info%name = "" + info%order = 0 + + if (.not. this%initialized) then + if (this%verbose) then + print *, "[ERROR] Registry not initialized" + end if + return + end if + + do i = 1, this%count + if (trim(this%components(i)%category) == trim(category) .and. & + trim(this%components(i)%name) == trim(name)) then + info = this%components(i) + return + end if + end do + + if (this%verbose) then + print *, "[ERROR] Not found: ", trim(category), ".", trim(name) + end if + end function cr_get + + subroutine cr_list_all(this) + class(component_registry_type), intent(in) :: this + integer :: i + + if (.not. this%initialized) then + print *, "[ERROR] Registry not initialized" + return + end if + + print *, "=== Registry Contents (", this%count, " components) ===" + if (this%count == 0) then + print *, " (empty)" + return + end if + + do i = 1, this%count + call this%components(i)%print() + end do + end subroutine cr_list_all + + integer function cr_size(this) + class(component_registry_type), intent(in) :: this + cr_size = this%count + end function cr_size + + subroutine cr_clear(this) + class(component_registry_type), intent(inout) :: this + if (allocated(this%components)) then + deallocate(this%components) + end if + this%count = 0 + this%capacity = 10 + this%initialized = .false. + print *, "[CLEAR] Registry cleared" + end subroutine cr_clear + + ! 工具函数 + function str(i) result(s) + integer, intent(in) :: i + character(len=20) :: s + write(s, '(I0)') i + s = trim(adjustl(s)) + end function str + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01a/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01a/tests/CMakeLists.txt new file mode 100644 index 000000000..141f8045f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01a/tests/CMakeLists.txt @@ -0,0 +1,15 @@ +# tests/CMakeLists.txt +# 创建测试程序 +add_executable(test_registry + test_registry.f90 +) + +# 链接核心库 +target_link_libraries(test_registry + core +) + +# 设置模块路径 +target_include_directories(test_registry PRIVATE + ${CMAKE_Fortran_MODULE_DIRECTORY} +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01a/tests/test_registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/01a/tests/test_registry.f90 new file mode 100644 index 000000000..f6ad17776 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01a/tests/test_registry.f90 @@ -0,0 +1,75 @@ +! tests/test_registry.f90 +program test_registry + use registry_module + implicit none + + type(component_info) :: info + + print *, "=== Fortran Registry System Test ===" + print *, "" + + ! 初始化注册表 + call initialize_registry(verbose=.true.) + print *, "" + + ! 测试1: 基本注册 + print *, "Test 1: Basic Registration" + print *, "--------------------------" + + call component_registry%list_all() + print *, "Initial size: ", component_registry%size() + + ! 使用两种方式注册组件 + call register_component("reconstructor", "eno") + call register_component("reconstructor", "weno3") ! 简单版本 + call register_component("reconstructor", "weno5") + call register_component("flux", "rusanov") + call register_component("flux", "engquist-osher") + + print *, "After registration size: ", component_registry%size() + call component_registry%list_all() + print *, "" + + ! 测试2: 重复注册 + print *, "Test 2: Duplicate Registration" + print *, "-------------------------------" + call register_component("reconstructor", "eno") + print *, "" + + ! 测试3: 获取组件 + print *, "Test 3: Component Retrieval" + print *, "---------------------------" + + info = component_registry%get("reconstructor", "eno") + print *, "Get eno: " + call info%print() + + info = component_registry%get("reconstructor", "non_existent") + print *, "Get non_existent (should be empty): " + call info%print() + print *, "" + + ! 测试4: 清空注册表 + print *, "Test 4: Clear Registry" + print *, "----------------------" + call component_registry%clear() + print *, "Size after clear: ", component_registry%size() + call component_registry%list_all() + print *, "" + + ! 测试5: 重新初始化并注册 + print *, "Test 5: Re-initialize and register" + print *, "----------------------------------" + call initialize_registry(verbose=.false.) + call register_component("integrator", "rk1") + call register_component("integrator", "rk2") + call register_component("integrator", "rk3") + call component_registry%list_all() + print *, "" + + ! 清理 + call cleanup_registry() + + print *, "=== All Tests Completed Successfully ===" + +end program test_registry \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01b/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01b/CMakeLists.txt new file mode 100644 index 000000000..a2fe5bb7f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01b/CMakeLists.txt @@ -0,0 +1,50 @@ +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + +# Set Fortran standard +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +# Module output directory +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) + +# Compiler flags for Intel Fortran +if(CMAKE_Fortran_COMPILER_ID MATCHES "IntelLLVM|Intel") + # Intel oneAPI/ifx compiler + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS}") + set(CMAKE_Fortran_FLAGS_DEBUG "/debug:full /Od /traceback /check:all /warn:all /fpe:0") + set(CMAKE_Fortran_FLAGS_RELEASE "/O3") + set(CMAKE_Fortran_FLAGS_RELWITHDEBINFO "/O2 /debug:full") +elseif(CMAKE_Fortran_COMPILER_ID MATCHES "GNU") + # GNU gfortran compiler + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -Wall -Wextra") + set(CMAKE_Fortran_FLAGS_DEBUG "-g -O0 -fcheck=all -fbacktrace -ffpe-trap=invalid,zero,overflow") + set(CMAKE_Fortran_FLAGS_RELEASE "-O3 -march=native") + set(CMAKE_Fortran_FLAGS_RELWITHDEBINFO "-O2 -g") +endif() + +# Set default build type +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Build type" FORCE) + message(STATUS "Setting build type to: ${CMAKE_BUILD_TYPE}") +endif() + +message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") + +# Create module directory +file(MAKE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY}) + +# Add subdirectories +add_subdirectory(src) +add_subdirectory(tests) + +# Install directory +set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/install CACHE PATH "Installation prefix") + +message(STATUS "Installation prefix: ${CMAKE_INSTALL_PREFIX}") +message(STATUS "Module directory: ${CMAKE_Fortran_MODULE_DIRECTORY}") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01b/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01b/src/CMakeLists.txt new file mode 100644 index 000000000..ee38952bb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01b/src/CMakeLists.txt @@ -0,0 +1,14 @@ +# ==================== src\CMakeLists.txt ==================== + +# src/CMakeLists.txt +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(core) +add_subdirectory(infrastructure) +add_subdirectory(numerics) + +message(STATUS "源代码目录配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01b/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01b/src/core/CMakeLists.txt new file mode 100644 index 000000000..376dd4fe5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01b/src/core/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/core/CMakeLists.txt +message(STATUS "配置核心模块...") + +add_library(core + registry.f90 + factory_interfaces.f90 +) + +target_include_directories(core PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 安装规则 +install(TARGETS core + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) +install(FILES + ${CMAKE_Fortran_MODULE_DIRECTORY}/registry_module.mod + ${CMAKE_Fortran_MODULE_DIRECTORY}/factory_interfaces.mod + DESTINATION include/fortran_cfd/core +) + +message(STATUS "核心模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01b/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/01b/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..b410321d3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01b/src/core/factory_interfaces.f90 @@ -0,0 +1,23 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp + + ! 抽象工厂接口 + type, abstract :: base_factory + contains + procedure(create_interface), deferred :: create + end type base_factory + + abstract interface + subroutine create_interface(this, instance) + import :: base_factory + class(base_factory), intent(in) :: this + class(*), allocatable, intent(out) :: instance + end subroutine create_interface + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01b/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/01b/src/core/registry.f90 new file mode 100644 index 000000000..68091c6b6 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01b/src/core/registry.f90 @@ -0,0 +1,423 @@ +! src/core/registry.f90 +module registry_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + + ! ==================== 公开接口 ==================== + public :: wp, component_info, component_registry + public :: register_component, create_component + public :: initialize_registry, cleanup_registry + public :: has_component, get_available_components + + ! ==================== 类型定义 ==================== + + ! 工厂过程接口 + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + + ! 组件信息类型 + type :: component_info + character(len=32) :: category = "" + character(len=32) :: name = "" + integer :: order = 0 + procedure(factory_procedure), pointer, nopass :: factory => null() + logical :: has_factory = .false. + contains + procedure :: print => ci_print + procedure :: create => ci_create + end type component_info + + ! 注册表类型 + type :: component_registry_type + private + type(component_info), allocatable :: components(:) + integer :: count = 0 + integer :: capacity = 100 + logical :: verbose = .true. + logical :: initialized = .false. + contains + procedure, private :: register_info => cr_register_info + procedure :: get => cr_get + procedure :: list_all => cr_list_all + procedure :: clear => cr_clear + procedure :: size => cr_size + procedure, private :: expand => cr_expand + end type component_registry_type + + ! 全局注册表实例 + type(component_registry_type), save :: component_registry + + ! 接口重载 + interface register_component + module procedure register_component_simple + module procedure register_component_with_factory + module procedure register_component_full + end interface register_component + +contains + + ! ==================== 公共API ==================== + + ! 初始化注册表 + subroutine initialize_registry(initial_capacity, verbose) + integer, optional, intent(in) :: initial_capacity + logical, optional, intent(in) :: verbose + + if (component_registry%initialized) then + if (component_registry%verbose) then + print *, "[INFO] 注册表已初始化" + end if + return + end if + + if (present(initial_capacity)) then + component_registry%capacity = max(10, initial_capacity) + end if + + if (present(verbose)) then + component_registry%verbose = verbose + end if + + ! 分配数组 + allocate(component_registry%components(component_registry%capacity)) + + component_registry%initialized = .true. + component_registry%count = 0 + + if (component_registry%verbose) then + print *, "[INIT] 注册表初始化完成,容量:", component_registry%capacity + end if + end subroutine initialize_registry + + ! 清理注册表 + subroutine cleanup_registry + call component_registry%clear() + if (component_registry%verbose) then + print *, "[CLEANUP] 注册表清理完成" + end if + end subroutine cleanup_registry + + ! 简单注册(只有名称) + subroutine register_component_simple(category, name) + character(len=*), intent(in) :: category, name + type(component_info) :: info + + info%category = to_lower(trim(adjustl(category))) + info%name = to_lower(trim(adjustl(name))) + info%order = 0 + info%factory => null() + info%has_factory = .false. + + call component_registry%register_info(info) + end subroutine register_component_simple + + ! 带阶数的注册 + subroutine register_component_with_factory(category, name, factory_proc, order) + character(len=*), intent(in) :: category, name + procedure(factory_procedure) :: factory_proc + integer, optional, intent(in) :: order + + type(component_info) :: info + + info%category = to_lower(trim(adjustl(category))) + info%name = to_lower(trim(adjustl(name))) + + if (present(order)) then + info%order = order + else + info%order = 0 + end if + + info%factory => factory_proc + info%has_factory = .true. + + call component_registry%register_info(info) + end subroutine register_component_with_factory + + ! 完整注册 + subroutine register_component_full(category, name, order, has_factory) + character(len=*), intent(in) :: category, name + integer, intent(in) :: order + logical, intent(in) :: has_factory + + type(component_info) :: info + + info%category = to_lower(trim(adjustl(category))) + info%name = to_lower(trim(adjustl(name))) + info%order = order + info%factory => null() + info%has_factory = has_factory + + call component_registry%register_info(info) + end subroutine register_component_full + + ! 创建组件实例 + subroutine create_component(category, name, instance) + character(len=*), intent(in) :: category, name + class(*), allocatable, intent(out) :: instance + + type(component_info) :: info + character(len=32) :: cat_lower, name_lower + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + info = component_registry%get(cat_lower, name_lower) + + if (len_trim(info%category) == 0) then + error stop "[ERROR] 组件未找到: " // trim(cat_lower) // "." // trim(name_lower) + end if + + if (.not. info%has_factory) then + error stop "[ERROR] 组件没有工厂函数: " // trim(cat_lower) // "." // trim(name_lower) + end if + + if (.not. associated(info%factory)) then + error stop "[ERROR] 工厂函数未关联: " // trim(cat_lower) // "." // trim(name_lower) + end if + + call info%create(instance) + end subroutine create_component + + ! 检查组件是否存在 + function has_component(category, name) result(found) + character(len=*), intent(in) :: category, name + logical :: found + + type(component_info) :: info + character(len=32) :: cat_lower, name_lower + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + info = component_registry%get(cat_lower, name_lower) + found = (len_trim(info%category) > 0) + end function has_component + + ! 获取某类别下的所有组件 + subroutine get_available_components(category, names, orders) + character(len=*), intent(in) :: category + character(len=:), allocatable, intent(out), optional :: names(:) + integer, allocatable, intent(out), optional :: orders(:) + + character(len=32) :: cat_lower + integer :: i, count, idx + type(component_info) :: info + + cat_lower = to_lower(trim(adjustl(category))) + + ! 先计算数量 + count = 0 + do i = 1, component_registry%count + if (component_registry%components(i)%category == cat_lower) then + count = count + 1 + end if + end do + + ! 分配数组 + if (present(names)) then + allocate(character(len=32) :: names(count)) + end if + + if (present(orders)) then + allocate(orders(count)) + end if + + ! 填充数组 + idx = 1 + do i = 1, component_registry%count + if (component_registry%components(i)%category == cat_lower) then + info = component_registry%components(i) + if (present(names)) then + names(idx) = info%name + end if + if (present(orders)) then + orders(idx) = info%order + end if + idx = idx + 1 + end if + end do + end subroutine get_available_components + + ! ==================== 组件信息方法 ==================== + + subroutine ci_print(this) + class(component_info), intent(in) :: this + + if (this%order > 0) then + if (this%has_factory) then + print *, " [", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ", has factory)]" + else + print *, " [", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ")]" + end if + else + if (this%has_factory) then + print *, " [", trim(this%category), ".", trim(this%name), & + " (has factory)]" + else + print *, " [", trim(this%category), ".", trim(this%name), "]" + end if + end if + end subroutine ci_print + + subroutine ci_create(this, instance) + class(component_info), intent(in) :: this + class(*), allocatable, intent(out) :: instance + + if (.not. this%has_factory) then + error stop "[ERROR] 组件没有工厂函数" + end if + + if (.not. associated(this%factory)) then + error stop "[ERROR] 工厂函数未关联" + end if + + call this%factory(instance) + end subroutine ci_create + + ! ==================== 注册表内部方法 ==================== + + subroutine cr_register_info(this, info) + class(component_registry_type), intent(inout) :: this + type(component_info), intent(in) :: info + + integer :: i + + if (.not. this%initialized) then + error stop "[ERROR] 注册表未初始化,请先调用 initialize_registry" + end if + + ! 检查是否已存在 + do i = 1, this%count + if (this%components(i)%category == info%category .and. & + this%components(i)%name == info%name) then + if (this%verbose) then + print *, "[WARN] 覆盖注册: ", & + trim(info%category), ".", trim(info%name) + end if + this%components(i) = info + return + end if + end do + + ! 检查是否需要扩展 + if (this%count >= this%capacity) then + call this%expand() + end if + + ! 添加组件 + this%count = this%count + 1 + this%components(this%count) = info + + if (this%verbose) then + print *, "[OK] 已注册: ", trim(info%category), ".", trim(info%name) + end if + end subroutine cr_register_info + + function cr_get(this, category, name) result(info) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category, name + type(component_info) :: info + + integer :: i + + ! 初始化返回值为空 + info%category = "" + info%name = "" + info%order = 0 + info%factory => null() + info%has_factory = .false. + + if (.not. this%initialized) then + return + end if + + do i = 1, this%count + if (this%components(i)%category == category .and. & + this%components(i)%name == name) then + info = this%components(i) + return + end if + end do + end function cr_get + + subroutine cr_list_all(this) + class(component_registry_type), intent(in) :: this + integer :: i + + if (.not. this%initialized) then + print *, "[INFO] 注册表未初始化" + return + end if + + print *, "=== 注册表内容 (", this%count, " 个组件) ===" + + if (this%count == 0) then + print *, " (空)" + return + end if + + ! 按类别分组显示 + do i = 1, this%count + call this%components(i)%print() + end do + + print *, "======================================" + end subroutine cr_list_all + + subroutine cr_clear(this) + class(component_registry_type), intent(inout) :: this + + if (allocated(this%components)) then + deallocate(this%components) + end if + + this%count = 0 + this%capacity = 100 + this%initialized = .false. + end subroutine cr_clear + + integer function cr_size(this) + class(component_registry_type), intent(in) :: this + cr_size = this%count + end function cr_size + + subroutine cr_expand(this) + class(component_registry_type), intent(inout) :: this + type(component_info), allocatable :: temp(:) + + this%capacity = this%capacity * 2 + allocate(temp(this%capacity)) + temp(1:this%count) = this%components(1:this%count) + call move_alloc(temp, this%components) + + if (this%verbose) then + print *, "[INFO] 注册表扩展至容量:", this%capacity + end if + end subroutine cr_expand + + ! ==================== 工具函数 ==================== + + function to_lower(str) result(lower_str) + character(len=*), intent(in) :: str + character(len=len(str)) :: lower_str + integer :: i + + do i = 1, len(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + lower_str(i:i) = char(ichar(str(i:i)) + 32) + else + lower_str(i:i) = str(i:i) + end if + end do + end function to_lower + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01b/src/core/registry_advanced.f90 b/example/1d-linear-convection/weno3/fortran/registry/01b/src/core/registry_advanced.f90 new file mode 100644 index 000000000..4b5aa05ac --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01b/src/core/registry_advanced.f90 @@ -0,0 +1,215 @@ +! src/core/registry_advanced.f90 +module registry_advanced_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use factory_interfaces, only: factory_procedure + implicit none + + private + public :: wp, component_info, component_registry, register_factory, create_instance + + ! 组件信息类型(增强版) + type :: component_info + character(len=50) :: category = "" + character(len=50) :: name = "" + integer :: order = 0 + procedure(factory_procedure), pointer, nopass :: factory => null() + logical :: has_factory = .false. + contains + procedure :: print => ci_print + procedure :: create => ci_create + end type component_info + + ! 注册表类型 + type :: component_registry_type + private + type(component_info), allocatable :: components(:) + integer :: count = 0 + integer :: capacity = 10 + logical :: verbose = .true. + contains + procedure :: register => cr_register + procedure :: get => cr_get + procedure :: list_all => cr_list_all + procedure :: create => cr_create + end type component_registry_type + + type(component_registry_type) :: component_registry + + ! 接口 + interface register_factory + module procedure register_factory_simple + module procedure register_factory_with_order + end interface register_factory + +contains + + ! ==================== 组件信息方法 ==================== + + subroutine ci_print(this) + class(component_info), intent(in) :: this + character(len=100) :: factory_str + + if (this%has_factory) then + factory_str = " [has factory]" + else + factory_str = "" + end if + + if (this%order > 0) then + print *, " [", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ")", trim(factory_str), "]" + else + print *, " [", trim(this%category), ".", trim(this%name), & + trim(factory_str), "]" + end if + end subroutine ci_print + + subroutine ci_create(this, instance) + class(component_info), intent(in) :: this + class(*), allocatable, intent(out) :: instance + + if (.not. this%has_factory) then + error stop "[ERROR] Component has no factory" + end if + + call this%factory(instance) + end subroutine ci_create + + ! ==================== 注册表方法 ==================== + + ! 注册工厂(简单版) + subroutine register_factory_simple(category, name, factory_proc) + character(len=*), intent(in) :: category, name + procedure(factory_procedure) :: factory_proc + + type(component_info) :: info + + info%category = trim(category) + info%name = trim(name) + info%order = 0 + info%factory => factory_proc + info%has_factory = .true. + + call component_registry%register(info) + end subroutine register_factory_simple + + ! 注册工厂(带阶数) + subroutine register_factory_with_order(category, name, factory_proc, order) + character(len=*), intent(in) :: category, name + procedure(factory_procedure) :: factory_proc + integer, intent(in) :: order + + type(component_info) :: info + + info%category = trim(category) + info%name = trim(name) + info%order = order + info%factory => factory_proc + info%has_factory = .true. + + call component_registry%register(info) + end subroutine register_factory_with_order + + ! 内部注册实现 + subroutine cr_register(this, info) + class(component_registry_type), intent(inout) :: this + type(component_info), intent(in) :: info + + type(component_info), allocatable :: temp(:) + integer :: i + + ! 检查是否已存在 + do i = 1, this%count + if (trim(this%components(i)%category) == trim(info%category) .and. & + trim(this%components(i)%name) == trim(info%name)) then + if (this%verbose) then + print *, "[WARN] Overwriting: ", & + trim(info%category), ".", trim(info%name) + end if + this%components(i) = info + return + end if + end do + + ! 初始化数组 + if (.not. allocated(this%components)) then + allocate(this%components(this%capacity)) + end if + + ! 扩展数组 + if (this%count >= this%capacity) then + this%capacity = this%capacity * 2 + allocate(temp(this%capacity)) + temp(1:this%count) = this%components(1:this%count) + call move_alloc(temp, this%components) + end if + + ! 添加组件 + this%count = this%count + 1 + this%components(this%count) = info + + if (this%verbose) then + print *, "[OK] Registered: ", trim(info%category), ".", trim(info%name) + end if + end subroutine cr_register + + function cr_get(this, category, name) result(info) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category, name + type(component_info) :: info + + integer :: i + + ! 初始化为空 + info%category = "" + info%name = "" + info%order = 0 + info%factory => null() + info%has_factory = .false. + + do i = 1, this%count + if (trim(this%components(i)%category) == trim(category) .and. & + trim(this%components(i)%name) == trim(name)) then + info = this%components(i) + return + end if + end do + + if (this%verbose) then + print *, "[ERROR] Not found: ", trim(category), ".", trim(name) + end if + end function cr_get + + ! 创建实例 + subroutine cr_create(this, category, name, instance) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category, name + class(*), allocatable, intent(out) :: instance + + type(component_info) :: info + + info = this%get(category, name) + + if (len_trim(info%category) > 0 .and. info%has_factory) then + call info%create(instance) + else + error stop "[ERROR] Cannot create instance: factory not available" + end if + end subroutine cr_create + + subroutine cr_list_all(this) + class(component_registry_type), intent(in) :: this + integer :: i + + print *, "=== Registry Contents (", this%count, " components) ===" + if (this%count == 0) then + print *, " (empty)" + return + end if + + do i = 1, this%count + call this%components(i)%print() + end do + end subroutine cr_list_all + +end module registry_advanced_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01b/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01b/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..2b72c5484 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01b/src/infrastructure/CMakeLists.txt @@ -0,0 +1,20 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "配置基础设施模块...") + +add_library(infrastructure + config.f90 + mesh.f90 +) + +target_link_libraries(infrastructure PUBLIC core) + +target_include_directories(infrastructure PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS infrastructure + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "基础设施模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01b/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/01b/src/infrastructure/config.f90 new file mode 100644 index 000000000..e8e003ae3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01b/src/infrastructure/config.f90 @@ -0,0 +1,117 @@ +! src/infrastructure/config.f90 +module config_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use registry_module, only: register_component + implicit none + + private + public :: wp, cfd_config + + ! CFD配置类型 + type :: cfd_config + character(len=20) :: ic_type = "step" + character(len=20) :: recon_scheme = "eno" + character(len=20) :: flux_type = "rusanov" + integer :: rk_order = 1 + real(wp) :: wave_speed = 1.0_wp + real(wp) :: final_time = 0.625_wp + real(wp) :: dt = 0.025_wp + character(len=20) :: boundary_type = "periodic" + real(wp) :: left_boundary_value = 1.0_wp + real(wp) :: right_boundary_value = 2.0_wp + integer :: spatial_order = 2 + logical :: verbose = .true. + contains + procedure :: with_reconstruction => config_with_reconstruction + procedure :: with_boundary => config_with_boundary + procedure :: print => config_print + end type cfd_config + +contains + + subroutine config_with_reconstruction(this, scheme, order) + class(cfd_config), intent(inout) :: this + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + character(len=20) :: scheme_lower + + ! 转换为小写 + scheme_lower = scheme + call to_lower_inplace(scheme_lower) + this%recon_scheme = trim(adjustl(scheme_lower)) + + ! 设置阶数 + if (present(order)) then + this%spatial_order = order + else + ! 智能默认 + if (index(this%recon_scheme, 'weno') > 0) then + this%spatial_order = 5 + else if (trim(this%recon_scheme) == 'eno') then + this%spatial_order = 3 + else + error stop "[ERROR] 不支持的重建格式: " // trim(this%recon_scheme) + end if + end if + + if (this%verbose) then + print *, "[CONFIG] 重建方案: ", trim(this%recon_scheme), & + " 阶数: ", this%spatial_order + end if + end subroutine config_with_reconstruction + + subroutine config_with_boundary(this, bc_type, left_value, right_value) + class(cfd_config), intent(inout) :: this + character(len=*), intent(in) :: bc_type + real(wp), optional, intent(in) :: left_value + real(wp), optional, intent(in) :: right_value + + this%boundary_type = trim(adjustl(bc_type)) + + if (present(left_value)) then + this%left_boundary_value = left_value + end if + + if (present(right_value)) then + this%right_boundary_value = right_value + end if + + if (this%verbose) then + print *, "[CONFIG] 边界条件: ", trim(this%boundary_type), & + " 左值: ", this%left_boundary_value, & + " 右值: ", this%right_boundary_value + end if + end subroutine config_with_boundary + + subroutine config_print(this) + class(cfd_config), intent(in) :: this + + print *, "=== CFD 配置 ===" + print *, "初始条件: ", trim(this%ic_type) + print *, "重建方案: ", trim(this%recon_scheme), " (order:", this%spatial_order, ")" + print *, "通量类型: ", trim(this%flux_type) + print *, "时间积分: RK", this%rk_order + print *, "波速: ", this%wave_speed + print *, "最终时间: ", this%final_time + print *, "时间步长: ", this%dt + print *, "边界条件: ", trim(this%boundary_type) + if (trim(this%boundary_type) == 'dirichlet') then + print *, " Dirichlet值: [", this%left_boundary_value, ", ", & + this%right_boundary_value, "]" + end if + print *, "==============================" + end subroutine config_print + + subroutine to_lower_inplace(str) + character(len=*), intent(inout) :: str + integer :: i + + do i = 1, len_trim(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + str(i:i) = char(ichar(str(i:i)) + 32) + end if + end do + end subroutine to_lower_inplace + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01b/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/01b/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..65a45dcdf --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01b/src/infrastructure/mesh.f90 @@ -0,0 +1,74 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, mesh_type, mesh_init, mesh_print_info + + ! 网格类型 + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer :: ncells = 40 + integer :: nnodes + integer :: nx + real(wp), allocatable :: x(:) ! 节点坐标 + real(wp), allocatable :: xcc(:) ! 单元中心坐标 + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer, optional, intent(in) :: ncells + + integer :: i + + ! 设置参数 + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! 计算派生参数 + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! 分配数组 + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! 生成节点坐标 + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! 生成单元中心坐标 + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== 网格信息 ===" + print *, "计算域: [", this%xmin, ", ", this%xmax, "]" + print *, "单元数: ", this%ncells + print *, "节点数: ", this%nnodes + print *, "网格尺寸 dx: ", this%dx + print *, "域长度 L: ", this%L + print *, "==========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..66874a7ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/CMakeLists.txt @@ -0,0 +1,7 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..e024ec4b0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,21 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +add_library(flux + base.f90 + rusanov.f90 + factory.f90 +) + +target_link_libraries(flux PUBLIC core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/flux/base.f90 new file mode 100644 index 000000000..57fc04dab --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/flux/base.f90 @@ -0,0 +1,35 @@ +! src/numerics/flux/base.f90 +module flux_base_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, flux_calculator_base + + ! 通量计算器抽象基类 + type, abstract :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure(compute_interface), deferred :: compute + procedure :: info => flux_info + end type flux_calculator_base + + abstract interface + subroutine compute_interface(this, qL, qR, flux, wave_speed) + import :: flux_calculator_base, wp + class(flux_calculator_base), intent(in) :: this + real(wp), intent(in) :: qL(:), qR(:) + real(wp), intent(out) :: flux(:) + real(wp), intent(in) :: wave_speed + end subroutine compute_interface + end interface + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "通量计算器信息:" + print *, " 名称: ", trim(this%name) + end subroutine flux_info + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/flux/factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/flux/factory.f90 new file mode 100644 index 000000000..3a4bb3f89 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/flux/factory.f90 @@ -0,0 +1,47 @@ +! src/numerics/flux/factory.f90 +module flux_factory_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use registry_module, only: create_component, has_component + use flux_base_module, only: flux_calculator_base + implicit none + + private + public :: wp, flux_factory_create + +contains + + subroutine flux_factory_create(flux_name, flux_calculator) + character(len=*), intent(in) :: flux_name + class(flux_calculator_base), allocatable, intent(out) :: flux_calculator + + class(*), allocatable :: instance + character(len=20) :: name_lower + integer :: i + + ! Convert to lowercase + name_lower = flux_name + do i = 1, len_trim(name_lower) + if (name_lower(i:i) >= 'A' .and. name_lower(i:i) <= 'Z') then + name_lower(i:i) = char(ichar(name_lower(i:i)) + 32) + end if + end do + + ! Check if registered + if (.not. has_component("flux", trim(name_lower))) then + print *, "[ERROR] Flux calculator not registered: ", trim(flux_name) + return + end if + + ! Create instance + call create_component("flux", trim(name_lower), instance) + + ! Type conversion + select type (inst => instance) + type is (flux_calculator_base) + allocate(flux_calculator, source=inst) + class default + error stop "[ERROR] Created flux calculator has wrong type" + end select + end subroutine flux_factory_create + +end module flux_factory_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/flux/rusanov.f90 b/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/flux/rusanov.f90 new file mode 100644 index 000000000..4f96137f7 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/flux/rusanov.f90 @@ -0,0 +1,70 @@ +! src/numerics/flux/rusanov.f90 +module rusanov_flux_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use flux_base_module, only: flux_calculator_base, wp + use registry_module, only: register_component_with_factory + implicit none + + private + public :: wp, rusanov_flux, create_rusanov + + type, extends(flux_calculator_base) :: rusanov_flux + real(wp) :: wave_speed_default = 1.0_wp + contains + procedure :: compute => rusanov_compute + procedure :: info => rusanov_info + end type rusanov_flux + +contains + + ! Factory function + subroutine create_rusanov(instance) + class(*), allocatable, intent(out) :: instance + type(rusanov_flux), allocatable :: flux + + allocate(flux) + flux%name = "Rusanov" + flux%wave_speed_default = 1.0_wp + + call move_alloc(flux, instance) + end subroutine create_rusanov + + ! Rusanov flux computation + subroutine rusanov_compute(this, qL, qR, flux, wave_speed) + class(rusanov_flux), intent(in) :: this + real(wp), intent(in) :: qL(:), qR(:) + real(wp), intent(out) :: flux(:) + real(wp), intent(in) :: wave_speed + + integer :: i, n + real(wp) :: max_speed, fL, fR + + n = size(qL) + + ! Ensure arrays have same size + if (size(qR) /= n .or. size(flux) /= n) then + error stop "[ERROR] Array size mismatch in rusanov_compute" + end if + + ! Compute maximum wave speed + max_speed = abs(wave_speed) + + ! Compute Rusanov flux at each interface + do i = 1, n + ! Simple linear convection flux: f(u) = wave_speed * u + fL = wave_speed * qL(i) + fR = wave_speed * qR(i) + + ! Rusanov flux formula + flux(i) = 0.5_wp * (fL + fR) - 0.5_wp * max_speed * (qR(i) - qL(i)) + end do + end subroutine rusanov_compute + + subroutine rusanov_info(this) + class(rusanov_flux), intent(in) :: this + call flux_info(this) + print *, " Type: Rusanov flux" + print *, " Default wave speed: ", this%wave_speed_default + end subroutine rusanov_info + +end module rusanov_flux_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..68bf7c353 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,23 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "配置重构器模块...") + +add_library(reconstructor + base.f90 + eno.f90 + weno3.f90 + weno5.f90 + factory.f90 +) + +target_link_libraries(reconstructor PUBLIC core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "重构器模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..bceda6793 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/reconstructor/base.f90 @@ -0,0 +1,40 @@ +! src/numerics/reconstructor/base.f90 +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, reconstructor_base + + ! 重构器抽象基类 + type, abstract :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + real(wp) :: epsilon = 1.0e-6_wp + contains + procedure(reconstruct_interface), deferred :: reconstruct + procedure :: info => reconstructor_info + end type reconstructor_base + + abstract interface + subroutine reconstruct_interface(this, q, qL, qR) + import :: reconstructor_base, wp + class(reconstructor_base), intent(in) :: this + real(wp), intent(in) :: q(:) + real(wp), intent(out) :: qL(:) + real(wp), intent(out) :: qR(:) + end subroutine reconstruct_interface + end interface + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + + print *, "重构器信息:" + print *, " 名称: ", trim(this%name) + print *, " 阶数: ", this%order + print *, " epsilon: ", this%epsilon + end subroutine reconstructor_info + +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/reconstructor/eno.f90 b/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/reconstructor/eno.f90 new file mode 100644 index 000000000..737b6bf02 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/reconstructor/eno.f90 @@ -0,0 +1,111 @@ +! src/numerics/reconstructor/eno.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use reconstructor_base_module, only: reconstructor_base, wp + use registry_module, only: register_component_with_factory + implicit none + + private + public :: wp, eno_reconstructor, create_eno + + type, extends(reconstructor_base) :: eno_reconstructor + real(wp), allocatable :: coef(:,:) + real(wp), allocatable :: dd(:,:) + integer, allocatable :: lmc(:) + contains + procedure :: reconstruct => eno_reconstruct + procedure :: info => eno_info + procedure :: initialize => eno_initialize + end type eno_reconstructor + +contains + + ! 工厂函数 + subroutine create_eno(instance) + class(*), allocatable, intent(out) :: instance + type(eno_reconstructor), allocatable :: eno + + allocate(eno) + eno%name = "ENO" + eno%order = 3 + eno%epsilon = 1.0e-6_wp + + call move_alloc(eno, instance) + end subroutine create_eno + + ! 初始化ENO系数 + subroutine eno_initialize(this, order) + class(eno_reconstructor), intent(inout) :: this + integer, intent(in) :: order + + integer :: i + + this%order = order + + ! 分配系数数组 + if (allocated(this%coef)) deallocate(this%coef) + if (allocated(this%dd)) deallocate(this%dd) + if (allocated(this%lmc)) deallocate(this%lmc) + + allocate(this%coef(order+1, order)) + allocate(this%dd(order, 1000)) ! 临时大小 + allocate(this%lmc(1000)) ! 临时大小 + + ! 初始化ENO系数(简化版本,后续需要完善) + this%coef = 0.0_wp + + ! 这里应该填充ENO系数表 + ! 为了简化,我们先使用线性插值系数 + if (order == 1) then + this%coef(1,1) = 1.0_wp + this%coef(2,1) = 1.0_wp + elseif (order == 2) then + this%coef(1,1:2) = [1.5_wp, -0.5_wp] + this%coef(2,1:2) = [0.5_wp, 0.5_wp] + this%coef(3,1:2) = [-0.5_wp, 1.5_wp] + elseif (order == 3) then + this%coef(1,1:3) = [11.0_wp/6.0_wp, -7.0_wp/6.0_wp, 1.0_wp/3.0_wp] + this%coef(2,1:3) = [1.0_wp/3.0_wp, 5.0_wp/6.0_wp, -1.0_wp/6.0_wp] + this%coef(3,1:3) = [-1.0_wp/6.0_wp, 5.0_wp/6.0_wp, 1.0_wp/3.0_wp] + this%coef(4,1:3) = [1.0_wp/3.0_wp, -7.0_wp/6.0_wp, 11.0_wp/6.0_wp] + end if + end subroutine eno_initialize + + ! ENO重构实现(简化版本) + subroutine eno_reconstruct(this, q, qL, qR) + class(eno_reconstructor), intent(in) :: this + real(wp), intent(in) :: q(:) + real(wp), intent(out) :: qL(:), qR(:) + + integer :: i, n + + n = size(q) - 1 + + if (this%order == 1) then + ! 1阶ENO:简单上风 + do i = 1, n + qL(i) = q(i) + qR(i) = q(i+1) + end do + elseif (this%order == 2) then + ! 2阶ENO:线性插值 + do i = 1, n + qL(i) = 1.5_wp*q(i) - 0.5_wp*q(i-1) + qR(i) = -0.5_wp*q(i) + 1.5_wp*q(i+1) + end do + else + ! 默认:简单上风 + do i = 1, n + qL(i) = q(i) + qR(i) = q(i+1) + end do + end if + end subroutine eno_reconstruct + + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + call reconstructor_info(this) + print *, " 类型: ENO重构器" + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/reconstructor/eno_type.f90 b/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/reconstructor/eno_type.f90 new file mode 100644 index 000000000..0f12edcb0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/reconstructor/eno_type.f90 @@ -0,0 +1,59 @@ +! src/numerics/reconstructor/eno_type.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, eno_reconstructor, create_eno + + type :: eno_reconstructor + integer :: order = 3 + real(wp) :: epsilon = 1.0e-6_wp + character(len=50) :: name = "ENO" + contains + procedure :: compute => eno_compute + procedure :: info => eno_info + end type eno_reconstructor + +contains + + ! 工厂函数 + subroutine create_eno(instance) + class(*), allocatable, intent(out) :: instance + type(eno_reconstructor), allocatable :: eno + + allocate(eno) + eno%name = "ENO Reconstructor" + eno%order = 3 + eno%epsilon = 1.0e-6_wp + + call move_alloc(eno, instance) + end subroutine create_eno + + ! 计算接口 + subroutine eno_compute(this, q, qL, qR) + class(eno_reconstructor), intent(in) :: this + real(wp), intent(in) :: q(:) + real(wp), intent(out) :: qL(:), qR(:) + integer :: i, n + + n = size(q) - 1 + print *, "[ENO] Computing interface values for ", n, " cells" + + ! 简化实现:复制中间值 + do i = 1, n + qL(i) = q(i) + qR(i) = q(i+1) + end do + end subroutine eno_compute + + ! 信息输出 + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + print *, "ENO Reconstructor:" + print *, " Order: ", this%order + print *, " Epsilon: ", this%epsilon + print *, " Name: ", trim(this%name) + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/reconstructor/factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/reconstructor/factory.f90 new file mode 100644 index 000000000..63d3d8b8a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/reconstructor/factory.f90 @@ -0,0 +1,47 @@ +! src/numerics/reconstructor/factory.f90 +module reconstructor_factory_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use registry_module, only: create_component, has_component + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: wp, reconstructor_factory_create + +contains + + subroutine reconstructor_factory_create(scheme_name, reconstructor) + character(len=*), intent(in) :: scheme_name + class(reconstructor_base), allocatable, intent(out) :: reconstructor + + class(*), allocatable :: instance + character(len=20) :: name_lower + integer :: i + + ! 转换为小写 + name_lower = scheme_name + do i = 1, len_trim(name_lower) + if (name_lower(i:i) >= 'A' .and. name_lower(i:i) <= 'Z') then + name_lower(i:i) = char(ichar(name_lower(i:i)) + 32) + end if + end do + + ! 检查是否已注册 + if (.not. has_component("reconstructor", trim(name_lower))) then + print *, "[ERROR] 重构器未注册: ", trim(scheme_name) + return + end if + + ! 创建实例 + call create_component("reconstructor", trim(name_lower), instance) + + ! 类型转换 + select type (inst => instance) + type is (reconstructor_base) + allocate(reconstructor, source=inst) + class default + error stop "[ERROR] 创建的重构器类型错误" + end select + end subroutine reconstructor_factory_create + +end module reconstructor_factory_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/reconstructor/weno3.f90 b/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/reconstructor/weno3.f90 new file mode 100644 index 000000000..d49ad949e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/reconstructor/weno3.f90 @@ -0,0 +1,88 @@ +! src/numerics/reconstructor/weno3.f90 +module weno3_reconstructor_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use reconstructor_base_module, only: reconstructor_base, wp + use registry_module, only: register_component_with_factory + implicit none + + private + public :: wp, weno3_reconstructor, create_weno3 + + type, extends(reconstructor_base) :: weno3_reconstructor + contains + procedure :: reconstruct => weno3_reconstruct + procedure :: info => weno3_info + end type weno3_reconstructor + +contains + + ! 工厂函数 + subroutine create_weno3(instance) + class(*), allocatable, intent(out) :: instance + type(weno3_reconstructor), allocatable :: weno3 + + allocate(weno3) + weno3%name = "WENO3" + weno3%order = 3 + weno3%epsilon = 1.0e-6_wp + + call move_alloc(weno3, instance) + end subroutine create_weno3 + + ! WENO-3重构实现(简化版本) + subroutine weno3_reconstruct(this, q, qL, qR) + class(weno3_reconstructor), intent(in) :: this + real(wp), intent(in) :: q(:) + real(wp), intent(out) :: qL(:), qR(:) + + integer :: i, n + real(wp) :: beta0, beta1, alpha0, alpha1, alpha, w0, w1 + real(wp) :: q0, q1, v0, v1, v2 + + n = size(q) - 2 ! 需要2个ghost cells + + do i = 2, n+1 + ! 获取模板值 + v0 = q(i-1) + v1 = q(i) + v2 = q(i+1) + + ! 计算左界面值 qL(i-1) + beta0 = (v1 - v0)**2 + beta1 = (v2 - v1)**2 + + alpha0 = 1.0_wp/3.0_wp / (this%epsilon + beta0)**2 + alpha1 = 2.0_wp/3.0_wp / (this%epsilon + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + + q0 = -0.5_wp*v0 + 1.5_wp*v1 + q1 = 0.5_wp*v1 + 0.5_wp*v2 + + qL(i-1) = w0 * q0 + w1 * q1 + + ! 计算右界面值 qR(i-1) + beta0 = (v1 - v0)**2 + beta1 = (v2 - v1)**2 + + alpha0 = 2.0_wp/3.0_wp / (this%epsilon + beta0)**2 + alpha1 = 1.0_wp/3.0_wp / (this%epsilon + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + + q0 = 0.5_wp*v0 + 0.5_wp*v1 + q1 = 1.5_wp*v1 - 0.5_wp*v2 + + qR(i-1) = w0 * q0 + w1 * q1 + end do + end subroutine weno3_reconstruct + + subroutine weno3_info(this) + class(weno3_reconstructor), intent(in) :: this + call reconstructor_info(this) + print *, " 类型: WENO-3重构器" + end subroutine weno3_info + +end module weno3_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/reconstructor/weno5.f90 b/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/reconstructor/weno5.f90 new file mode 100644 index 000000000..19efbb898 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01b/src/numerics/reconstructor/weno5.f90 @@ -0,0 +1,106 @@ +! src/numerics/reconstructor/weno5.f90 +module weno5_reconstructor_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use reconstructor_base_module, only: reconstructor_base, wp + use registry_module, only: register_component_with_factory + implicit none + + private + public :: wp, weno5_reconstructor, create_weno5 + + type, extends(reconstructor_base) :: weno5_reconstructor + contains + procedure :: reconstruct => weno5_reconstruct + procedure :: info => weno5_info + end type weno5_reconstructor + +contains + + ! 工厂函数 + subroutine create_weno5(instance) + class(*), allocatable, intent(out) :: instance + type(weno5_reconstructor), allocatable :: weno5 + + allocate(weno5) + weno5%name = "WENO5" + weno5%order = 5 + weno5%epsilon = 1.0e-6_wp + + call move_alloc(weno5, instance) + end subroutine create_weno5 + + ! WENO-5重构实现(简化版本) + subroutine weno5_reconstruct(this, q, qL, qR) + class(weno5_reconstructor), intent(in) :: this + real(wp), intent(in) :: q(:) + real(wp), intent(out) :: qL(:), qR(:) + + integer :: i, n + real(wp) :: beta0, beta1, beta2, alpha0, alpha1, alpha2, alpha + real(wp) :: w0, w1, w2, q0, q1, q2 + real(wp) :: v0, v1, v2, v3, v4 + + n = size(q) - 4 ! 需要4个ghost cells + + do i = 3, n+2 + ! 获取模板值 + v0 = q(i-2) + v1 = q(i-1) + v2 = q(i) + v3 = q(i+1) + v4 = q(i+2) + + ! 计算左界面值 qL(i-2) + beta0 = (13.0_wp/12.0_wp)*(v0 - 2.0_wp*v1 + v2)**2 & + + (1.0_wp/4.0_wp)*(v0 - 4.0_wp*v1 + 3.0_wp*v2)**2 + beta1 = (13.0_wp/12.0_wp)*(v1 - 2.0_wp*v2 + v3)**2 & + + (1.0_wp/4.0_wp)*(v1 - v3)**2 + beta2 = (13.0_wp/12.0_wp)*(v2 - 2.0_wp*v3 + v4)**2 & + + (1.0_wp/4.0_wp)*(3.0_wp*v2 - 4.0_wp*v3 + v4)**2 + + alpha0 = 0.1_wp / (this%epsilon + beta0)**2 + alpha1 = 0.6_wp / (this%epsilon + beta1)**2 + alpha2 = 0.3_wp / (this%epsilon + beta2)**2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + + q0 = (1.0_wp/3.0_wp)*v0 - (7.0_wp/6.0_wp)*v1 + (11.0_wp/6.0_wp)*v2 + q1 = (-1.0_wp/6.0_wp)*v1 + (5.0_wp/6.0_wp)*v2 + (1.0_wp/3.0_wp)*v3 + q2 = (1.0_wp/3.0_wp)*v2 + (5.0_wp/6.0_wp)*v3 - (1.0_wp/6.0_wp)*v4 + + qL(i-2) = w0 * q0 + w1 * q1 + w2 * q2 + + ! 计算右界面值 qR(i-2) + ! (使用对称模板) + beta0 = (13.0_wp/12.0_wp)*(v0 - 2.0_wp*v1 + v2)**2 & + + (1.0_wp/4.0_wp)*(v0 - 4.0_wp*v1 + 3.0_wp*v2)**2 + beta1 = (13.0_wp/12.0_wp)*(v1 - 2.0_wp*v2 + v3)**2 & + + (1.0_wp/4.0_wp)*(v1 - v3)**2 + beta2 = (13.0_wp/12.0_wp)*(v2 - 2.0_wp*v3 + v4)**2 & + + (1.0_wp/4.0_wp)*(3.0_wp*v2 - 4.0_wp*v3 + v4)**2 + + alpha0 = 0.3_wp / (this%epsilon + beta0)**2 + alpha1 = 0.6_wp / (this%epsilon + beta1)**2 + alpha2 = 0.1_wp / (this%epsilon + beta2)**2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + + q0 = (-1.0_wp/6.0_wp)*v0 + (5.0_wp/6.0_wp)*v1 + (1.0_wp/3.0_wp)*v2 + q1 = (1.0_wp/3.0_wp)*v1 + (5.0_wp/6.0_wp)*v2 - (1.0_wp/6.0_wp)*v3 + q2 = (11.0_wp/6.0_wp)*v2 - (7.0_wp/6.0_wp)*v3 + (1.0_wp/3.0_wp)*v4 + + qR(i-2) = w0 * q0 + w1 * q1 + w2 * q2 + end do + end subroutine weno5_reconstruct + + subroutine weno5_info(this) + class(weno5_reconstructor), intent(in) :: this + call reconstructor_info(this) + print *, " 类型: WENO-5重构器" + end subroutine weno5_info + +end module weno5_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01b/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01b/tests/CMakeLists.txt new file mode 100644 index 000000000..987d62f15 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01b/tests/CMakeLists.txt @@ -0,0 +1,27 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +# Minimal functionality test +add_executable(test_minimal + test_minimal.f90 +) +target_link_libraries(test_minimal + core + infrastructure +) +target_include_directories(test_minimal PRIVATE + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# Test target +add_custom_target(run_tests + COMMAND echo "=== Running Tests ===" + COMMAND echo "Running minimal test..." + COMMAND test_minimal + COMMAND echo "" + COMMAND echo "=== Tests Completed ===" + DEPENDS test_minimal + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} +) + +message(STATUS "Tests configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01b/tests/test_factory_system.f90 b/example/1d-linear-convection/weno3/fortran/registry/01b/tests/test_factory_system.f90 new file mode 100644 index 000000000..1ef18c124 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01b/tests/test_factory_system.f90 @@ -0,0 +1,86 @@ +! tests/test_factory_system.f90 +program test_factory_system + use registry_advanced_module + use eno_reconstructor_module + use rusanov_flux_module + implicit none + + class(*), allocatable :: instance1, instance2 + type(eno_reconstructor), pointer :: eno_ptr + type(rusanov_flux), pointer :: flux_ptr + real(wp), allocatable :: q(:), qL(:), qR(:), flux(:) + integer :: i + + print *, "=== Factory System Test ===" + print *, "" + + ! 注册工厂 + print *, "1. Registering factories..." + call register_factory_with_order("reconstructor", "eno", create_eno, 3) + call register_factory("flux", "rusanov", create_rusanov) + + call component_registry%list_all() + print *, "" + + ! 创建实例 + print *, "2. Creating instances..." + call component_registry%create("reconstructor", "eno", instance1) + call component_registry%create("flux", "rusanov", instance2) + print *, "" + + ! 类型转换和测试 + print *, "3. Testing instances..." + + ! ENO重构器 + select type (inst => instance1) + type is (eno_reconstructor) + eno_ptr => inst + call eno_ptr%info() + + ! 创建测试数据 + allocate(q(6), qL(5), qR(5)) + do i = 1, 6 + q(i) = real(i, wp) + end do + + ! 计算 + call eno_ptr%compute(q, qL, qR) + print *, " q: ", q + print *, " qL: ", qL + print *, " qR: ", qR + + deallocate(q, qL, qR) + class default + print *, "[ERROR] Wrong type for eno_reconstructor" + end select + + print *, "" + + ! Rusanov通量 + select type (inst => instance2) + type is (rusanov_flux) + flux_ptr => inst + call flux_ptr%info() + + ! 创建测试数据 + allocate(qL(5), qR(5), flux(5)) + do i = 1, 5 + qL(i) = real(i, wp) + qR(i) = real(i + 0.5, wp) + end do + + ! 计算 + call flux_ptr%compute(qL, qR, flux) + print *, " qL: ", qL + print *, " qR: ", qR + print *, " flux: ", flux + + deallocate(qL, qR, flux) + class default + print *, "[ERROR] Wrong type for rusanov_flux" + end select + + print *, "" + print *, "=== Factory System Test Complete ===" + +end program test_factory_system \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01b/tests/test_minimal.f90 b/example/1d-linear-convection/weno3/fortran/registry/01b/tests/test_minimal.f90 new file mode 100644 index 000000000..cf4c93a50 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01b/tests/test_minimal.f90 @@ -0,0 +1,77 @@ +! tests/test_minimal.f90 +program test_minimal + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config%print() + + call config%with_reconstruction("eno", 3) + call config%with_boundary("periodic") + + call config%print() + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", component_registry%size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Minimal test completed successfully ===" + +end program test_minimal \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01b/tests/test_registry_basic.f90 b/example/1d-linear-convection/weno3/fortran/registry/01b/tests/test_registry_basic.f90 new file mode 100644 index 000000000..7c960b5ea --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01b/tests/test_registry_basic.f90 @@ -0,0 +1,68 @@ +! tests/test_registry_basic.f90 +program test_registry_basic + use registry_module + implicit none + + type(component_info) :: info + logical :: found + + print *, "=== 注册系统基础测试 ===" + print *, "" + + ! 1. 初始化 + print *, "1. 初始化注册表" + print *, "----------------" + call initialize_registry(verbose=.true.) + print *, "初始大小: ", component_registry%size() + print *, "" + + ! 2. 注册组件 + print *, "2. 注册组件" + print *, "------------" + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "注册后大小: ", component_registry%size() + print *, "" + + ! 3. 查询组件 + print *, "3. 查询组件" + print *, "------------" + + ! 测试存在的组件 + found = has_component("reconstructor", "eno") + print *, "has_component('reconstructor', 'eno') = ", found + + info = component_registry%get("reconstructor", "eno") + print *, "get('reconstructor', 'eno'):" + call info%print() + + ! 测试不存在的组件 + found = has_component("reconstructor", "unknown") + print *, "has_component('reconstructor', 'unknown') = ", found + + info = component_registry%get("reconstructor", "unknown") + print *, "get('reconstructor', 'unknown'):" + call info%print() + print *, "" + + ! 4. 清理 + print *, "4. 清理注册表" + print *, "--------------" + call cleanup_registry() + print *, "清理后大小: ", component_registry%size() + call component_registry%list_all() + + print *, "" + print *, "=== 基础测试完成 ===" + +end program test_registry_basic \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01c/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01c/CMakeLists.txt new file mode 100644 index 000000000..a2fe5bb7f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01c/CMakeLists.txt @@ -0,0 +1,50 @@ +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + +# Set Fortran standard +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +# Module output directory +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) + +# Compiler flags for Intel Fortran +if(CMAKE_Fortran_COMPILER_ID MATCHES "IntelLLVM|Intel") + # Intel oneAPI/ifx compiler + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS}") + set(CMAKE_Fortran_FLAGS_DEBUG "/debug:full /Od /traceback /check:all /warn:all /fpe:0") + set(CMAKE_Fortran_FLAGS_RELEASE "/O3") + set(CMAKE_Fortran_FLAGS_RELWITHDEBINFO "/O2 /debug:full") +elseif(CMAKE_Fortran_COMPILER_ID MATCHES "GNU") + # GNU gfortran compiler + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -Wall -Wextra") + set(CMAKE_Fortran_FLAGS_DEBUG "-g -O0 -fcheck=all -fbacktrace -ffpe-trap=invalid,zero,overflow") + set(CMAKE_Fortran_FLAGS_RELEASE "-O3 -march=native") + set(CMAKE_Fortran_FLAGS_RELWITHDEBINFO "-O2 -g") +endif() + +# Set default build type +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Build type" FORCE) + message(STATUS "Setting build type to: ${CMAKE_BUILD_TYPE}") +endif() + +message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") + +# Create module directory +file(MAKE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY}) + +# Add subdirectories +add_subdirectory(src) +add_subdirectory(tests) + +# Install directory +set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/install CACHE PATH "Installation prefix") + +message(STATUS "Installation prefix: ${CMAKE_INSTALL_PREFIX}") +message(STATUS "Module directory: ${CMAKE_Fortran_MODULE_DIRECTORY}") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01c/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01c/src/CMakeLists.txt new file mode 100644 index 000000000..ee38952bb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01c/src/CMakeLists.txt @@ -0,0 +1,14 @@ +# ==================== src\CMakeLists.txt ==================== + +# src/CMakeLists.txt +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(core) +add_subdirectory(infrastructure) +add_subdirectory(numerics) + +message(STATUS "源代码目录配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01c/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01c/src/core/CMakeLists.txt new file mode 100644 index 000000000..376dd4fe5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01c/src/core/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/core/CMakeLists.txt +message(STATUS "配置核心模块...") + +add_library(core + registry.f90 + factory_interfaces.f90 +) + +target_include_directories(core PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 安装规则 +install(TARGETS core + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) +install(FILES + ${CMAKE_Fortran_MODULE_DIRECTORY}/registry_module.mod + ${CMAKE_Fortran_MODULE_DIRECTORY}/factory_interfaces.mod + DESTINATION include/fortran_cfd/core +) + +message(STATUS "核心模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01c/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/01c/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..5511c36c3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01c/src/core/factory_interfaces.f90 @@ -0,0 +1,17 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp + + ! Factory procedure interface (simplified) + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01c/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/01c/src/core/registry.f90 new file mode 100644 index 000000000..4e2b3b22f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01c/src/core/registry.f90 @@ -0,0 +1,311 @@ +! src/core/registry.f90 +module registry_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + + ! Public interface + public :: wp, component_info, component_registry + public :: register_component_simple, initialize_registry, cleanup_registry + public :: has_component, get_available_components + + ! Type definitions + type :: component_info + character(len=32) :: category = "" + character(len=32) :: name = "" + integer :: order = 0 + logical :: has_factory = .false. + contains + procedure :: print => ci_print + end type component_info + + type :: component_registry_type + private + type(component_info), allocatable :: components(:) + integer :: count = 0 + integer :: capacity = 100 + logical :: verbose = .true. + logical :: initialized = .false. + contains + procedure :: register => cr_register + procedure :: get => cr_get + procedure :: list_all => cr_list_all + procedure :: clear => cr_clear + procedure :: size => cr_size + end type component_registry_type + + ! Global registry instance + type(component_registry_type), save :: component_registry + +contains + + ! ==================== PUBLIC API ==================== + + ! Initialize registry + subroutine initialize_registry(initial_capacity, verbose) + integer, optional, intent(in) :: initial_capacity + logical, optional, intent(in) :: verbose + + if (component_registry%initialized) then + if (component_registry%verbose) then + print *, "[INFO] Registry already initialized" + end if + return + end if + + if (present(initial_capacity)) then + component_registry%capacity = max(10, initial_capacity) + end if + + if (present(verbose)) then + component_registry%verbose = verbose + end if + + ! Allocate array + allocate(component_registry%components(component_registry%capacity)) + + component_registry%initialized = .true. + component_registry%count = 0 + + if (component_registry%verbose) then + print *, "[INIT] Registry initialized, capacity:", component_registry%capacity + end if + end subroutine initialize_registry + + ! Cleanup registry + subroutine cleanup_registry + call component_registry%clear() + if (component_registry%verbose) then + print *, "[CLEANUP] Registry cleaned up" + end if + end subroutine cleanup_registry + + ! Simple registration + subroutine register_component_simple(category, name) + character(len=*), intent(in) :: category, name + + type(component_info) :: info + + info%category = to_lower(trim(adjustl(category))) + info%name = to_lower(trim(adjustl(name))) + info%order = 0 + info%has_factory = .false. + + call component_registry%register(info) + end subroutine register_component_simple + + ! Check if component exists + function has_component(category, name) result(found) + character(len=*), intent(in) :: category, name + logical :: found + + type(component_info) :: info + character(len=32) :: cat_lower, name_lower + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + info = component_registry%get(cat_lower, name_lower) + found = (len_trim(info%category) > 0) + end function has_component + + ! Get available components in a category + subroutine get_available_components(category, names, orders) + character(len=*), intent(in) :: category + character(len=:), allocatable, intent(out), optional :: names(:) + integer, allocatable, intent(out), optional :: orders(:) + + character(len=32) :: cat_lower + integer :: i, count, idx + type(component_info) :: info + + cat_lower = to_lower(trim(adjustl(category))) + + ! Count components in this category + count = 0 + do i = 1, component_registry%count + if (component_registry%components(i)%category == cat_lower) then + count = count + 1 + end if + end do + + ! Allocate arrays if requested + if (present(names)) then + allocate(character(len=32) :: names(count)) + end if + + if (present(orders)) then + allocate(orders(count)) + end if + + ! Fill arrays + idx = 1 + do i = 1, component_registry%count + if (component_registry%components(i)%category == cat_lower) then + info = component_registry%components(i) + if (present(names)) then + names(idx) = info%name + end if + if (present(orders)) then + orders(idx) = info%order + end if + idx = idx + 1 + end if + end do + end subroutine get_available_components + + ! ==================== COMPONENT INFO METHODS ==================== + + subroutine ci_print(this) + class(component_info), intent(in) :: this + + if (this%order > 0) then + if (this%has_factory) then + print *, " [", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ", has factory)]" + else + print *, " [", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ")]" + end if + else + if (this%has_factory) then + print *, " [", trim(this%category), ".", trim(this%name), & + " (has factory)]" + else + print *, " [", trim(this%category), ".", trim(this%name), "]" + end if + end if + end subroutine ci_print + + ! ==================== REGISTRY INTERNAL METHODS ==================== + + subroutine cr_register(this, info) + class(component_registry_type), intent(inout) :: this + type(component_info), intent(in) :: info + + type(component_info), allocatable :: temp(:) + integer :: i + + if (.not. this%initialized) then + error stop "[ERROR] Registry not initialized, call initialize_registry first" + end if + + ! Check if already exists + do i = 1, this%count + if (this%components(i)%category == info%category .and. & + this%components(i)%name == info%name) then + if (this%verbose) then + print *, "[WARN] Overwriting: ", & + trim(info%category), ".", trim(info%name) + end if + this%components(i) = info + return + end if + end do + + ! Expand array if needed + if (this%count >= this%capacity) then + this%capacity = this%capacity * 2 + allocate(temp(this%capacity)) + temp(1:this%count) = this%components(1:this%count) + call move_alloc(temp, this%components) + + if (this%verbose) then + print *, "[INFO] Registry expanded to capacity:", this%capacity + end if + end if + + ! Add component + this%count = this%count + 1 + this%components(this%count) = info + + if (this%verbose) then + print *, "[OK] Registered: ", trim(info%category), ".", trim(info%name) + end if + end subroutine cr_register + + function cr_get(this, category, name) result(info) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category, name + type(component_info) :: info + + integer :: i + + ! Initialize return value as empty + info%category = "" + info%name = "" + info%order = 0 + info%has_factory = .false. + + if (.not. this%initialized) then + return + end if + + do i = 1, this%count + if (this%components(i)%category == category .and. & + this%components(i)%name == name) then + info = this%components(i) + return + end if + end do + end function cr_get + + subroutine cr_list_all(this) + class(component_registry_type), intent(in) :: this + integer :: i + + if (.not. this%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + print *, "=== Registry Contents (", this%count, " components) ===" + + if (this%count == 0) then + print *, " (empty)" + return + end if + + ! Show components grouped by category + do i = 1, this%count + call this%components(i)%print() + end do + + print *, "===========================================" + end subroutine cr_list_all + + subroutine cr_clear(this) + class(component_registry_type), intent(inout) :: this + + if (allocated(this%components)) then + deallocate(this%components) + end if + + this%count = 0 + this%capacity = 100 + this%initialized = .false. + end subroutine cr_clear + + integer function cr_size(this) + class(component_registry_type), intent(in) :: this + cr_size = this%count + end function cr_size + + ! ==================== UTILITY FUNCTIONS ==================== + + function to_lower(str) result(lower_str) + character(len=*), intent(in) :: str + character(len=len(str)) :: lower_str + integer :: i + + do i = 1, len(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + lower_str(i:i) = char(ichar(str(i:i)) + 32) + else + lower_str(i:i) = str(i:i) + end if + end do + end function to_lower + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01c/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01c/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..2b72c5484 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01c/src/infrastructure/CMakeLists.txt @@ -0,0 +1,20 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "配置基础设施模块...") + +add_library(infrastructure + config.f90 + mesh.f90 +) + +target_link_libraries(infrastructure PUBLIC core) + +target_include_directories(infrastructure PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS infrastructure + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "基础设施模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01c/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/01c/src/infrastructure/config.f90 new file mode 100644 index 000000000..6a646d17e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01c/src/infrastructure/config.f90 @@ -0,0 +1,98 @@ +! src/infrastructure/config.f90 +module config_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, cfd_config, config_print, config_with_reconstruction + + ! CFD configuration type + type :: cfd_config + character(len=20) :: ic_type = "step" + character(len=20) :: recon_scheme = "eno" + character(len=20) :: flux_type = "rusanov" + integer :: rk_order = 1 + real(wp) :: wave_speed = 1.0_wp + real(wp) :: final_time = 0.625_wp + real(wp) :: dt = 0.025_wp + character(len=20) :: boundary_type = "periodic" + real(wp) :: left_boundary_value = 1.0_wp + real(wp) :: right_boundary_value = 2.0_wp + integer :: spatial_order = 2 + logical :: verbose = .true. + end type cfd_config + + ! Interfaces + interface config_print + module procedure config_print_proc + end interface + + interface config_with_reconstruction + module procedure config_with_reconstruction_proc + end interface + +contains + + subroutine config_print_proc(this) + type(cfd_config), intent(in) :: this + + print *, "=== CFD Configuration ===" + print *, "Initial condition: ", trim(this%ic_type) + print *, "Reconstruction: ", trim(this%recon_scheme), " (order:", this%spatial_order, ")" + print *, "Flux type: ", trim(this%flux_type) + print *, "Time integration: RK", this%rk_order + print *, "Wave speed: ", this%wave_speed + print *, "Final time: ", this%final_time + print *, "Time step: ", this%dt + print *, "Boundary: ", trim(this%boundary_type) + if (trim(this%boundary_type) == 'dirichlet') then + print *, " Dirichlet values: [", this%left_boundary_value, ", ", & + this%right_boundary_value, "]" + end if + print *, "===============================" + end subroutine config_print_proc + + subroutine config_with_reconstruction_proc(this, scheme, order) + type(cfd_config), intent(inout) :: this + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + character(len=20) :: scheme_lower + + ! Convert to lowercase + scheme_lower = scheme + call to_lower_inplace(scheme_lower) + this%recon_scheme = trim(adjustl(scheme_lower)) + + ! Set order + if (present(order)) then + this%spatial_order = order + else + ! Smart defaults + if (index(this%recon_scheme, 'weno') > 0) then + this%spatial_order = 5 + else if (trim(this%recon_scheme) == 'eno') then + this%spatial_order = 3 + else + error stop "[ERROR] Unsupported reconstruction scheme: " // trim(this%recon_scheme) + end if + end if + + if (this%verbose) then + print *, "[CONFIG] Reconstruction: ", trim(this%recon_scheme), & + " Order: ", this%spatial_order + end if + end subroutine config_with_reconstruction_proc + + subroutine to_lower_inplace(str) + character(len=*), intent(inout) :: str + integer :: i + + do i = 1, len_trim(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + str(i:i) = char(ichar(str(i:i)) + 32) + end if + end do + end subroutine to_lower_inplace + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01c/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/01c/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..65a45dcdf --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01c/src/infrastructure/mesh.f90 @@ -0,0 +1,74 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, mesh_type, mesh_init, mesh_print_info + + ! 网格类型 + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer :: ncells = 40 + integer :: nnodes + integer :: nx + real(wp), allocatable :: x(:) ! 节点坐标 + real(wp), allocatable :: xcc(:) ! 单元中心坐标 + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer, optional, intent(in) :: ncells + + integer :: i + + ! 设置参数 + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! 计算派生参数 + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! 分配数组 + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! 生成节点坐标 + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! 生成单元中心坐标 + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== 网格信息 ===" + print *, "计算域: [", this%xmin, ", ", this%xmax, "]" + print *, "单元数: ", this%ncells + print *, "节点数: ", this%nnodes + print *, "网格尺寸 dx: ", this%dx + print *, "域长度 L: ", this%L + print *, "==========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01c/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01c/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..66874a7ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01c/src/numerics/CMakeLists.txt @@ -0,0 +1,7 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01c/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01c/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..b2617f290 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01c/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,20 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +# Start with just the base module +add_library(flux + base.f90 +) + +target_link_libraries(flux PUBLIC core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01c/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/01c/src/numerics/flux/base.f90 new file mode 100644 index 000000000..dce85b487 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01c/src/numerics/flux/base.f90 @@ -0,0 +1,24 @@ +! src/numerics/flux/base.f90 +module flux_base_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp + + ! Base flux calculator type (simplified) + type :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure :: info => flux_info + end type flux_calculator_base + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "Flux calculator information:" + print *, " Name: ", trim(this%name) + end subroutine flux_info + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01c/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01c/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..77808c1ad --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01c/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,20 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "Configuring reconstructor module...") + +# Start with just the base module +add_library(reconstructor + base.f90 +) + +target_link_libraries(reconstructor PUBLIC core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Reconstructor module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01c/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/01c/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..c3a1ac8b4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01c/src/numerics/reconstructor/base.f90 @@ -0,0 +1,27 @@ +! src/numerics/reconstructor/base.f90 +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp + + ! Base reconstructor type (simplified) + type :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + contains + procedure :: info => reconstructor_info + end type reconstructor_base + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + + print *, "Reconstructor information:" + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_info + +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01c/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01c/tests/CMakeLists.txt new file mode 100644 index 000000000..987d62f15 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01c/tests/CMakeLists.txt @@ -0,0 +1,27 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +# Minimal functionality test +add_executable(test_minimal + test_minimal.f90 +) +target_link_libraries(test_minimal + core + infrastructure +) +target_include_directories(test_minimal PRIVATE + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# Test target +add_custom_target(run_tests + COMMAND echo "=== Running Tests ===" + COMMAND echo "Running minimal test..." + COMMAND test_minimal + COMMAND echo "" + COMMAND echo "=== Tests Completed ===" + DEPENDS test_minimal + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} +) + +message(STATUS "Tests configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01c/tests/test_minimal.f90 b/example/1d-linear-convection/weno3/fortran/registry/01c/tests/test_minimal.f90 new file mode 100644 index 000000000..bb4b7cda9 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01c/tests/test_minimal.f90 @@ -0,0 +1,108 @@ +! tests/test_minimal.f90 +program test_minimal + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i ! Declare loop variable here + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", component_registry%size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components + print *, "5. Testing available components" + print *, "--------------------------------" + + ! Use a separate block to avoid allocation issues + call test_available_components() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Minimal test completed successfully ===" + +contains + + ! Internal subroutine for testing available components + subroutine test_available_components() + character(len=:), allocatable :: names(:) + integer, allocatable :: orders(:) + integer :: j + + call get_available_components("reconstructor", names, orders) + + if (allocated(names)) then + print *, "Reconstructors:" + do j = 1, size(names) + print *, " - ", trim(names(j)) + if (allocated(orders)) then + print *, " Order: ", orders(j) + end if + end do + else + print *, "No reconstructors found" + end if + end subroutine test_available_components + +end program test_minimal \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01d/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01d/CMakeLists.txt new file mode 100644 index 000000000..a2fe5bb7f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01d/CMakeLists.txt @@ -0,0 +1,50 @@ +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + +# Set Fortran standard +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +# Module output directory +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) + +# Compiler flags for Intel Fortran +if(CMAKE_Fortran_COMPILER_ID MATCHES "IntelLLVM|Intel") + # Intel oneAPI/ifx compiler + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS}") + set(CMAKE_Fortran_FLAGS_DEBUG "/debug:full /Od /traceback /check:all /warn:all /fpe:0") + set(CMAKE_Fortran_FLAGS_RELEASE "/O3") + set(CMAKE_Fortran_FLAGS_RELWITHDEBINFO "/O2 /debug:full") +elseif(CMAKE_Fortran_COMPILER_ID MATCHES "GNU") + # GNU gfortran compiler + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -Wall -Wextra") + set(CMAKE_Fortran_FLAGS_DEBUG "-g -O0 -fcheck=all -fbacktrace -ffpe-trap=invalid,zero,overflow") + set(CMAKE_Fortran_FLAGS_RELEASE "-O3 -march=native") + set(CMAKE_Fortran_FLAGS_RELWITHDEBINFO "-O2 -g") +endif() + +# Set default build type +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Build type" FORCE) + message(STATUS "Setting build type to: ${CMAKE_BUILD_TYPE}") +endif() + +message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") + +# Create module directory +file(MAKE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY}) + +# Add subdirectories +add_subdirectory(src) +add_subdirectory(tests) + +# Install directory +set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/install CACHE PATH "Installation prefix") + +message(STATUS "Installation prefix: ${CMAKE_INSTALL_PREFIX}") +message(STATUS "Module directory: ${CMAKE_Fortran_MODULE_DIRECTORY}") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01d/build.bat b/example/1d-linear-convection/weno3/fortran/registry/01d/build.bat new file mode 100644 index 000000000..dd93a8f0c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01d/build.bat @@ -0,0 +1,72 @@ +@echo off +echo ========================================= +echo Fortran CFD Project Builder (Batch) +echo ========================================= +echo. + +REM 设置 Intel oneAPI 环境 +echo [1/5] Setting up Intel oneAPI environment... +call "C:\Program Files (x86)\Intel\oneAPI\setvars.bat" > nul 2>&1 +if %errorlevel% neq 0 ( + echo [ERROR] Failed to set Intel oneAPI environment + pause + exit /b 1 +) +echo ✓ Intel oneAPI environment set + +REM 清理构建目录 +echo [2/5] Cleaning build directory... +if exist build ( + rmdir /s /q build + echo ✓ Old build directory removed +) else ( + echo - No existing build directory +) +mkdir build +cd build + +REM 配置项目 +echo [3/5] Configuring project with Intel Fortran... +cmake -G "Visual Studio 17 2022" -A x64 -T fortran=ifx .. +if %errorlevel% neq 0 ( + echo [ERROR] CMake configuration failed + pause + exit /b 1 +) +echo ✓ CMake configuration successful + +REM 构建项目 +echo [4/5] Building project... +cmake --build . --config Debug +if %errorlevel% neq 0 ( + echo [ERROR] Build failed + pause + exit /b 1 +) +echo ✓ Build successful + +REM 运行测试 +echo [5/5] Running tests... +echo ========================================= +if exist "Debug\test_simple.exe" ( + echo [TEST] Simple functionality test... + echo ----------------------------------------- + Debug\test_simple.exe + echo. +) else ( + echo [WARN] test_simple.exe not found +) + +if exist "Debug\test_factory.exe" ( + echo [TEST] Factory pattern test... + echo ----------------------------------------- + Debug\test_factory.exe + echo. +) else ( + echo [WARN] test_factory.exe not found +) + +echo ========================================= +echo Build directory: %CD% +echo ========================================= +pause \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01d/build.ps1 b/example/1d-linear-convection/weno3/fortran/registry/01d/build.ps1 new file mode 100644 index 000000000..9b14a8dfd --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01d/build.ps1 @@ -0,0 +1,137 @@ +# Fortran CFD Project Builder (PowerShell) +Write-Host "=========================================" -ForegroundColor Cyan +Write-Host " Fortran CFD Project Builder (PowerShell)" -ForegroundColor Cyan +Write-Host "=========================================" -ForegroundColor Cyan +Write-Host "" + +# 设置 Intel oneAPI 环境 +Write-Host "[1/5] Setting up Intel oneAPI environment..." -ForegroundColor Yellow +$setvarsPath = "C:\Program Files (x86)\Intel\oneAPI\setvars.bat" +if (-not (Test-Path $setvarsPath)) { + Write-Host "[ERROR] Intel oneAPI setvars.bat not found at: $setvarsPath" -ForegroundColor Red + Write-Host "Please check your Intel oneAPI installation." -ForegroundColor Red + Read-Host "Press Enter to exit" + exit 1 +} + +# 调用 setvars.bat 并设置环境变量 +try { + cmd.exe /c "`"$setvarsPath`" > nul 2>&1 && set" | ForEach-Object { + if ($_ -match '^([^=]+)=(.*)$') { + $name = $matches[1] + $value = $matches[2] + [Environment]::SetEnvironmentVariable($name, $value, 'Process') + } + } + Write-Host "✓ Intel oneAPI environment set" -ForegroundColor Green +} +catch { + Write-Host "[ERROR] Failed to set Intel oneAPI environment: $_" -ForegroundColor Red + Read-Host "Press Enter to exit" + exit 1 +} + +# 清理构建目录 +Write-Host "[2/5] Cleaning build directory..." -ForegroundColor Yellow +if (Test-Path "build") { + Remove-Item -Path "build" -Recurse -Force + Write-Host "✓ Old build directory removed" -ForegroundColor Green +} +else { + Write-Host "- No existing build directory" -ForegroundColor Gray +} + +New-Item -ItemType Directory -Path "build" -Force | Out-Null +Set-Location "build" + +# 配置项目 +Write-Host "[3/5] Configuring project with Intel Fortran..." -ForegroundColor Yellow +$cmakeArgs = @('-G', 'Visual Studio 17 2022', '-A', 'x64', '-T', 'fortran=ifx', '..') +try { + & cmake @cmakeArgs + if ($LASTEXITCODE -ne 0) { + throw "CMake configuration failed with exit code: $LASTEXITCODE" + } + Write-Host "✓ CMake configuration successful" -ForegroundColor Green +} +catch { + Write-Host "[ERROR] $_" -ForegroundColor Red + Read-Host "Press Enter to exit" + exit 1 +} + +# 构建项目 +Write-Host "[4/5] Building project..." -ForegroundColor Yellow +try { + & cmake --build . --config Debug + if ($LASTEXITCODE -ne 0) { + throw "Build failed with exit code: $LASTEXITCODE" + } + Write-Host "✓ Build successful" -ForegroundColor Green +} +catch { + Write-Host "[ERROR] $_" -ForegroundColor Red + Read-Host "Press Enter to exit" + exit 1 +} + +# 运行测试 +Write-Host "[5/5] Running tests..." -ForegroundColor Yellow +Write-Host "=========================================" -ForegroundColor Cyan + +$testResults = @() + +# 运行简单测试 +if (Test-Path "Debug\test_simple.exe") { + Write-Host "[TEST] Simple functionality test..." -ForegroundColor Magenta + Write-Host "-----------------------------------------" -ForegroundColor DarkGray + try { + $output = & "Debug\test_simple.exe" + Write-Host $output + $testResults += @{ Name = "Simple Test"; Status = "PASSED" } + Write-Host "" + } + catch { + Write-Host "[ERROR] Simple test failed: $_" -ForegroundColor Red + $testResults += @{ Name = "Simple Test"; Status = "FAILED" } + } +} +else { + Write-Host "[WARN] test_simple.exe not found" -ForegroundColor Yellow +} + +# 运行工厂测试 +if (Test-Path "Debug\test_factory.exe") { + Write-Host "[TEST] Factory pattern test..." -ForegroundColor Magenta + Write-Host "-----------------------------------------" -ForegroundColor DarkGray + try { + $output = & "Debug\test_factory.exe" + Write-Host $output + $testResults += @{ Name = "Factory Test"; Status = "PASSED" } + Write-Host "" + } + catch { + Write-Host "[ERROR] Factory test failed: $_" -ForegroundColor Red + $testResults += @{ Name = "Factory Test"; Status = "FAILED" } + } +} +else { + Write-Host "[WARN] test_factory.exe not found" -ForegroundColor Yellow +} + +# 显示测试总结 +Write-Host "=========================================" -ForegroundColor Cyan +Write-Host "TEST SUMMARY:" -ForegroundColor Cyan +Write-Host "-----------------------------------------" -ForegroundColor DarkGray +foreach ($test in $testResults) { + $color = if ($test.Status -eq "PASSED") { "Green" } else { "Red" } + Write-Host "$($test.Name): $($test.Status)" -ForegroundColor $color +} + +Write-Host "=========================================" -ForegroundColor Cyan +Write-Host "Build directory: $(Get-Location)" -ForegroundColor Cyan +Write-Host "=========================================" -ForegroundColor Cyan + +if ($testResults.Count -eq 0 -or ($testResults.Status -contains "FAILED")) { + Read-Host "Press Enter to exit" +} \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01d/build.py b/example/1d-linear-convection/weno3/fortran/registry/01d/build.py new file mode 100644 index 000000000..e390ec8ad --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01d/build.py @@ -0,0 +1,234 @@ +#!/usr/bin/env python3 +""" +Fortran CFD Project Builder (Python) +支持 Intel oneAPI 环境的自动化构建脚本 +""" + +import os +import sys +import subprocess +import shutil +from pathlib import Path +from typing import List, Tuple, Optional +import platform + +class Color: + """终端颜色代码""" + HEADER = '\033[95m' + BLUE = '\033[94m' + CYAN = '\033[96m' + GREEN = '\033[92m' + YELLOW = '\033[93m' + RED = '\033[91m' + END = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + +def print_step(step_num: int, total_steps: int, message: str): + """打印步骤信息""" + print(f"{Color.CYAN}[{step_num}/{total_steps}]{Color.END} {message}...") + +def print_success(message: str): + """打印成功信息""" + print(f"{Color.GREEN}✓{Color.END} {message}") + +def print_error(message: str): + """打印错误信息""" + print(f"{Color.RED}[ERROR]{Color.END} {message}") + +def print_warning(message: str): + """打印警告信息""" + print(f"{Color.YELLOW}[WARN]{Color.END} {message}") + +def run_command(cmd: List[str], cwd: Optional[str] = None, check: bool = True) -> Tuple[int, str]: + """运行命令并返回结果""" + print(f"{Color.GRAY}Running: {' '.join(cmd)}{Color.END}") + + try: + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + + if result.stdout: + print(result.stdout) + if result.stderr: + print(f"{Color.YELLOW}{result.stderr}{Color.END}") + + if check and result.returncode != 0: + print_error(f"Command failed with exit code: {result.returncode}") + + return result.returncode, result.stdout + + except FileNotFoundError as e: + print_error(f"Command not found: {cmd[0]}") + if check: + raise + return 1, str(e) + +def setup_intel_environment(): + """设置 Intel oneAPI 环境""" + setvars_path = r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" + + if not os.path.exists(setvars_path): + print_error(f"Intel oneAPI setvars.bat not found at: {setvars_path}") + print("Please check your Intel oneAPI installation.") + return False + + # 创建临时的 batch 文件来设置环境 + temp_bat = "setup_intel_env.bat" + with open(temp_bat, 'w') as f: + f.write(f'@echo off\n') + f.write(f'call "{setvars_path}" > nul 2>&1\n') + f.write(f'set\n') # 输出所有环境变量 + + try: + # 运行 batch 文件并捕获环境变量 + env_result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + shell=True, + encoding='utf-8', + errors='ignore' + ) + + # 解析环境变量并设置 + for line in env_result.stdout.split('\n'): + if '=' in line: + key, value = line.split('=', 1) + os.environ[key.strip()] = value.strip() + + os.remove(temp_bat) + print_success("Intel oneAPI environment set") + return True + + except Exception as e: + print_error(f"Failed to set Intel environment: {e}") + if os.path.exists(temp_bat): + os.remove(temp_bat) + return False + +def main(): + """主函数""" + print(f"{Color.CYAN}{'='*50}{Color.END}") + print(f"{Color.BOLD} Fortran CFD Project Builder (Python){Color.END}") + print(f"{Color.CYAN}{'='*50}{Color.END}\n") + + total_steps = 5 + + # 步骤1: 设置 Intel 环境 + print_step(1, total_steps, "Setting up Intel oneAPI environment") + if not setup_intel_environment(): + input("Press Enter to exit...") + sys.exit(1) + + # 步骤2: 清理构建目录 + print_step(2, total_steps, "Cleaning build directory") + build_dir = Path("build") + if build_dir.exists(): + try: + shutil.rmtree(build_dir) + print_success("Old build directory removed") + except Exception as e: + print_error(f"Failed to remove build directory: {e}") + sys.exit(1) + else: + print("- No existing build directory") + + build_dir.mkdir(exist_ok=True) + os.chdir(build_dir) + + # 步骤3: 配置项目 + print_step(3, total_steps, "Configuring project with Intel Fortran") + cmake_cmd = [ + "cmake", + "-G", "Visual Studio 17 2022", + "-A", "x64", + "-T", "fortran=ifx", + ".." + ] + + return_code, _ = run_command(cmake_cmd, check=False) + if return_code != 0: + print_error("CMake configuration failed") + input("Press Enter to exit...") + sys.exit(1) + print_success("CMake configuration successful") + + # 步骤4: 构建项目 + print_step(4, total_steps, "Building project") + build_cmd = ["cmake", "--build", ".", "--config", "Debug"] + + return_code, _ = run_command(build_cmd, check=False) + if return_code != 0: + print_error("Build failed") + input("Press Enter to exit...") + sys.exit(1) + print_success("Build successful") + + # 步骤5: 运行测试 + print_step(5, total_steps, "Running tests") + print(f"{Color.CYAN}{'='*50}{Color.END}") + + test_results = [] + + # 运行简单测试 + test_simple = Path("Debug/test_simple.exe") + if test_simple.exists(): + print(f"{Color.MAGENTA}[TEST] Simple functionality test...{Color.END}") + print(f"{Color.DARK_GRAY}{'-'*50}{Color.END}") + return_code, output = run_command([str(test_simple)], check=False) + if return_code == 0: + print_success("Simple test passed") + test_results.append(("Simple Test", "PASSED", Color.GREEN)) + else: + print_error("Simple test failed") + test_results.append(("Simple Test", "FAILED", Color.RED)) + print() + else: + print_warning("test_simple.exe not found") + + # 运行工厂测试 + test_factory = Path("Debug/test_factory.exe") + if test_factory.exists(): + print(f"{Color.MAGENTA}[TEST] Factory pattern test...{Color.END}") + print(f"{Color.DARK_GRAY}{'-'*50}{Color.END}") + return_code, output = run_command([str(test_factory)], check=False) + if return_code == 0: + print_success("Factory test passed") + test_results.append(("Factory Test", "PASSED", Color.GREEN)) + else: + print_error("Factory test failed") + test_results.append(("Factory Test", "FAILED", Color.RED)) + print() + else: + print_warning("test_factory.exe not found") + + # 显示测试总结 + print(f"{Color.CYAN}{'='*50}{Color.END}") + print(f"{Color.BOLD}TEST SUMMARY:{Color.END}") + print(f"{Color.DARK_GRAY}{'-'*50}{Color.END}") + + for test_name, status, color in test_results: + print(f"{color}{test_name}: {status}{Color.END}") + + print(f"{Color.CYAN}{'='*50}{Color.END}") + print(f"{Color.BOLD}Build directory:{Color.END} {os.getcwd()}") + print(f"{Color.CYAN}{'='*50}{Color.END}") + + # 如果有测试失败,等待用户确认 + if any(status == "FAILED" for _, status, _ in test_results): + input("Press Enter to exit...") + +if __name__ == "__main__": + # 添加颜色支持 + if platform.system() == "Windows": + # Windows 需要启用 ANSI 转义序列 + os.system("") + + main() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01d/build_config.yaml b/example/1d-linear-convection/weno3/fortran/registry/01d/build_config.yaml new file mode 100644 index 000000000..973c10063 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01d/build_config.yaml @@ -0,0 +1,29 @@ +# 构建配置文件 +project: + name: "FortranCFD" + version: "1.0.0" + language: "Fortran" + +intel: + setvars_path: "C:/Program Files (x86)/Intel/oneAPI/setvars.bat" + compiler: "ifx" + +cmake: + generator: "Visual Studio 17 2022" + platform: "x64" + toolset: "fortran=ifx" + build_type: "Debug" + source_dir: ".." + +build: + clean_before_build: true + run_tests: true + +tests: + - name: "test_simple" + executable: "Debug/test_simple.exe" + description: "Simple functionality test" + + - name: "test_factory" + executable: "Debug/test_factory.exe" + description: "Factory pattern test" \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01d/build_with_config.py b/example/1d-linear-convection/weno3/fortran/registry/01d/build_with_config.py new file mode 100644 index 000000000..95b35ae5e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01d/build_with_config.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +""" +配置文件驱动的构建脚本 +""" + +import yaml +import os +import sys +from pathlib import Path +from build import ( # 复用上面的 build.py 中的函数 + Color, print_step, print_success, + print_error, print_warning, run_command +) + +def load_config(config_file="build_config.yaml"): + """加载配置文件""" + try: + with open(config_file, 'r', encoding='utf-8') as f: + config = yaml.safe_load(f) + return config + except FileNotFoundError: + print_error(f"Config file not found: {config_file}") + return None + except yaml.YAMLError as e: + print_error(f"Error parsing config file: {e}") + return None + +def setup_intel_from_config(config): + """根据配置设置 Intel 环境""" + setvars_path = config['intel']['setvars_path'] + + if not os.path.exists(setvars_path): + print_error(f"Intel setvars.bat not found: {setvars_path}") + return False + + # 这里可以复用 build.py 中的 setup_intel_environment 函数 + # 或者简化的版本 + os.environ['PATH'] = f"C:\\Program Files (x86)\\Intel\\oneAPI\\compiler\\2025.2\\bin\\intel64;{os.environ['PATH']}" + return True + +def main(): + """主函数 - 配置文件版本""" + config = load_config() + if not config: + sys.exit(1) + + print(f"{Color.CYAN}{'='*60}{Color.END}") + print(f"{Color.BOLD} {config['project']['name']} v{config['project']['version']} - Builder{Color.END}") + print(f"{Color.CYAN}{'='*60}{Color.END}\n") + + total_steps = 4 + + # 步骤1: 设置环境 + print_step(1, total_steps, f"Setting up {config['intel']['compiler']} compiler") + if not setup_intel_from_config(config): + input("Press Enter to exit...") + sys.exit(1) + print_success(f"{config['intel']['compiler']} environment set") + + # 步骤2: 准备构建目录 + print_step(2, total_steps, "Preparing build directory") + build_dir = Path("build") + + if config['build']['clean_before_build'] and build_dir.exists(): + import shutil + try: + shutil.rmtree(build_dir) + print_success("Cleaned build directory") + except Exception as e: + print_error(f"Failed to clean: {e}") + sys.exit(1) + + build_dir.mkdir(exist_ok=True) + os.chdir(build_dir) + + # 步骤3: 配置和构建 + print_step(3, total_steps, "Configuring and building") + + cmake_cmd = [ + "cmake", + "-G", config['cmake']['generator'], + "-A", config['cmake']['platform'], + "-T", config['cmake']['toolset'], + config['cmake']['source_dir'] + ] + + return_code, _ = run_command(cmake_cmd, check=False) + if return_code != 0: + print_error("CMake configuration failed") + sys.exit(1) + + build_cmd = [ + "cmake", "--build", ".", + "--config", config['cmake']['build_type'] + ] + + return_code, _ = run_command(build_cmd, check=False) + if return_code != 0: + print_error("Build failed") + sys.exit(1) + + print_success("Build completed") + + # 步骤4: 运行测试 + if config['build']['run_tests']: + print_step(4, total_steps, "Running tests") + print(f"{Color.CYAN}{'-'*60}{Color.END}") + + for test in config['tests']: + test_exe = Path(test['executable']) + if test_exe.exists(): + print(f"{Color.MAGENTA}[TEST] {test['description']}...{Color.END}") + return_code, output = run_command([str(test_exe)], check=False) + if return_code == 0: + print_success(f"{test['name']} passed") + else: + print_error(f"{test['name']} failed") + print() + else: + print_warning(f"{test['name']} executable not found: {test_exe}") + + print(f"{Color.CYAN}{'='*60}{Color.END}") + print(f"{Color.BOLD}Build completed successfully!{Color.END}") + print(f"{Color.CYAN}{'='*60}{Color.END}") + +if __name__ == "__main__": + import platform + if platform.system() == "Windows": + os.system("") + main() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01d/clean_build.bat b/example/1d-linear-convection/weno3/fortran/registry/01d/clean_build.bat new file mode 100644 index 000000000..1ebc2c429 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01d/clean_build.bat @@ -0,0 +1,48 @@ +@echo off +echo === Clean Build for Fortran CFD === +echo. + +REM Clean build directory +if exist build rmdir /s /q build +if exist modules rmdir /s /q modules +mkdir build +cd build + +echo 1. Configuring project... +cmake -G "Visual Studio 17 2022" -A x64 -T fortran=ifx .. + +if errorlevel 1 ( + echo Configuration failed! + pause + exit /b 1 +) + +echo. +echo 2. Building project... +cmake --build . --config Debug + +if errorlevel 1 ( + echo Build failed! + pause + exit /b 1 +) + +echo. +echo 3. Running tests... +if exist "Debug\test_simple.exe" ( + echo "=== Simple Test ===" + Debug\test_simple.exe + echo. +) + +if exist "Debug\test_factory.exe" ( + echo "=== Factory Test ===" + Debug\test_factory.exe +) else ( + echo Test executable not found! +) + +echo. +echo === Build process completed === +echo Build directory: %CD% +pause \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01d/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01d/src/CMakeLists.txt new file mode 100644 index 000000000..ee38952bb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01d/src/CMakeLists.txt @@ -0,0 +1,14 @@ +# ==================== src\CMakeLists.txt ==================== + +# src/CMakeLists.txt +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(core) +add_subdirectory(infrastructure) +add_subdirectory(numerics) + +message(STATUS "源代码目录配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01d/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01d/src/core/CMakeLists.txt new file mode 100644 index 000000000..376dd4fe5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01d/src/core/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/core/CMakeLists.txt +message(STATUS "配置核心模块...") + +add_library(core + registry.f90 + factory_interfaces.f90 +) + +target_include_directories(core PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 安装规则 +install(TARGETS core + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) +install(FILES + ${CMAKE_Fortran_MODULE_DIRECTORY}/registry_module.mod + ${CMAKE_Fortran_MODULE_DIRECTORY}/factory_interfaces.mod + DESTINATION include/fortran_cfd/core +) + +message(STATUS "核心模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01d/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/01d/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..5511c36c3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01d/src/core/factory_interfaces.f90 @@ -0,0 +1,17 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp + + ! Factory procedure interface (simplified) + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01d/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/01d/src/core/registry.f90 new file mode 100644 index 000000000..5d8ca6df1 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01d/src/core/registry.f90 @@ -0,0 +1,386 @@ +! src/core/registry.f90 +module registry_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use factory_interfaces, only: factory_procedure + implicit none + + private + + ! Public interface + public :: wp, component_info, component_registry + public :: register_component_simple, register_component_with_factory + public :: create_component, initialize_registry, cleanup_registry + public :: has_component, get_available_components + + ! Type definitions + type :: component_info + character(len=32) :: category = "" + character(len=32) :: name = "" + integer :: order = 0 + procedure(factory_procedure), pointer, nopass :: factory => null() + logical :: has_factory = .false. + contains + procedure :: print => ci_print + procedure :: create => ci_create + end type component_info + + type :: component_registry_type + private + type(component_info), allocatable :: components(:) + integer :: count = 0 + integer :: capacity = 100 + logical :: verbose = .true. + logical :: initialized = .false. + contains + procedure :: register => cr_register + procedure :: get => cr_get + procedure :: list_all => cr_list_all + procedure :: clear => cr_clear + procedure :: size => cr_size + end type component_registry_type + + ! Global registry instance + type(component_registry_type), save :: component_registry + +contains + + ! ==================== PUBLIC API ==================== + + ! Initialize registry + subroutine initialize_registry(initial_capacity, verbose) + integer, optional, intent(in) :: initial_capacity + logical, optional, intent(in) :: verbose + + if (component_registry%initialized) then + if (component_registry%verbose) then + print *, "[INFO] Registry already initialized" + end if + return + end if + + if (present(initial_capacity)) then + component_registry%capacity = max(10, initial_capacity) + end if + + if (present(verbose)) then + component_registry%verbose = verbose + end if + + ! Allocate array + allocate(component_registry%components(component_registry%capacity)) + + component_registry%initialized = .true. + component_registry%count = 0 + + if (component_registry%verbose) then + print *, "[INIT] Registry initialized, capacity:", component_registry%capacity + end if + end subroutine initialize_registry + + ! Cleanup registry + subroutine cleanup_registry + call component_registry%clear() + if (component_registry%verbose) then + print *, "[CLEANUP] Registry cleaned up" + end if + end subroutine cleanup_registry + + ! Simple registration (no factory) + subroutine register_component_simple(category, name) + character(len=*), intent(in) :: category, name + + type(component_info) :: info + + info%category = to_lower(trim(adjustl(category))) + info%name = to_lower(trim(adjustl(name))) + info%order = 0 + info%factory => null() + info%has_factory = .false. + + call component_registry%register(info) + end subroutine register_component_simple + + ! Registration with factory procedure + subroutine register_component_with_factory(category, name, factory_proc, order) + character(len=*), intent(in) :: category, name + procedure(factory_procedure) :: factory_proc + integer, optional, intent(in) :: order + + type(component_info) :: info + + info%category = to_lower(trim(adjustl(category))) + info%name = to_lower(trim(adjustl(name))) + + if (present(order)) then + info%order = order + else + info%order = 0 + end if + + info%factory => factory_proc + info%has_factory = .true. + + call component_registry%register(info) + end subroutine register_component_with_factory + + ! Create component instance using factory + subroutine create_component(category, name, instance) + character(len=*), intent(in) :: category, name + class(*), allocatable, intent(out) :: instance + + type(component_info) :: info + character(len=32) :: cat_lower, name_lower + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + info = component_registry%get(cat_lower, name_lower) + + if (len_trim(info%category) == 0) then + print *, "[ERROR] Component not found: ", trim(cat_lower), ".", trim(name_lower) + return + end if + + if (.not. info%has_factory) then + print *, "[ERROR] Component has no factory: ", trim(cat_lower), ".", trim(name_lower) + return + end if + + if (.not. associated(info%factory)) then + print *, "[ERROR] Factory procedure not associated: ", trim(cat_lower), ".", trim(name_lower) + return + end if + + call info%create(instance) + end subroutine create_component + + ! Check if component exists + function has_component(category, name) result(found) + character(len=*), intent(in) :: category, name + logical :: found + + type(component_info) :: info + character(len=32) :: cat_lower, name_lower + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + info = component_registry%get(cat_lower, name_lower) + found = (len_trim(info%category) > 0) + end function has_component + + ! Get available components in a category + subroutine get_available_components(category, names, orders) + character(len=*), intent(in) :: category + character(len=:), allocatable, intent(out), optional :: names(:) + integer, allocatable, intent(out), optional :: orders(:) + + character(len=32) :: cat_lower + integer :: i, count, idx + type(component_info) :: info + + cat_lower = to_lower(trim(adjustl(category))) + + ! Count components in this category + count = 0 + do i = 1, component_registry%count + if (component_registry%components(i)%category == cat_lower) then + count = count + 1 + end if + end do + + ! Allocate arrays if requested + if (present(names)) then + allocate(character(len=32) :: names(count)) + end if + + if (present(orders)) then + allocate(orders(count)) + end if + + ! Fill arrays + idx = 1 + do i = 1, component_registry%count + if (component_registry%components(i)%category == cat_lower) then + info = component_registry%components(i) + if (present(names)) then + names(idx) = info%name + end if + if (present(orders)) then + orders(idx) = info%order + end if + idx = idx + 1 + end if + end do + end subroutine get_available_components + + ! ==================== COMPONENT INFO METHODS ==================== + + subroutine ci_print(this) + class(component_info), intent(in) :: this + + if (this%order > 0) then + if (this%has_factory) then + print *, " [", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ", has factory)]" + else + print *, " [", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ")]" + end if + else + if (this%has_factory) then + print *, " [", trim(this%category), ".", trim(this%name), & + " (has factory)]" + else + print *, " [", trim(this%category), ".", trim(this%name), "]" + end if + end if + end subroutine ci_print + + subroutine ci_create(this, instance) + class(component_info), intent(in) :: this + class(*), allocatable, intent(out) :: instance + + if (.not. this%has_factory) then + error stop "[ERROR] Component has no factory procedure" + end if + + if (.not. associated(this%factory)) then + error stop "[ERROR] Factory procedure not associated" + end if + + call this%factory(instance) + end subroutine ci_create + + ! ==================== REGISTRY INTERNAL METHODS ==================== + + subroutine cr_register(this, info) + class(component_registry_type), intent(inout) :: this + type(component_info), intent(in) :: info + + type(component_info), allocatable :: temp(:) + integer :: i + + if (.not. this%initialized) then + error stop "[ERROR] Registry not initialized, call initialize_registry first" + end if + + ! Check if already exists + do i = 1, this%count + if (this%components(i)%category == info%category .and. & + this%components(i)%name == info%name) then + if (this%verbose) then + print *, "[WARN] Overwriting: ", & + trim(info%category), ".", trim(info%name) + end if + this%components(i) = info + return + end if + end do + + ! Expand array if needed + if (this%count >= this%capacity) then + this%capacity = this%capacity * 2 + allocate(temp(this%capacity)) + temp(1:this%count) = this%components(1:this%count) + call move_alloc(temp, this%components) + + if (this%verbose) then + print *, "[INFO] Registry expanded to capacity:", this%capacity + end if + end if + + ! Add component + this%count = this%count + 1 + this%components(this%count) = info + + if (this%verbose) then + print *, "[OK] Registered: ", trim(info%category), ".", trim(info%name) + end if + end subroutine cr_register + + function cr_get(this, category, name) result(info) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category, name + type(component_info) :: info + + integer :: i + + ! Initialize return value as empty + info%category = "" + info%name = "" + info%order = 0 + info%factory => null() + info%has_factory = .false. + + if (.not. this%initialized) then + return + end if + + do i = 1, this%count + if (this%components(i)%category == category .and. & + this%components(i)%name == name) then + info = this%components(i) + return + end if + end do + end function cr_get + + subroutine cr_list_all(this) + class(component_registry_type), intent(in) :: this + integer :: i + + if (.not. this%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + print *, "=== Registry Contents (", this%count, " components) ===" + + if (this%count == 0) then + print *, " (empty)" + return + end if + + ! Show components grouped by category + do i = 1, this%count + call this%components(i)%print() + end do + + print *, "===========================================" + end subroutine cr_list_all + + subroutine cr_clear(this) + class(component_registry_type), intent(inout) :: this + + if (allocated(this%components)) then + deallocate(this%components) + end if + + this%count = 0 + this%capacity = 100 + this%initialized = .false. + end subroutine cr_clear + + integer function cr_size(this) + class(component_registry_type), intent(in) :: this + cr_size = this%count + end function cr_size + + ! ==================== UTILITY FUNCTIONS ==================== + + function to_lower(str) result(lower_str) + character(len=*), intent(in) :: str + character(len=len(str)) :: lower_str + integer :: i + + do i = 1, len(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + lower_str(i:i) = char(ichar(str(i:i)) + 32) + else + lower_str(i:i) = str(i:i) + end if + end do + end function to_lower + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01d/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01d/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..2b72c5484 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01d/src/infrastructure/CMakeLists.txt @@ -0,0 +1,20 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "配置基础设施模块...") + +add_library(infrastructure + config.f90 + mesh.f90 +) + +target_link_libraries(infrastructure PUBLIC core) + +target_include_directories(infrastructure PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS infrastructure + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "基础设施模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01d/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/01d/src/infrastructure/config.f90 new file mode 100644 index 000000000..6a646d17e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01d/src/infrastructure/config.f90 @@ -0,0 +1,98 @@ +! src/infrastructure/config.f90 +module config_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, cfd_config, config_print, config_with_reconstruction + + ! CFD configuration type + type :: cfd_config + character(len=20) :: ic_type = "step" + character(len=20) :: recon_scheme = "eno" + character(len=20) :: flux_type = "rusanov" + integer :: rk_order = 1 + real(wp) :: wave_speed = 1.0_wp + real(wp) :: final_time = 0.625_wp + real(wp) :: dt = 0.025_wp + character(len=20) :: boundary_type = "periodic" + real(wp) :: left_boundary_value = 1.0_wp + real(wp) :: right_boundary_value = 2.0_wp + integer :: spatial_order = 2 + logical :: verbose = .true. + end type cfd_config + + ! Interfaces + interface config_print + module procedure config_print_proc + end interface + + interface config_with_reconstruction + module procedure config_with_reconstruction_proc + end interface + +contains + + subroutine config_print_proc(this) + type(cfd_config), intent(in) :: this + + print *, "=== CFD Configuration ===" + print *, "Initial condition: ", trim(this%ic_type) + print *, "Reconstruction: ", trim(this%recon_scheme), " (order:", this%spatial_order, ")" + print *, "Flux type: ", trim(this%flux_type) + print *, "Time integration: RK", this%rk_order + print *, "Wave speed: ", this%wave_speed + print *, "Final time: ", this%final_time + print *, "Time step: ", this%dt + print *, "Boundary: ", trim(this%boundary_type) + if (trim(this%boundary_type) == 'dirichlet') then + print *, " Dirichlet values: [", this%left_boundary_value, ", ", & + this%right_boundary_value, "]" + end if + print *, "===============================" + end subroutine config_print_proc + + subroutine config_with_reconstruction_proc(this, scheme, order) + type(cfd_config), intent(inout) :: this + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + character(len=20) :: scheme_lower + + ! Convert to lowercase + scheme_lower = scheme + call to_lower_inplace(scheme_lower) + this%recon_scheme = trim(adjustl(scheme_lower)) + + ! Set order + if (present(order)) then + this%spatial_order = order + else + ! Smart defaults + if (index(this%recon_scheme, 'weno') > 0) then + this%spatial_order = 5 + else if (trim(this%recon_scheme) == 'eno') then + this%spatial_order = 3 + else + error stop "[ERROR] Unsupported reconstruction scheme: " // trim(this%recon_scheme) + end if + end if + + if (this%verbose) then + print *, "[CONFIG] Reconstruction: ", trim(this%recon_scheme), & + " Order: ", this%spatial_order + end if + end subroutine config_with_reconstruction_proc + + subroutine to_lower_inplace(str) + character(len=*), intent(inout) :: str + integer :: i + + do i = 1, len_trim(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + str(i:i) = char(ichar(str(i:i)) + 32) + end if + end do + end subroutine to_lower_inplace + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01d/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/01d/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..65a45dcdf --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01d/src/infrastructure/mesh.f90 @@ -0,0 +1,74 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, mesh_type, mesh_init, mesh_print_info + + ! 网格类型 + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer :: ncells = 40 + integer :: nnodes + integer :: nx + real(wp), allocatable :: x(:) ! 节点坐标 + real(wp), allocatable :: xcc(:) ! 单元中心坐标 + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer, optional, intent(in) :: ncells + + integer :: i + + ! 设置参数 + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! 计算派生参数 + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! 分配数组 + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! 生成节点坐标 + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! 生成单元中心坐标 + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== 网格信息 ===" + print *, "计算域: [", this%xmin, ", ", this%xmax, "]" + print *, "单元数: ", this%ncells + print *, "节点数: ", this%nnodes + print *, "网格尺寸 dx: ", this%dx + print *, "域长度 L: ", this%L + print *, "==========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..66874a7ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/CMakeLists.txt @@ -0,0 +1,7 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..ca8122dcb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,20 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +add_library(flux + base.f90 + rusanov.f90 +) + +target_link_libraries(flux PUBLIC core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/flux/base.f90 new file mode 100644 index 000000000..bba586a7c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/flux/base.f90 @@ -0,0 +1,35 @@ +! src/numerics/flux/base.f90 +module flux_base_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, flux_calculator_base + + ! Base flux calculator type + type, abstract :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure(compute_interface), deferred :: compute + procedure :: info => flux_info + end type flux_calculator_base + + abstract interface + subroutine compute_interface(this, qL, qR, flux, wave_speed) + import :: flux_calculator_base, wp + class(flux_calculator_base), intent(in) :: this + real(wp), intent(in) :: qL(:), qR(:) + real(wp), intent(out) :: flux(:) + real(wp), intent(in) :: wave_speed + end subroutine compute_interface + end interface + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "Flux calculator information:" + print *, " Name: ", trim(this%name) + end subroutine flux_info + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/flux/rusanov.f90 b/example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/flux/rusanov.f90 new file mode 100644 index 000000000..dc7ac3f58 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/flux/rusanov.f90 @@ -0,0 +1,70 @@ +! src/numerics/flux/rusanov.f90 +module rusanov_flux_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use flux_base_module, only: flux_calculator_base, wp + use registry_module, only: register_component_with_factory + implicit none + + private + public :: wp, rusanov_flux, create_rusanov + + type, extends(flux_calculator_base) :: rusanov_flux + real(wp) :: wave_speed_default = 1.0_wp + contains + procedure :: compute => rusanov_compute + procedure :: info => rusanov_info + end type rusanov_flux + +contains + + ! Factory function + subroutine create_rusanov(instance) + class(*), allocatable, intent(out) :: instance + type(rusanov_flux), allocatable :: flux + + allocate(flux) + flux%name = "Rusanov" + flux%wave_speed_default = 1.0_wp + + call move_alloc(flux, instance) + end subroutine create_rusanov + + ! Rusanov flux computation for linear convection + subroutine rusanov_compute(this, qL, qR, flux, wave_speed) + class(rusanov_flux), intent(in) :: this + real(wp), intent(in) :: qL(:), qR(:) + real(wp), intent(out) :: flux(:) + real(wp), intent(in) :: wave_speed + + integer :: i, n + real(wp) :: max_speed, fL, fR + + n = size(qL) + + if (size(qR) /= n .or. size(flux) /= n) then + print *, "[ERROR] Array size mismatch in rusanov_compute" + return + end if + + ! Maximum wave speed + max_speed = abs(wave_speed) + + ! Compute Rusanov flux at each interface + do i = 1, n + ! Linear convection flux: f(u) = wave_speed * u + fL = wave_speed * qL(i) + fR = wave_speed * qR(i) + + ! Rusanov flux formula + flux(i) = 0.5_wp * (fL + fR) - 0.5_wp * max_speed * (qR(i) - qL(i)) + end do + end subroutine rusanov_compute + + subroutine rusanov_info(this) + class(rusanov_flux), intent(in) :: this + call flux_info(this) + print *, " Type: Rusanov flux" + print *, " Default wave speed: ", this%wave_speed_default + end subroutine rusanov_info + +end module rusanov_flux_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..9d8f99399 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,21 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "Configuring reconstructor module...") + +add_library(reconstructor + base.f90 + eno.f90 + weno3.f90 +) + +target_link_libraries(reconstructor PUBLIC core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Reconstructor module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..982cf824c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/reconstructor/base.f90 @@ -0,0 +1,38 @@ +! src/numerics/reconstructor/base.f90 +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, reconstructor_base + + ! Base reconstructor type + type, abstract :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + contains + procedure(reconstruct_interface), deferred :: reconstruct + procedure :: info => reconstructor_info + end type reconstructor_base + + abstract interface + subroutine reconstruct_interface(this, q, qL, qR) + import :: reconstructor_base, wp + class(reconstructor_base), intent(in) :: this + real(wp), intent(in) :: q(:) + real(wp), intent(out) :: qL(:) + real(wp), intent(out) :: qR(:) + end subroutine reconstruct_interface + end interface + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + + print *, "Reconstructor information:" + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_info + +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/reconstructor/eno.f90 b/example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/reconstructor/eno.f90 new file mode 100644 index 000000000..c117e4b5f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/reconstructor/eno.f90 @@ -0,0 +1,70 @@ +! src/numerics/reconstructor/eno.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use reconstructor_base_module, only: reconstructor_base, wp + use registry_module, only: register_component_with_factory + implicit none + + private + public :: wp, eno_reconstructor, create_eno + + type, extends(reconstructor_base) :: eno_reconstructor + real(wp) :: epsilon = 1.0e-6_wp + contains + procedure :: reconstruct => eno_reconstruct + procedure :: info => eno_info + end type eno_reconstructor + +contains + + ! Factory function + subroutine create_eno(instance) + class(*), allocatable, intent(out) :: instance + type(eno_reconstructor), allocatable :: eno + + allocate(eno) + eno%name = "ENO" + eno%order = 3 + eno%epsilon = 1.0e-6_wp + + call move_alloc(eno, instance) + end subroutine create_eno + + ! ENO reconstruction (simplified 3rd order) + subroutine eno_reconstruct(this, q, qL, qR) + class(eno_reconstructor), intent(in) :: this + real(wp), intent(in) :: q(:) + real(wp), intent(out) :: qL(:) + real(wp), intent(out) :: qR(:) + + integer :: i, n + + n = size(q) - 1 ! Number of interfaces + + if (n /= size(qL) .or. n /= size(qR)) then + print *, "[ERROR] Array size mismatch in eno_reconstruct" + return + end if + + ! Simple implementation: 3rd order ENO + do i = 1, n + if (i >= 2 .and. i <= n-1) then + ! 3-point stencil for 3rd order + qL(i) = (1.0_wp/3.0_wp)*q(i-1) - (7.0_wp/6.0_wp)*q(i) + (11.0_wp/6.0_wp)*q(i+1) + qR(i) = (-1.0_wp/6.0_wp)*q(i) + (5.0_wp/6.0_wp)*q(i+1) + (1.0_wp/3.0_wp)*q(i+2) + else + ! Use simple upwind near boundaries + qL(i) = q(i) + qR(i) = q(i+1) + end if + end do + end subroutine eno_reconstruct + + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + call reconstructor_info(this) + print *, " Type: ENO reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/reconstructor/weno3.f90 b/example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/reconstructor/weno3.f90 new file mode 100644 index 000000000..486771f94 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01d/src/numerics/reconstructor/weno3.f90 @@ -0,0 +1,91 @@ +! src/numerics/reconstructor/weno3.f90 +module weno3_reconstructor_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use reconstructor_base_module, only: reconstructor_base, wp + use registry_module, only: register_component_with_factory + implicit none + + private + public :: wp, weno3_reconstructor, create_weno3 + + type, extends(reconstructor_base) :: weno3_reconstructor + real(wp) :: epsilon = 1.0e-6_wp + contains + procedure :: reconstruct => weno3_reconstruct + procedure :: info => weno3_info + end type weno3_reconstructor + +contains + + ! Factory function + subroutine create_weno3(instance) + class(*), allocatable, intent(out) :: instance + type(weno3_reconstructor), allocatable :: weno3 + + allocate(weno3) + weno3%name = "WENO3" + weno3%order = 3 + weno3%epsilon = 1.0e-6_wp + + call move_alloc(weno3, instance) + end subroutine create_weno3 + + ! WENO-3 reconstruction + subroutine weno3_reconstruct(this, q, qL, qR) + class(weno3_reconstructor), intent(in) :: this + real(wp), intent(in) :: q(:) + real(wp), intent(out) :: qL(:) + real(wp), intent(out) :: qR(:) + + integer :: i, n + real(wp) :: beta0, beta1, alpha0, alpha1, alpha, w0, w1 + real(wp) :: q0, q1 + + n = size(q) - 2 ! Need 2 ghost cells + + if (n /= size(qL) .or. n /= size(qR)) then + print *, "[ERROR] Array size mismatch in weno3_reconstruct" + return + end if + + do i = 2, n+1 + ! Left interface value qL(i-1) + beta0 = (q(i) - q(i-1))**2 + beta1 = (q(i+1) - q(i))**2 + + alpha0 = 1.0_wp/3.0_wp / (this%epsilon + beta0)**2 + alpha1 = 2.0_wp/3.0_wp / (this%epsilon + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + + q0 = -0.5_wp*q(i-1) + 1.5_wp*q(i) + q1 = 0.5_wp*q(i) + 0.5_wp*q(i+1) + + qL(i-1) = w0 * q0 + w1 * q1 + + ! Right interface value qR(i-1) + beta0 = (q(i) - q(i-1))**2 + beta1 = (q(i+1) - q(i))**2 + + alpha0 = 2.0_wp/3.0_wp / (this%epsilon + beta0)**2 + alpha1 = 1.0_wp/3.0_wp / (this%epsilon + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + + q0 = 0.5_wp*q(i-1) + 0.5_wp*q(i) + q1 = 1.5_wp*q(i) - 0.5_wp*q(i+1) + + qR(i-1) = w0 * q0 + w1 * q1 + end do + end subroutine weno3_reconstruct + + subroutine weno3_info(this) + class(weno3_reconstructor), intent(in) :: this + call reconstructor_info(this) + print *, " Type: WENO-3 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno3_info + +end module weno3_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01d/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01d/tests/CMakeLists.txt new file mode 100644 index 000000000..681bfdaa5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01d/tests/CMakeLists.txt @@ -0,0 +1,45 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +# Simple functionality test +add_executable(test_simple + test_minimal_simple.f90 +) +target_link_libraries(test_simple + core + infrastructure +) +target_include_directories(test_simple PRIVATE + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# Factory pattern test +add_executable(test_factory + test_factory.f90 +) +target_link_libraries(test_factory + core + infrastructure + reconstructor + flux +) +target_include_directories(test_factory PRIVATE + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# Test targets +add_custom_target(run_all_tests + COMMAND echo "=== Running All Tests ===" + COMMAND echo "" + COMMAND echo "1. Simple functionality test..." + COMMAND test_simple + COMMAND echo "" + COMMAND echo "2. Factory pattern test..." + COMMAND test_factory + COMMAND echo "" + COMMAND echo "=== All Tests Completed ===" + DEPENDS test_simple test_factory + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} +) + +message(STATUS "Tests configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01d/tests/test_factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/01d/tests/test_factory.f90 new file mode 100644 index 000000000..8968b9c28 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01d/tests/test_factory.f90 @@ -0,0 +1,154 @@ +! tests/test_factory.f90 +program test_factory + use registry_module + use config_module + use mesh_module + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + class(*), allocatable :: instance + class(reconstructor_base), allocatable :: recon + class(flux_calculator_base), allocatable :: flux_calc + real(wp), allocatable :: q(:), qL(:), qR(:), flux(:) + integer :: i, n + + print *, "=== Factory Pattern Test ===" + print *, "" + + ! Initialize systems + call initialize_registry(verbose=.true.) + + ! Register components with factories + print *, "1. Registering components with factories..." + call register_component_with_factory("reconstructor", "eno", create_eno, 3) + call register_component_with_factory("reconstructor", "weno3", create_weno3, 3) + call register_component_with_factory("flux", "rusanov", create_rusanov) + + call component_registry%list_all() + print *, "" + + ! Test creating ENO reconstructor + print *, "2. Creating ENO reconstructor..." + call create_component("reconstructor", "eno", instance) + + if (allocated(instance)) then + select type (inst => instance) + type is (eno_reconstructor) + allocate(recon, source=inst) + print *, "ENO reconstructor created successfully" + call recon%info() + print *, "" + + ! Test reconstruction + n = 10 + allocate(q(0:n+1), qL(n), qR(n)) + + ! Initialize test data (sine wave) + do i = 0, n+1 + q(i) = sin(2.0_wp * 3.141592653589793_wp * real(i-1, wp) / real(n, wp)) + end do + + print *, "Testing ENO reconstruction..." + call recon%reconstruct(q, qL, qR) + + print *, "q (internal):" + do i = 1, n + write(*, '(I3, F10.6)') i, q(i) + end do + + print *, "qL (left interface values):" + do i = 1, n + write(*, '(I3, F10.6)') i, qL(i) + end do + + deallocate(q, qL, qR) + class default + print *, "[ERROR] Wrong type for ENO reconstructor" + end select + else + print *, "[ERROR] Failed to create ENO reconstructor" + end if + print *, "" + + ! Test creating WENO3 reconstructor + print *, "3. Creating WENO3 reconstructor..." + if (allocated(instance)) deallocate(instance) + call create_component("reconstructor", "weno3", instance) + + if (allocated(instance)) then + select type (inst => instance) + type is (weno3_reconstructor) + if (allocated(recon)) deallocate(recon) + allocate(recon, source=inst) + print *, "WENO3 reconstructor created successfully" + call recon%info() + class default + print *, "[ERROR] Wrong type for WENO3 reconstructor" + end select + else + print *, "[ERROR] Failed to create WENO3 reconstructor" + end if + print *, "" + + ! Test creating Rusanov flux calculator + print *, "4. Creating Rusanov flux calculator..." + if (allocated(instance)) deallocate(instance) + call create_component("flux", "rusanov", instance) + + if (allocated(instance)) then + select type (inst => instance) + type is (rusanov_flux) + allocate(flux_calc, source=inst) + print *, "Rusanov flux calculator created successfully" + call flux_calc%info() + print *, "" + + ! Test flux computation + n = 5 + allocate(qL(n), qR(n), flux(n)) + + ! Initialize test data + do i = 1, n + qL(i) = 1.0_wp + 0.1_wp * real(i-1, wp) + qR(i) = 1.0_wp + 0.1_wp * real(i, wp) + end do + + print *, "Testing Rusanov flux computation..." + call flux_calc%compute(qL, qR, flux, 1.0_wp) + + print *, "qL:" + do i = 1, n + write(*, '(I3, F10.6)') i, qL(i) + end do + + print *, "qR:" + do i = 1, n + write(*, '(I3, F10.6)') i, qR(i) + end do + + print *, "Flux:" + do i = 1, n + write(*, '(I3, F10.6)') i, flux(i) + end do + + deallocate(qL, qR, flux) + class default + print *, "[ERROR] Wrong type for Rusanov flux" + end select + else + print *, "[ERROR] Failed to create Rusanov flux calculator" + end if + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Factory pattern test completed successfully ===" + +end program test_factory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01d/tests/test_minimal.f90 b/example/1d-linear-convection/weno3/fortran/registry/01d/tests/test_minimal.f90 new file mode 100644 index 000000000..bb4b7cda9 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01d/tests/test_minimal.f90 @@ -0,0 +1,108 @@ +! tests/test_minimal.f90 +program test_minimal + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i ! Declare loop variable here + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", component_registry%size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components + print *, "5. Testing available components" + print *, "--------------------------------" + + ! Use a separate block to avoid allocation issues + call test_available_components() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Minimal test completed successfully ===" + +contains + + ! Internal subroutine for testing available components + subroutine test_available_components() + character(len=:), allocatable :: names(:) + integer, allocatable :: orders(:) + integer :: j + + call get_available_components("reconstructor", names, orders) + + if (allocated(names)) then + print *, "Reconstructors:" + do j = 1, size(names) + print *, " - ", trim(names(j)) + if (allocated(orders)) then + print *, " Order: ", orders(j) + end if + end do + else + print *, "No reconstructors found" + end if + end subroutine test_available_components + +end program test_minimal \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01d/tests/test_minimal_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/01d/tests/test_minimal_simple.f90 new file mode 100644 index 000000000..bb4b7cda9 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01d/tests/test_minimal_simple.f90 @@ -0,0 +1,108 @@ +! tests/test_minimal.f90 +program test_minimal + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i ! Declare loop variable here + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", component_registry%size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components + print *, "5. Testing available components" + print *, "--------------------------------" + + ! Use a separate block to avoid allocation issues + call test_available_components() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Minimal test completed successfully ===" + +contains + + ! Internal subroutine for testing available components + subroutine test_available_components() + character(len=:), allocatable :: names(:) + integer, allocatable :: orders(:) + integer :: j + + call get_available_components("reconstructor", names, orders) + + if (allocated(names)) then + print *, "Reconstructors:" + do j = 1, size(names) + print *, " - ", trim(names(j)) + if (allocated(orders)) then + print *, " Order: ", orders(j) + end if + end do + else + print *, "No reconstructors found" + end if + end subroutine test_available_components + +end program test_minimal \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01e/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01e/CMakeLists.txt new file mode 100644 index 000000000..02eba69ef --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01e/CMakeLists.txt @@ -0,0 +1,43 @@ +# 根目录CMakeLists.txt +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + +# 设置Fortran标准 +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +# 模块输出目录 +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) + +# 编译器选项 +if(CMAKE_Fortran_COMPILER_ID MATCHES "IntelLLVM|Intel") + set(CMAKE_Fortran_FLAGS_DEBUG "/debug:full /Od /traceback /check:all /warn:all /fpe:0") + set(CMAKE_Fortran_FLAGS_RELEASE "/O3") + set(CMAKE_Fortran_FLAGS_RELWITHDEBINFO "/O2 /debug:full") +endif() + +# 设置默认构建类型 +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Build type" FORCE) + message(STATUS "Setting default build type to: ${CMAKE_BUILD_TYPE}") +endif() + +message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") + +# 确保模块目录存在 +file(MAKE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(src) +add_subdirectory(tests) + +# 安装目录 +set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/install CACHE PATH "Installation prefix") + +message(STATUS "Installation prefix: ${CMAKE_INSTALL_PREFIX}") +message(STATUS "Module directory: ${CMAKE_Fortran_MODULE_DIRECTORY}") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01e/README.md b/example/1d-linear-convection/weno3/fortran/registry/01e/README.md new file mode 100644 index 000000000..c4889757b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01e/README.md @@ -0,0 +1,221 @@ +# Fortran CFD Project + +基于工厂模式和注册系统的CFD求解器框架。 + +## 项目结构 + +``` +fortran_cfd/ +├── CMakeLists.txt # 根CMake配置 +├── scripts/ # 构建脚本 +│ ├── build.py # Python构建脚本(推荐) +│ ├── build.bat # Batch包装脚本 +│ ├── build.ps1 # PowerShell包装脚本 +│ └── requirements.txt # Python依赖 +├── src/ # 源代码 +│ ├── CMakeLists.txt +│ ├── core/ # 核心模块(注册系统、工厂接口) +│ ├── infrastructure/ # 基础设施(配置、网格) +│ └── numerics/ # 数值方法 +├── tests/ # 测试 +│ ├── CMakeLists.txt +│ ├── test_minimal_simple.f90 # 简单测试 +│ └── test_factory.f90 # 工厂模式测试 +└── README.md # 项目说明(本文件) +``` + +## 快速开始 + +### 使用Python脚本(推荐) + +```bash +# 进入脚本目录 +cd scripts + +# 基本构建 +python build.py + +# 清理并构建 +python build.py --clean + +# 发布构建 +python build.py --build-type Release --clean + +# 并行构建(使用所有核心) +python build.py -j + +# 不运行测试 +python build.py --no-tests + +# 查看帮助 +python build.py --help +``` + +### 使用批处理脚本 + +```bash +cd scripts +.\build.bat +``` + +### 使用PowerShell脚本 + +```powershell +cd scripts +.\build.ps1 +``` + +## 手动构建 + +```bash +# 设置Intel环境 +cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && powershell' + +# 配置项目 +mkdir build +cd build +cmake -G "Visual Studio 17 2022" -A x64 -T fortran=ifx .. + +# 构建项目 +cmake --build . --config Debug + +# 运行测试 +Debug\test_simple.exe +Debug\test_factory.exe +``` + +## 功能特性 + +✅ 组件注册系统 +✅ 工厂模式 +✅ ENO/WENO重构器 +✅ Rusanov通量计算器 +✅ 可扩展架构 +✅ 自动化测试 + +## 依赖 + +- Intel oneAPI(包含ifx编译器) +- CMake 3.12+ +- Python 3.6+(仅用于构建脚本) + +## 源代码文件 + +### 核心模块 +- `src/core/registry.f90` - 组件注册系统 +- `src/core/factory_interfaces.f90` - 工厂接口 + +### 基础设施 +- `src/infrastructure/config.f90` - 配置管理 +- `src/infrastructure/mesh.f90` - 网格生成 + +### 数值方法 +- `src/numerics/reconstructor/base.f90` - 重构器基类 +- `src/numerics/reconstructor/eno.f90` - ENO重构器 +- `src/numerics/reconstructor/weno3.f90` - WENO-3重构器 +- `src/numerics/flux/base.f90` - 通量计算器基类 +- `src/numerics/flux/rusanov.f90` - Rusanov通量 + +### 测试 +- `tests/test_minimal_simple.f90` - 简单功能测试 +- `tests/test_factory.f90` - 工厂模式测试 + +## 构建脚本 + +### Python脚本 (build.py) +主构建脚本,支持: +- 自动设置Intel oneAPI环境 +- 参数化构建选项 +- 并行编译 +- 自动化测试 +- 彩色输出 + +### Batch脚本 (build.bat) +Windows批处理包装器,调用Python脚本。 + +### PowerShell脚本 (build.ps1) +PowerShell包装器,调用Python脚本。 + +## 示例输出 + +成功构建后,会看到类似输出: + +``` +============================================================ + Fortran CFD Project Builder +============================================================ + +[1/4] Setting up Intel Fortran compiler... +✓ Intel oneAPI environment configured + +[2/4] Preparing build directory... +✓ Cleaned build directory + +[3/4] Configuring project... + $ cmake -G Visual Studio 17 2022 -A x64 -T fortran=ifx -DCMAKE_BUILD_TYPE=Debug .. +✓ CMake configuration successful + +[4/4] Building project... + $ cmake --build . --config Debug +✓ Build completed in 12.3 seconds + +============================================================ + Running Tests +============================================================ + +[TEST] Simple functionality test... +✓ Simple test passed + +[TEST] Factory pattern test... +✓ Factory test passed + +============================================================ + Build Summary +============================================================ +✓ Build directory: D:\path\to\build +✓ Build type: Debug +✓ Total time: 15.2s +``` + +## 开发指南 + +### 添加新组件 + +1. 在相应模块中创建Fortran源文件 +2. 实现工厂函数 +3. 使用`register_component_with_factory`注册组件 +4. 添加测试 + +### 扩展架构 + +项目采用模块化设计: +1. **注册系统** - 管理所有可用的组件 +2. **工厂模式** - 动态创建组件实例 +3. **抽象接口** - 确保组件兼容性 +4. **配置驱动** - 通过配置文件控制行为 + +## 故障排除 + +### 常见问题 + +1. **Intel环境未找到** + - 检查Intel oneAPI安装路径 + - 手动运行`setvars.bat` + +2. **CMake配置失败** + - 确保使用正确的生成器:`Visual Studio 17 2022` + - 检查Fortran编译器是否可用 + +3. **构建失败** + - 检查依赖项是否完整 + - 查看详细的错误信息 + +### 调试建议 + +- 使用`--verbose`参数获取详细输出 +- 检查`build/CMakeCache.txt`了解配置详情 +- 查看编译器错误日志 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01e/scripts/build.bat b/example/1d-linear-convection/weno3/fortran/registry/01e/scripts/build.bat new file mode 100644 index 000000000..d243695b0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01e/scripts/build.bat @@ -0,0 +1,26 @@ +@echo off +echo ======================================== +echo Fortran CFD Project Builder +echo (Wrapper for Python script) +echo ======================================== +echo. + +REM 检查Python +where python >nul 2>nul +if %errorlevel% neq 0 ( + echo [ERROR] Python not found. Please install Python 3. + pause + exit /b 1 +) + +REM 运行Python构建脚本 +echo [INFO] Running Python build script... +python build.py %* + +if %errorlevel% neq 0 ( + echo [ERROR] Build failed + pause + exit /b 1 +) + +pause \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01e/scripts/build.ps1 b/example/1d-linear-convection/weno3/fortran/registry/01e/scripts/build.ps1 new file mode 100644 index 000000000..4497c5ebd --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01e/scripts/build.ps1 @@ -0,0 +1,32 @@ +# Fortran CFD Project Builder (PowerShell wrapper) + +Write-Host "========================================" -ForegroundColor Cyan +Write-Host " Fortran CFD Project Builder" -ForegroundColor Cyan +Write-Host " (PowerShell wrapper for Python script)" -ForegroundColor Cyan +Write-Host "========================================" -ForegroundColor Cyan +Write-Host "" + +# 检查Python +$python = Get-Command python -ErrorAction SilentlyContinue +if (-not $python) { + $python = Get-Command python3 -ErrorAction SilentlyContinue +} + +if (-not $python) { + Write-Host "[ERROR] Python not found. Please install Python 3." -ForegroundColor Red + Read-Host "Press Enter to exit" + exit 1 +} + +# 运行Python构建脚本 +Write-Host "[INFO] Running Python build script..." -ForegroundColor Yellow +$argsString = $args -join ' ' +$command = "python build.py $argsString" + +Invoke-Expression $command + +if ($LASTEXITCODE -ne 0) { + Write-Host "[ERROR] Build failed" -ForegroundColor Red + Read-Host "Press Enter to exit" + exit 1 +} \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01e/scripts/build.py b/example/1d-linear-convection/weno3/fortran/registry/01e/scripts/build.py new file mode 100644 index 000000000..50038e86c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01e/scripts/build.py @@ -0,0 +1,331 @@ +#!/usr/bin/env python3 +""" +Fortran CFD 项目构建脚本 (Python版) +支持 Intel oneAPI 环境的自动化构建 +""" + +import os +import sys +import subprocess +import shutil +from pathlib import Path +import argparse +import platform +import time + +class Colors: + """终端颜色""" + if platform.system() == "Windows": + # Windows 启用 ANSI + os.system("") + + HEADER = '\033[95m' + BLUE = '\033[94m' + CYAN = '\033[96m' + GREEN = '\033[92m' + YELLOW = '\033[93m' + RED = '\033[91m' + END = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + +def print_color(text, color=Colors.END): + """彩色打印""" + print(f"{color}{text}{Colors.END}") + +def print_header(text): + """打印标题""" + print_color("\n" + "="*60, Colors.CYAN) + print_color(f" {text}", Colors.BOLD + Colors.CYAN) + print_color("="*60 + "\n", Colors.CYAN) + +def print_step(step, total, message): + """打印步骤""" + print_color(f"[{step}/{total}] {message}...", Colors.YELLOW) + +def print_success(message): + """打印成功""" + print_color(f"✓ {message}", Colors.GREEN) + +def print_error(message): + """打印错误""" + print_color(f"✗ {message}", Colors.RED) + +def print_warning(message): + """打印警告""" + print_color(f"! {message}", Colors.YELLOW) + +def run_command(cmd, cwd=None, check=True, capture=True): + """运行命令""" + print_color(f" $ {' '.join(cmd)}", Colors.BLUE) + + try: + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=capture, + text=True, + encoding='utf-8', + errors='ignore', + shell=False + ) + + if capture and result.stdout: + print(result.stdout) + if capture and result.stderr: + print_color(result.stderr, Colors.YELLOW) + + if check and result.returncode != 0: + print_error(f"Command failed with exit code: {result.returncode}") + return False + + return True + + except FileNotFoundError as e: + print_error(f"Command not found: {cmd[0]}") + if check: + raise + return False + except Exception as e: + print_error(f"Command execution failed: {e}") + return False + +def setup_intel_environment(): + """设置 Intel oneAPI 环境""" + setvars_paths = [ + r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat", + r"C:\Program Files\Intel\oneAPI\setvars.bat", + ] + + setvars_path = None + for path in setvars_paths: + if os.path.exists(path): + setvars_path = path + break + + if not setvars_path: + print_error("Intel oneAPI setvars.bat not found.") + print_warning("Please install Intel oneAPI or update the path in build.py") + return False + + # 创建临时的 batch 文件 + temp_bat = "temp_setvars.bat" + with open(temp_bat, 'w') as f: + f.write(f'@echo off\n') + f.write(f'call "{setvars_path}" > nul 2>&1\n') + f.write(f'set\n') + + try: + # 运行并捕获环境变量 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + shell=True, + encoding='utf-8', + errors='ignore' + ) + + # 解析并设置环境变量 + for line in result.stdout.split('\n'): + if '=' in line: + key, value = line.split('=', 1) + os.environ[key.strip()] = value.strip() + + os.remove(temp_bat) + print_success("Intel oneAPI environment configured") + return True + + except Exception as e: + print_error(f"Failed to setup Intel environment: {e}") + if os.path.exists(temp_bat): + os.remove(temp_bat) + return False + +def build_project(args): + """构建项目主函数""" + start_time = time.time() + + print_header(f"Fortran CFD Project Builder") + print_color(f"Build type: {args.build_type}", Colors.CYAN) + print_color(f"Run tests: {args.run_tests}", Colors.CYAN) + print() + + # 获取项目根目录(脚本所在目录的父目录) + script_dir = Path(__file__).parent + project_root = script_dir.parent + os.chdir(project_root) + + print_color(f"Project root: {project_root}", Colors.BLUE) + + # 步骤1: 设置 Intel 环境 + print_step(1, 4, "Setting up Intel Fortran compiler") + if not setup_intel_environment(): + return False + + # 步骤2: 准备构建目录 + print_step(2, 4, "Preparing build directory") + build_dir = project_root / "build" + + if args.clean and build_dir.exists(): + try: + shutil.rmtree(build_dir) + print_success("Cleaned build directory") + except Exception as e: + print_error(f"Failed to clean build directory: {e}") + if not args.force: + return False + + build_dir.mkdir(exist_ok=True) + os.chdir(build_dir) + + # 步骤3: 配置项目 + print_step(3, 4, "Configuring project") + + cmake_cmd = [ + "cmake", + "-G", "Visual Studio 17 2022", + "-A", "x64", + "-T", "fortran=ifx", + f"-DCMAKE_BUILD_TYPE={args.build_type}", + ".." + ] + + if not run_command(cmake_cmd, check=not args.force): + if not args.force: + return False + + # 步骤4: 构建项目 + print_step(4, 4, "Building project") + + build_cmd = [ + "cmake", + "--build", ".", + "--config", args.build_type, + f"-j{args.jobs}" if args.jobs > 1 else "" + ] + build_cmd = [c for c in build_cmd if c] # 移除空字符串 + + if not run_command(build_cmd, check=not args.force): + if not args.force: + return False + + build_time = time.time() - start_time + print_success(f"Build completed in {build_time:.1f} seconds") + + # 运行测试 + if args.run_tests: + print_header("Running Tests") + run_tests(args.build_type) + + print_header("Build Summary") + print_color(f"Build directory: {build_dir}", Colors.GREEN) + print_color(f"Build type: {args.build_type}", Colors.GREEN) + print_color(f"Total time: {build_time:.1f}s", Colors.GREEN) + + return True + +def run_tests(build_type): + """运行测试""" + tests = [ + ("test_simple", "Simple functionality test"), + ("test_factory", "Factory pattern test"), + ] + + for test_name, description in tests: + test_exe = Path(f"./{build_type}/{test_name}.exe") + + if test_exe.exists(): + print_color(f"\n[TEST] {description}...", Colors.MAGENTA) + print_color("-" * 50, Colors.DARK_GRAY) + + try: + result = subprocess.run( + [str(test_exe)], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + + if result.stdout: + print(result.stdout) + + if result.returncode == 0: + print_success(f"{test_name} passed") + else: + print_error(f"{test_name} failed (exit code: {result.returncode})") + if result.stderr: + print_color(result.stderr, Colors.YELLOW) + + except Exception as e: + print_error(f"{test_name} failed: {e}") + else: + print_warning(f"{test_name}.exe not found at {test_exe}") + +def main(): + """主函数""" + parser = argparse.ArgumentParser(description="Fortran CFD Project Builder") + + parser.add_argument( + "--build-type", + choices=["Debug", "Release", "RelWithDebInfo", "MinSizeRel"], + default="Debug", + help="Build type (default: Debug)" + ) + + parser.add_argument( + "--clean", + action="store_true", + help="Clean build directory before building" + ) + + parser.add_argument( + "--no-tests", + action="store_true", + help="Skip running tests" + ) + + parser.add_argument( + "-j", "--jobs", + type=int, + default=0, + help="Number of parallel jobs (0 = use all cores)" + ) + + parser.add_argument( + "--force", + action="store_true", + help="Continue on errors" + ) + + parser.add_argument( + "--verbose", + action="store_true", + help="Verbose output" + ) + + args = parser.parse_args() + + if args.jobs == 0: + import multiprocessing + args.jobs = multiprocessing.cpu_count() + + args.run_tests = not args.no_tests + + try: + success = build_project(args) + if not success and not args.force: + sys.exit(1) + except KeyboardInterrupt: + print_color("\nBuild interrupted by user", Colors.YELLOW) + sys.exit(1) + except Exception as e: + print_error(f"Unexpected error: {e}") + if args.verbose: + import traceback + traceback.print_exc() + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01e/scripts/requirements.txt b/example/1d-linear-convection/weno3/fortran/registry/01e/scripts/requirements.txt new file mode 100644 index 000000000..d21a12b45 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01e/scripts/requirements.txt @@ -0,0 +1,2 @@ +# 构建脚本的Python依赖(可选) +# 目前没有特殊依赖,保持空文件或添加未来可能需要的包 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01e/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01e/src/CMakeLists.txt new file mode 100644 index 000000000..ee38952bb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01e/src/CMakeLists.txt @@ -0,0 +1,14 @@ +# ==================== src\CMakeLists.txt ==================== + +# src/CMakeLists.txt +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(core) +add_subdirectory(infrastructure) +add_subdirectory(numerics) + +message(STATUS "源代码目录配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01e/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01e/src/core/CMakeLists.txt new file mode 100644 index 000000000..376dd4fe5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01e/src/core/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/core/CMakeLists.txt +message(STATUS "配置核心模块...") + +add_library(core + registry.f90 + factory_interfaces.f90 +) + +target_include_directories(core PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 安装规则 +install(TARGETS core + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) +install(FILES + ${CMAKE_Fortran_MODULE_DIRECTORY}/registry_module.mod + ${CMAKE_Fortran_MODULE_DIRECTORY}/factory_interfaces.mod + DESTINATION include/fortran_cfd/core +) + +message(STATUS "核心模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01e/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/01e/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..19cc2c59e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01e/src/core/factory_interfaces.f90 @@ -0,0 +1,17 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, factory_procedure + + ! Factory procedure interface + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01e/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/01e/src/core/registry.f90 new file mode 100644 index 000000000..f996c89b3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01e/src/core/registry.f90 @@ -0,0 +1,318 @@ +! src/core/registry.f90 +module registry_module + use, intrinsic :: iso_fortran_env, only: real64 + use factory_interfaces, only: factory_procedure + implicit none + + private + + ! Public interface + public :: real64, component_info, component_registry + public :: register_component_simple, initialize_registry, cleanup_registry + public :: has_component, get_available_components + public :: registry_is_initialized, registry_get_size ! 添加公共访问方法 + + ! Type definitions (simplified, no factory for now) + type :: component_info + character(len=32) :: category = "" + character(len=32) :: name = "" + integer :: order = 0 + contains + procedure :: print => ci_print + end type component_info + + type :: component_registry_type + private + type(component_info), allocatable :: components(:) + integer :: count = 0 + integer :: capacity = 100 + logical :: verbose = .true. + logical :: initialized = .false. + contains + procedure :: register => cr_register + procedure :: get => cr_get + procedure :: list_all => cr_list_all + procedure :: clear => cr_clear + procedure :: size => cr_size + procedure :: is_initialized => cr_is_initialized ! 内部方法 + end type component_registry_type + + ! Global registry instance + type(component_registry_type), save :: component_registry + +contains + + ! ==================== PUBLIC API ==================== + + ! Initialize registry + subroutine initialize_registry(initial_capacity, verbose) + integer, optional, intent(in) :: initial_capacity + logical, optional, intent(in) :: verbose + + if (component_registry%initialized) then + if (component_registry%verbose) then + print *, "[INFO] Registry already initialized" + end if + return + end if + + if (present(initial_capacity)) then + component_registry%capacity = max(10, initial_capacity) + end if + + if (present(verbose)) then + component_registry%verbose = verbose + end if + + ! Allocate array + allocate(component_registry%components(component_registry%capacity)) + + component_registry%initialized = .true. + component_registry%count = 0 + + if (component_registry%verbose) then + print *, "[INIT] Registry initialized, capacity:", component_registry%capacity + end if + end subroutine initialize_registry + + ! Cleanup registry + subroutine cleanup_registry + call component_registry%clear() + if (component_registry%verbose) then + print *, "[CLEANUP] Registry cleaned up" + end if + end subroutine cleanup_registry + + ! Simple registration (no factory) + subroutine register_component_simple(category, name) + character(len=*), intent(in) :: category, name + + type(component_info) :: info + + info%category = to_lower(trim(adjustl(category))) + info%name = to_lower(trim(adjustl(name))) + info%order = 0 + + call component_registry%register(info) + end subroutine register_component_simple + + ! Check if component exists + function has_component(category, name) result(found) + character(len=*), intent(in) :: category, name + logical :: found + + type(component_info) :: info + character(len=32) :: cat_lower, name_lower + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + info = component_registry%get(cat_lower, name_lower) + found = (len_trim(info%category) > 0) + end function has_component + + ! Get available components in a category + subroutine get_available_components(category, names, orders) + character(len=*), intent(in) :: category + character(len=:), allocatable, intent(out), optional :: names(:) + integer, allocatable, intent(out), optional :: orders(:) + + character(len=32) :: cat_lower + integer :: i, count, idx + type(component_info) :: info + + cat_lower = to_lower(trim(adjustl(category))) + + ! Count components in this category + count = 0 + do i = 1, component_registry%count + if (component_registry%components(i)%category == cat_lower) then + count = count + 1 + end if + end do + + ! Allocate arrays if requested + if (present(names)) then + allocate(character(len=32) :: names(count)) + end if + + if (present(orders)) then + allocate(orders(count)) + end if + + ! Fill arrays + idx = 1 + do i = 1, component_registry%count + if (component_registry%components(i)%category == cat_lower) then + info = component_registry%components(i) + if (present(names)) then + names(idx) = info%name + end if + if (present(orders)) then + orders(idx) = info%order + end if + idx = idx + 1 + end if + end do + end subroutine get_available_components + + ! Public function to check if registry is initialized + function registry_is_initialized() result(is_initialized) + logical :: is_initialized + is_initialized = component_registry%is_initialized() + end function registry_is_initialized + + ! Public function to get registry size + function registry_get_size() result(size_val) + integer :: size_val + size_val = component_registry%size() + end function registry_get_size + + ! ==================== COMPONENT INFO METHODS ==================== + + subroutine ci_print(this) + class(component_info), intent(in) :: this + + if (this%order > 0) then + print *, " [", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ")]" + else + print *, " [", trim(this%category), ".", trim(this%name), "]" + end if + end subroutine ci_print + + ! ==================== REGISTRY INTERNAL METHODS ==================== + + subroutine cr_register(this, info) + class(component_registry_type), intent(inout) :: this + type(component_info), intent(in) :: info + + type(component_info), allocatable :: temp(:) + integer :: i + + if (.not. this%initialized) then + error stop "[ERROR] Registry not initialized, call initialize_registry first" + end if + + ! Check if already exists + do i = 1, this%count + if (this%components(i)%category == info%category .and. & + this%components(i)%name == info%name) then + if (this%verbose) then + print *, "[WARN] Overwriting: ", & + trim(info%category), ".", trim(info%name) + end if + this%components(i) = info + return + end if + end do + + ! Expand array if needed + if (this%count >= this%capacity) then + this%capacity = this%capacity * 2 + allocate(temp(this%capacity)) + temp(1:this%count) = this%components(1:this%count) + call move_alloc(temp, this%components) + + if (this%verbose) then + print *, "[INFO] Registry expanded to capacity:", this%capacity + end if + end if + + ! Add component + this%count = this%count + 1 + this%components(this%count) = info + + if (this%verbose) then + print *, "[OK] Registered: ", trim(info%category), ".", trim(info%name) + end if + end subroutine cr_register + + function cr_get(this, category, name) result(info) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category, name + type(component_info) :: info + + integer :: i + + ! Initialize return value as empty + info%category = "" + info%name = "" + info%order = 0 + + if (.not. this%initialized) then + return + end if + + do i = 1, this%count + if (this%components(i)%category == category .and. & + this%components(i)%name == name) then + info = this%components(i) + return + end if + end do + end function cr_get + + subroutine cr_list_all(this) + class(component_registry_type), intent(in) :: this + integer :: i + + if (.not. this%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + print *, "=== Registry Contents (", this%count, " components) ===" + + if (this%count == 0) then + print *, " (empty)" + return + end if + + ! Show components grouped by category + do i = 1, this%count + call this%components(i)%print() + end do + + print *, "===========================================" + end subroutine cr_list_all + + subroutine cr_clear(this) + class(component_registry_type), intent(inout) :: this + + if (allocated(this%components)) then + deallocate(this%components) + end if + + this%count = 0 + this%capacity = 100 + this%initialized = .false. + end subroutine cr_clear + + integer function cr_size(this) + class(component_registry_type), intent(in) :: this + cr_size = this%count + end function cr_size + + logical function cr_is_initialized(this) + class(component_registry_type), intent(in) :: this + cr_is_initialized = this%initialized + end function cr_is_initialized + + ! ==================== UTILITY FUNCTIONS ==================== + + function to_lower(str) result(lower_str) + character(len=*), intent(in) :: str + character(len=len(str)) :: lower_str + integer :: i + + do i = 1, len(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + lower_str(i:i) = char(ichar(str(i:i)) + 32) + else + lower_str(i:i) = str(i:i) + end if + end do + end function to_lower + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01e/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01e/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..2b72c5484 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01e/src/infrastructure/CMakeLists.txt @@ -0,0 +1,20 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "配置基础设施模块...") + +add_library(infrastructure + config.f90 + mesh.f90 +) + +target_link_libraries(infrastructure PUBLIC core) + +target_include_directories(infrastructure PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS infrastructure + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "基础设施模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01e/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/01e/src/infrastructure/config.f90 new file mode 100644 index 000000000..34019f2f5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01e/src/infrastructure/config.f90 @@ -0,0 +1,90 @@ +! src/infrastructure/config.f90 +module config_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, cfd_config, config_print, config_with_reconstruction + + ! CFD configuration type + type :: cfd_config + character(len=20) :: ic_type = "step" + character(len=20) :: recon_scheme = "eno" + character(len=20) :: flux_type = "rusanov" + integer :: rk_order = 1 + real(real64) :: wave_speed = 1.0_real64 + real(real64) :: final_time = 0.625_real64 + real(real64) :: dt = 0.025_real64 + character(len=20) :: boundary_type = "periodic" + real(real64) :: left_boundary_value = 1.0_real64 + real(real64) :: right_boundary_value = 2.0_real64 + integer :: spatial_order = 2 + logical :: verbose = .true. + end type cfd_config + +contains + + subroutine config_print(this) + type(cfd_config), intent(in) :: this + + print *, "=== CFD Configuration ===" + print *, "Initial condition: ", trim(this%ic_type) + print *, "Reconstruction: ", trim(this%recon_scheme), " (order:", this%spatial_order, ")" + print *, "Flux type: ", trim(this%flux_type) + print *, "Time integration: RK", this%rk_order + print *, "Wave speed: ", this%wave_speed + print *, "Final time: ", this%final_time + print *, "Time step: ", this%dt + print *, "Boundary: ", trim(this%boundary_type) + if (trim(this%boundary_type) == 'dirichlet') then + print *, " Dirichlet values: [", this%left_boundary_value, ", ", & + this%right_boundary_value, "]" + end if + print *, "===============================" + end subroutine config_print + + subroutine config_with_reconstruction(this, scheme, order) + type(cfd_config), intent(inout) :: this + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + character(len=20) :: scheme_lower + + ! Convert to lowercase + scheme_lower = scheme + call to_lower_inplace(scheme_lower) + this%recon_scheme = trim(adjustl(scheme_lower)) + + ! Set order + if (present(order)) then + this%spatial_order = order + else + ! Smart defaults + if (index(this%recon_scheme, 'weno') > 0) then + this%spatial_order = 5 + else if (trim(this%recon_scheme) == 'eno') then + this%spatial_order = 3 + else + print *, "[ERROR] Unsupported reconstruction scheme: ", trim(this%recon_scheme) + return + end if + end if + + if (this%verbose) then + print *, "[CONFIG] Reconstruction: ", trim(this%recon_scheme), & + " Order: ", this%spatial_order + end if + end subroutine config_with_reconstruction + + subroutine to_lower_inplace(str) + character(len=*), intent(inout) :: str + integer :: i + + do i = 1, len_trim(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + str(i:i) = char(ichar(str(i:i)) + 32) + end if + end do + end subroutine to_lower_inplace + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01e/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/01e/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..65a45dcdf --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01e/src/infrastructure/mesh.f90 @@ -0,0 +1,74 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, mesh_type, mesh_init, mesh_print_info + + ! 网格类型 + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer :: ncells = 40 + integer :: nnodes + integer :: nx + real(wp), allocatable :: x(:) ! 节点坐标 + real(wp), allocatable :: xcc(:) ! 单元中心坐标 + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer, optional, intent(in) :: ncells + + integer :: i + + ! 设置参数 + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! 计算派生参数 + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! 分配数组 + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! 生成节点坐标 + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! 生成单元中心坐标 + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== 网格信息 ===" + print *, "计算域: [", this%xmin, ", ", this%xmax, "]" + print *, "单元数: ", this%ncells + print *, "节点数: ", this%nnodes + print *, "网格尺寸 dx: ", this%dx + print *, "域长度 L: ", this%L + print *, "==========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..66874a7ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/CMakeLists.txt @@ -0,0 +1,7 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..b2617f290 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,20 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +# Start with just the base module +add_library(flux + base.f90 +) + +target_link_libraries(flux PUBLIC core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/flux/base.f90 new file mode 100644 index 000000000..d9bd640c2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/flux/base.f90 @@ -0,0 +1,24 @@ +! src/numerics/flux/base.f90 +module flux_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, flux_calculator_base + + ! Base flux calculator type + type :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure :: info => flux_info + end type flux_calculator_base + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "Flux calculator information:" + print *, " Name: ", trim(this%name) + end subroutine flux_info + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/flux/rusanov.f90 b/example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/flux/rusanov.f90 new file mode 100644 index 000000000..414f783da --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/flux/rusanov.f90 @@ -0,0 +1,25 @@ +! src/numerics/flux/rusanov.f90 +module rusanov_flux_module + use, intrinsic :: iso_fortran_env, only: real64 + use flux_base_module, only: flux_calculator_base + implicit none + + private + public :: real64, rusanov_flux + + type, extends(flux_calculator_base) :: rusanov_flux + real(real64) :: wave_speed_default = 1.0_real64 + contains + procedure :: info => rusanov_info + end type rusanov_flux + +contains + + subroutine rusanov_info(this) + class(rusanov_flux), intent(in) :: this + call flux_info(this) + print *, " Type: Rusanov flux" + print *, " Default wave speed: ", this%wave_speed_default + end subroutine rusanov_info + +end module rusanov_flux_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..77808c1ad --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,20 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "Configuring reconstructor module...") + +# Start with just the base module +add_library(reconstructor + base.f90 +) + +target_link_libraries(reconstructor PUBLIC core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Reconstructor module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..8d3119ee7 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/reconstructor/base.f90 @@ -0,0 +1,27 @@ +! src/numerics/reconstructor/base.f90 +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, reconstructor_base + + ! Base reconstructor type + type :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + contains + procedure :: info => reconstructor_info + end type reconstructor_base + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + + print *, "Reconstructor information:" + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_info + +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/reconstructor/eno.f90 b/example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/reconstructor/eno.f90 new file mode 100644 index 000000000..19029b6dc --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/reconstructor/eno.f90 @@ -0,0 +1,25 @@ +! src/numerics/reconstructor/eno.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, eno_reconstructor + + type, extends(reconstructor_base) :: eno_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => eno_info + end type eno_reconstructor + +contains + + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + call reconstructor_info(this) + print *, " Type: ENO reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/reconstructor/weno3.f90 b/example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/reconstructor/weno3.f90 new file mode 100644 index 000000000..4f268ac9a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01e/src/numerics/reconstructor/weno3.f90 @@ -0,0 +1,25 @@ +! src/numerics/reconstructor/weno3.f90 +module weno3_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno3_reconstructor + + type, extends(reconstructor_base) :: weno3_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno3_info + end type weno3_reconstructor + +contains + + subroutine weno3_info(this) + class(weno3_reconstructor), intent(in) :: this + call reconstructor_info(this) + print *, " Type: WENO-3 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno3_info + +end module weno3_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01e/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01e/tests/CMakeLists.txt new file mode 100644 index 000000000..a98581897 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01e/tests/CMakeLists.txt @@ -0,0 +1,16 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +# Simple functionality test +add_executable(test_simple + test_minimal_simple.f90 +) +target_link_libraries(test_simple + core + infrastructure +) +target_include_directories(test_simple PRIVATE + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Tests configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01e/tests/test_factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/01e/tests/test_factory.f90 new file mode 100644 index 000000000..8968b9c28 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01e/tests/test_factory.f90 @@ -0,0 +1,154 @@ +! tests/test_factory.f90 +program test_factory + use registry_module + use config_module + use mesh_module + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + class(*), allocatable :: instance + class(reconstructor_base), allocatable :: recon + class(flux_calculator_base), allocatable :: flux_calc + real(wp), allocatable :: q(:), qL(:), qR(:), flux(:) + integer :: i, n + + print *, "=== Factory Pattern Test ===" + print *, "" + + ! Initialize systems + call initialize_registry(verbose=.true.) + + ! Register components with factories + print *, "1. Registering components with factories..." + call register_component_with_factory("reconstructor", "eno", create_eno, 3) + call register_component_with_factory("reconstructor", "weno3", create_weno3, 3) + call register_component_with_factory("flux", "rusanov", create_rusanov) + + call component_registry%list_all() + print *, "" + + ! Test creating ENO reconstructor + print *, "2. Creating ENO reconstructor..." + call create_component("reconstructor", "eno", instance) + + if (allocated(instance)) then + select type (inst => instance) + type is (eno_reconstructor) + allocate(recon, source=inst) + print *, "ENO reconstructor created successfully" + call recon%info() + print *, "" + + ! Test reconstruction + n = 10 + allocate(q(0:n+1), qL(n), qR(n)) + + ! Initialize test data (sine wave) + do i = 0, n+1 + q(i) = sin(2.0_wp * 3.141592653589793_wp * real(i-1, wp) / real(n, wp)) + end do + + print *, "Testing ENO reconstruction..." + call recon%reconstruct(q, qL, qR) + + print *, "q (internal):" + do i = 1, n + write(*, '(I3, F10.6)') i, q(i) + end do + + print *, "qL (left interface values):" + do i = 1, n + write(*, '(I3, F10.6)') i, qL(i) + end do + + deallocate(q, qL, qR) + class default + print *, "[ERROR] Wrong type for ENO reconstructor" + end select + else + print *, "[ERROR] Failed to create ENO reconstructor" + end if + print *, "" + + ! Test creating WENO3 reconstructor + print *, "3. Creating WENO3 reconstructor..." + if (allocated(instance)) deallocate(instance) + call create_component("reconstructor", "weno3", instance) + + if (allocated(instance)) then + select type (inst => instance) + type is (weno3_reconstructor) + if (allocated(recon)) deallocate(recon) + allocate(recon, source=inst) + print *, "WENO3 reconstructor created successfully" + call recon%info() + class default + print *, "[ERROR] Wrong type for WENO3 reconstructor" + end select + else + print *, "[ERROR] Failed to create WENO3 reconstructor" + end if + print *, "" + + ! Test creating Rusanov flux calculator + print *, "4. Creating Rusanov flux calculator..." + if (allocated(instance)) deallocate(instance) + call create_component("flux", "rusanov", instance) + + if (allocated(instance)) then + select type (inst => instance) + type is (rusanov_flux) + allocate(flux_calc, source=inst) + print *, "Rusanov flux calculator created successfully" + call flux_calc%info() + print *, "" + + ! Test flux computation + n = 5 + allocate(qL(n), qR(n), flux(n)) + + ! Initialize test data + do i = 1, n + qL(i) = 1.0_wp + 0.1_wp * real(i-1, wp) + qR(i) = 1.0_wp + 0.1_wp * real(i, wp) + end do + + print *, "Testing Rusanov flux computation..." + call flux_calc%compute(qL, qR, flux, 1.0_wp) + + print *, "qL:" + do i = 1, n + write(*, '(I3, F10.6)') i, qL(i) + end do + + print *, "qR:" + do i = 1, n + write(*, '(I3, F10.6)') i, qR(i) + end do + + print *, "Flux:" + do i = 1, n + write(*, '(I3, F10.6)') i, flux(i) + end do + + deallocate(qL, qR, flux) + class default + print *, "[ERROR] Wrong type for Rusanov flux" + end select + else + print *, "[ERROR] Failed to create Rusanov flux calculator" + end if + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Factory pattern test completed successfully ===" + +end program test_factory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01e/tests/test_minimal.f90 b/example/1d-linear-convection/weno3/fortran/registry/01e/tests/test_minimal.f90 new file mode 100644 index 000000000..bb4b7cda9 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01e/tests/test_minimal.f90 @@ -0,0 +1,108 @@ +! tests/test_minimal.f90 +program test_minimal + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i ! Declare loop variable here + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", component_registry%size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components + print *, "5. Testing available components" + print *, "--------------------------------" + + ! Use a separate block to avoid allocation issues + call test_available_components() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Minimal test completed successfully ===" + +contains + + ! Internal subroutine for testing available components + subroutine test_available_components() + character(len=:), allocatable :: names(:) + integer, allocatable :: orders(:) + integer :: j + + call get_available_components("reconstructor", names, orders) + + if (allocated(names)) then + print *, "Reconstructors:" + do j = 1, size(names) + print *, " - ", trim(names(j)) + if (allocated(orders)) then + print *, " Order: ", orders(j) + end if + end do + else + print *, "No reconstructors found" + end if + end subroutine test_available_components + +end program test_minimal \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01e/tests/test_minimal_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/01e/tests/test_minimal_simple.f90 new file mode 100644 index 000000000..689165de8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01e/tests/test_minimal_simple.f90 @@ -0,0 +1,84 @@ +! tests/test_minimal_simple.f90 +program test_minimal_simple + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Simple Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components (simple version) + print *, "5. Testing registry functionality" + print *, "---------------------------------" + print *, "Registry initialized: ", registry_is_initialized() + print *, "Number of components: ", registry_get_size() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Simple test completed successfully ===" + +end program test_minimal_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01f/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01f/CMakeLists.txt new file mode 100644 index 000000000..02eba69ef --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01f/CMakeLists.txt @@ -0,0 +1,43 @@ +# 根目录CMakeLists.txt +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + +# 设置Fortran标准 +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +# 模块输出目录 +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) + +# 编译器选项 +if(CMAKE_Fortran_COMPILER_ID MATCHES "IntelLLVM|Intel") + set(CMAKE_Fortran_FLAGS_DEBUG "/debug:full /Od /traceback /check:all /warn:all /fpe:0") + set(CMAKE_Fortran_FLAGS_RELEASE "/O3") + set(CMAKE_Fortran_FLAGS_RELWITHDEBINFO "/O2 /debug:full") +endif() + +# 设置默认构建类型 +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Build type" FORCE) + message(STATUS "Setting default build type to: ${CMAKE_BUILD_TYPE}") +endif() + +message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") + +# 确保模块目录存在 +file(MAKE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(src) +add_subdirectory(tests) + +# 安装目录 +set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/install CACHE PATH "Installation prefix") + +message(STATUS "Installation prefix: ${CMAKE_INSTALL_PREFIX}") +message(STATUS "Module directory: ${CMAKE_Fortran_MODULE_DIRECTORY}") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01f/README.md b/example/1d-linear-convection/weno3/fortran/registry/01f/README.md new file mode 100644 index 000000000..c4889757b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01f/README.md @@ -0,0 +1,221 @@ +# Fortran CFD Project + +基于工厂模式和注册系统的CFD求解器框架。 + +## 项目结构 + +``` +fortran_cfd/ +├── CMakeLists.txt # 根CMake配置 +├── scripts/ # 构建脚本 +│ ├── build.py # Python构建脚本(推荐) +│ ├── build.bat # Batch包装脚本 +│ ├── build.ps1 # PowerShell包装脚本 +│ └── requirements.txt # Python依赖 +├── src/ # 源代码 +│ ├── CMakeLists.txt +│ ├── core/ # 核心模块(注册系统、工厂接口) +│ ├── infrastructure/ # 基础设施(配置、网格) +│ └── numerics/ # 数值方法 +├── tests/ # 测试 +│ ├── CMakeLists.txt +│ ├── test_minimal_simple.f90 # 简单测试 +│ └── test_factory.f90 # 工厂模式测试 +└── README.md # 项目说明(本文件) +``` + +## 快速开始 + +### 使用Python脚本(推荐) + +```bash +# 进入脚本目录 +cd scripts + +# 基本构建 +python build.py + +# 清理并构建 +python build.py --clean + +# 发布构建 +python build.py --build-type Release --clean + +# 并行构建(使用所有核心) +python build.py -j + +# 不运行测试 +python build.py --no-tests + +# 查看帮助 +python build.py --help +``` + +### 使用批处理脚本 + +```bash +cd scripts +.\build.bat +``` + +### 使用PowerShell脚本 + +```powershell +cd scripts +.\build.ps1 +``` + +## 手动构建 + +```bash +# 设置Intel环境 +cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && powershell' + +# 配置项目 +mkdir build +cd build +cmake -G "Visual Studio 17 2022" -A x64 -T fortran=ifx .. + +# 构建项目 +cmake --build . --config Debug + +# 运行测试 +Debug\test_simple.exe +Debug\test_factory.exe +``` + +## 功能特性 + +✅ 组件注册系统 +✅ 工厂模式 +✅ ENO/WENO重构器 +✅ Rusanov通量计算器 +✅ 可扩展架构 +✅ 自动化测试 + +## 依赖 + +- Intel oneAPI(包含ifx编译器) +- CMake 3.12+ +- Python 3.6+(仅用于构建脚本) + +## 源代码文件 + +### 核心模块 +- `src/core/registry.f90` - 组件注册系统 +- `src/core/factory_interfaces.f90` - 工厂接口 + +### 基础设施 +- `src/infrastructure/config.f90` - 配置管理 +- `src/infrastructure/mesh.f90` - 网格生成 + +### 数值方法 +- `src/numerics/reconstructor/base.f90` - 重构器基类 +- `src/numerics/reconstructor/eno.f90` - ENO重构器 +- `src/numerics/reconstructor/weno3.f90` - WENO-3重构器 +- `src/numerics/flux/base.f90` - 通量计算器基类 +- `src/numerics/flux/rusanov.f90` - Rusanov通量 + +### 测试 +- `tests/test_minimal_simple.f90` - 简单功能测试 +- `tests/test_factory.f90` - 工厂模式测试 + +## 构建脚本 + +### Python脚本 (build.py) +主构建脚本,支持: +- 自动设置Intel oneAPI环境 +- 参数化构建选项 +- 并行编译 +- 自动化测试 +- 彩色输出 + +### Batch脚本 (build.bat) +Windows批处理包装器,调用Python脚本。 + +### PowerShell脚本 (build.ps1) +PowerShell包装器,调用Python脚本。 + +## 示例输出 + +成功构建后,会看到类似输出: + +``` +============================================================ + Fortran CFD Project Builder +============================================================ + +[1/4] Setting up Intel Fortran compiler... +✓ Intel oneAPI environment configured + +[2/4] Preparing build directory... +✓ Cleaned build directory + +[3/4] Configuring project... + $ cmake -G Visual Studio 17 2022 -A x64 -T fortran=ifx -DCMAKE_BUILD_TYPE=Debug .. +✓ CMake configuration successful + +[4/4] Building project... + $ cmake --build . --config Debug +✓ Build completed in 12.3 seconds + +============================================================ + Running Tests +============================================================ + +[TEST] Simple functionality test... +✓ Simple test passed + +[TEST] Factory pattern test... +✓ Factory test passed + +============================================================ + Build Summary +============================================================ +✓ Build directory: D:\path\to\build +✓ Build type: Debug +✓ Total time: 15.2s +``` + +## 开发指南 + +### 添加新组件 + +1. 在相应模块中创建Fortran源文件 +2. 实现工厂函数 +3. 使用`register_component_with_factory`注册组件 +4. 添加测试 + +### 扩展架构 + +项目采用模块化设计: +1. **注册系统** - 管理所有可用的组件 +2. **工厂模式** - 动态创建组件实例 +3. **抽象接口** - 确保组件兼容性 +4. **配置驱动** - 通过配置文件控制行为 + +## 故障排除 + +### 常见问题 + +1. **Intel环境未找到** + - 检查Intel oneAPI安装路径 + - 手动运行`setvars.bat` + +2. **CMake配置失败** + - 确保使用正确的生成器:`Visual Studio 17 2022` + - 检查Fortran编译器是否可用 + +3. **构建失败** + - 检查依赖项是否完整 + - 查看详细的错误信息 + +### 调试建议 + +- 使用`--verbose`参数获取详细输出 +- 检查`build/CMakeCache.txt`了解配置详情 +- 查看编译器错误日志 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01f/scripts/build.bat b/example/1d-linear-convection/weno3/fortran/registry/01f/scripts/build.bat new file mode 100644 index 000000000..d243695b0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01f/scripts/build.bat @@ -0,0 +1,26 @@ +@echo off +echo ======================================== +echo Fortran CFD Project Builder +echo (Wrapper for Python script) +echo ======================================== +echo. + +REM 检查Python +where python >nul 2>nul +if %errorlevel% neq 0 ( + echo [ERROR] Python not found. Please install Python 3. + pause + exit /b 1 +) + +REM 运行Python构建脚本 +echo [INFO] Running Python build script... +python build.py %* + +if %errorlevel% neq 0 ( + echo [ERROR] Build failed + pause + exit /b 1 +) + +pause \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01f/scripts/build.ps1 b/example/1d-linear-convection/weno3/fortran/registry/01f/scripts/build.ps1 new file mode 100644 index 000000000..4497c5ebd --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01f/scripts/build.ps1 @@ -0,0 +1,32 @@ +# Fortran CFD Project Builder (PowerShell wrapper) + +Write-Host "========================================" -ForegroundColor Cyan +Write-Host " Fortran CFD Project Builder" -ForegroundColor Cyan +Write-Host " (PowerShell wrapper for Python script)" -ForegroundColor Cyan +Write-Host "========================================" -ForegroundColor Cyan +Write-Host "" + +# 检查Python +$python = Get-Command python -ErrorAction SilentlyContinue +if (-not $python) { + $python = Get-Command python3 -ErrorAction SilentlyContinue +} + +if (-not $python) { + Write-Host "[ERROR] Python not found. Please install Python 3." -ForegroundColor Red + Read-Host "Press Enter to exit" + exit 1 +} + +# 运行Python构建脚本 +Write-Host "[INFO] Running Python build script..." -ForegroundColor Yellow +$argsString = $args -join ' ' +$command = "python build.py $argsString" + +Invoke-Expression $command + +if ($LASTEXITCODE -ne 0) { + Write-Host "[ERROR] Build failed" -ForegroundColor Red + Read-Host "Press Enter to exit" + exit 1 +} \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01f/scripts/build.py b/example/1d-linear-convection/weno3/fortran/registry/01f/scripts/build.py new file mode 100644 index 000000000..d632c338b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01f/scripts/build.py @@ -0,0 +1,355 @@ +#!/usr/bin/env python3 +""" +Fortran CFD 项目构建脚本 (Python版) +支持 Intel oneAPI 环境的自动化构建 +""" + +import os +import sys +import subprocess +import shutil +from pathlib import Path +import argparse +import platform +import time + +class Colors: + """终端颜色""" + if platform.system() == "Windows": + # Windows 启用 ANSI + os.system("") + + HEADER = '\033[95m' + BLUE = '\033[94m' + CYAN = '\033[96m' + GREEN = '\033[92m' + YELLOW = '\033[93m' + RED = '\033[91m' + MAGENTA = '\033[95m' + DARK_GRAY = '\033[90m' # 添加这个 + END = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + +def print_color(text, color=Colors.END): + """彩色打印""" + print(f"{color}{text}{Colors.END}") + +def print_header(text): + """打印标题""" + print_color("\n" + "="*60, Colors.CYAN) + print_color(f" {text}", Colors.BOLD + Colors.CYAN) + print_color("="*60 + "\n", Colors.CYAN) + +def print_step(step, total, message): + """打印步骤""" + print_color(f"[{step}/{total}] {message}...", Colors.YELLOW) + +def print_success(message): + """打印成功""" + print_color(f"✓ {message}", Colors.GREEN) + +def print_error(message): + """打印错误""" + print_color(f"✗ {message}", Colors.RED) + +def print_warning(message): + """打印警告""" + print_color(f"! {message}", Colors.YELLOW) + +def run_command(cmd, cwd=None, check=True, capture=True): + """运行命令""" + print_color(f" $ {' '.join(cmd)}", Colors.BLUE) + + try: + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=capture, + text=True, + encoding='utf-8', + errors='ignore', + shell=False + ) + + if capture and result.stdout: + print(result.stdout) + if capture and result.stderr: + print_color(result.stderr, Colors.YELLOW) + + if check and result.returncode != 0: + print_error(f"Command failed with exit code: {result.returncode}") + return False + + return True + + except FileNotFoundError as e: + print_error(f"Command not found: {cmd[0]}") + if check: + raise + return False + except Exception as e: + print_error(f"Command execution failed: {e}") + return False + +def setup_intel_environment(): + """设置 Intel oneAPI 环境""" + setvars_paths = [ + r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat", + r"C:\Program Files\Intel\oneAPI\setvars.bat", + ] + + setvars_path = None + for path in setvars_paths: + if os.path.exists(path): + setvars_path = path + break + + if not setvars_path: + print_error("Intel oneAPI setvars.bat not found.") + print_warning("Please install Intel oneAPI or update the path in build.py") + return False + + # 创建临时的 batch 文件 + temp_bat = "temp_setvars.bat" + with open(temp_bat, 'w') as f: + f.write(f'@echo off\n') + f.write(f'call "{setvars_path}" > nul 2>&1\n') + f.write(f'set\n') + + try: + # 运行并捕获环境变量 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + shell=True, + encoding='utf-8', + errors='ignore' + ) + + # 解析并设置环境变量 + for line in result.stdout.split('\n'): + if '=' in line: + key, value = line.split('=', 1) + os.environ[key.strip()] = value.strip() + + os.remove(temp_bat) + print_success("Intel oneAPI environment configured") + return True + + except Exception as e: + print_error(f"Failed to setup Intel environment: {e}") + if os.path.exists(temp_bat): + os.remove(temp_bat) + return False + +def build_project(args): + """构建项目主函数""" + start_time = time.time() + + print_header(f"Fortran CFD Project Builder") + print_color(f"Build type: {args.build_type}", Colors.CYAN) + print_color(f"Run tests: {args.run_tests}", Colors.CYAN) + print() + + # 获取项目根目录(脚本所在目录的父目录) + script_dir = Path(__file__).parent + project_root = script_dir.parent + os.chdir(project_root) + + print_color(f"Project root: {project_root}", Colors.BLUE) + + # 步骤1: 设置 Intel 环境 + print_step(1, 4, "Setting up Intel Fortran compiler") + if not setup_intel_environment(): + return False + + # 步骤2: 准备构建目录 + print_step(2, 4, "Preparing build directory") + build_dir = project_root / "build" + + if args.clean and build_dir.exists(): + try: + shutil.rmtree(build_dir) + print_success("Cleaned build directory") + except Exception as e: + print_error(f"Failed to clean build directory: {e}") + if not args.force: + return False + + build_dir.mkdir(exist_ok=True) + os.chdir(build_dir) + + # 步骤3: 配置项目 + print_step(3, 4, "Configuring project") + + cmake_cmd = [ + "cmake", + "-G", "Visual Studio 17 2022", + "-A", "x64", + "-T", "fortran=ifx", + f"-DCMAKE_BUILD_TYPE={args.build_type}", + ".." + ] + + if not run_command(cmake_cmd, check=not args.force): + if not args.force: + return False + + # 步骤4: 构建项目 + print_step(4, 4, "Building project") + + build_cmd = [ + "cmake", + "--build", ".", + "--config", args.build_type, + f"-j{args.jobs}" if args.jobs > 1 else "" + ] + build_cmd = [c for c in build_cmd if c] # 移除空字符串 + + if not run_command(build_cmd, check=not args.force): + if not args.force: + return False + + build_time = time.time() - start_time + print_success(f"Build completed in {build_time:.1f} seconds") + + # 运行测试 + if args.run_tests: + print_header("Running Tests") + run_tests(args.build_type) + + print_header("Build Summary") + print_color(f"Build directory: {build_dir}", Colors.GREEN) + print_color(f"Build type: {args.build_type}", Colors.GREEN) + print_color(f"Total time: {build_time:.1f}s", Colors.GREEN) + + return True + +def run_tests(build_type): + """运行测试""" + tests = [ + ("test_simple", "Simple functionality test"), + ("test_factory", "Factory pattern test"), + ] + + for test_name, description in tests: + # 修复:在这里直接访问当前循环的 test_name + _run_single_test(test_name, description, build_type) + +def _run_single_test(test_name, description, build_type): + """运行单个测试(修复作用域问题)""" + # 可能的测试可执行文件路径 + possible_paths = [ + Path(f"./{build_type}/{test_name}.exe"), + Path(f"./tests/{build_type}/{test_name}.exe"), + Path(f"./tests/{test_name}.exe"), + Path(f"{test_name}.exe"), + Path(f"./{test_name}.exe"), + ] + + test_exe = None + + # 尝试所有可能的路径 + for path in possible_paths: + if path.exists(): + test_exe = path + break + + if test_exe: + print_color(f"\n[TEST] {description}...", Colors.MAGENTA) + print_color("-" * 50, Colors.DARK_GRAY) + + try: + result = subprocess.run( + [str(test_exe)], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + + if result.stdout: + print(result.stdout) + + if result.returncode == 0: + print_success(f"{test_name} passed") + else: + print_error(f"{test_name} failed (exit code: {result.returncode})") + if result.stderr: + print_color(result.stderr, Colors.YELLOW) + + except Exception as e: + print_error(f"{test_name} failed: {e}") + else: + print_warning(f"{test_name}.exe not found. Searched paths:") + for path in possible_paths: + print_color(f" - {path}", Colors.YELLOW) + +def main(): + """主函数""" + parser = argparse.ArgumentParser(description="Fortran CFD Project Builder") + + parser.add_argument( + "--build-type", + choices=["Debug", "Release", "RelWithDebInfo", "MinSizeRel"], + default="Debug", + help="Build type (default: Debug)" + ) + + parser.add_argument( + "--clean", + action="store_true", + help="Clean build directory before building" + ) + + parser.add_argument( + "--no-tests", + action="store_true", + help="Skip running tests" + ) + + parser.add_argument( + "-j", "--jobs", + type=int, + default=0, + help="Number of parallel jobs (0 = use all cores)" + ) + + parser.add_argument( + "--force", + action="store_true", + help="Continue on errors" + ) + + parser.add_argument( + "--verbose", + action="store_true", + help="Verbose output" + ) + + args = parser.parse_args() + + if args.jobs == 0: + import multiprocessing + args.jobs = multiprocessing.cpu_count() + + args.run_tests = not args.no_tests + + try: + success = build_project(args) + if not success and not args.force: + sys.exit(1) + except KeyboardInterrupt: + print_color("\nBuild interrupted by user", Colors.YELLOW) + sys.exit(1) + except Exception as e: + print_error(f"Unexpected error: {e}") + if args.verbose: + import traceback + traceback.print_exc() + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01f/scripts/requirements.txt b/example/1d-linear-convection/weno3/fortran/registry/01f/scripts/requirements.txt new file mode 100644 index 000000000..d21a12b45 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01f/scripts/requirements.txt @@ -0,0 +1,2 @@ +# 构建脚本的Python依赖(可选) +# 目前没有特殊依赖,保持空文件或添加未来可能需要的包 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01f/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01f/src/CMakeLists.txt new file mode 100644 index 000000000..ee38952bb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01f/src/CMakeLists.txt @@ -0,0 +1,14 @@ +# ==================== src\CMakeLists.txt ==================== + +# src/CMakeLists.txt +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(core) +add_subdirectory(infrastructure) +add_subdirectory(numerics) + +message(STATUS "源代码目录配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01f/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01f/src/core/CMakeLists.txt new file mode 100644 index 000000000..376dd4fe5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01f/src/core/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/core/CMakeLists.txt +message(STATUS "配置核心模块...") + +add_library(core + registry.f90 + factory_interfaces.f90 +) + +target_include_directories(core PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 安装规则 +install(TARGETS core + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) +install(FILES + ${CMAKE_Fortran_MODULE_DIRECTORY}/registry_module.mod + ${CMAKE_Fortran_MODULE_DIRECTORY}/factory_interfaces.mod + DESTINATION include/fortran_cfd/core +) + +message(STATUS "核心模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01f/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/01f/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..19cc2c59e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01f/src/core/factory_interfaces.f90 @@ -0,0 +1,17 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, factory_procedure + + ! Factory procedure interface + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01f/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/01f/src/core/registry.f90 new file mode 100644 index 000000000..f996c89b3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01f/src/core/registry.f90 @@ -0,0 +1,318 @@ +! src/core/registry.f90 +module registry_module + use, intrinsic :: iso_fortran_env, only: real64 + use factory_interfaces, only: factory_procedure + implicit none + + private + + ! Public interface + public :: real64, component_info, component_registry + public :: register_component_simple, initialize_registry, cleanup_registry + public :: has_component, get_available_components + public :: registry_is_initialized, registry_get_size ! 添加公共访问方法 + + ! Type definitions (simplified, no factory for now) + type :: component_info + character(len=32) :: category = "" + character(len=32) :: name = "" + integer :: order = 0 + contains + procedure :: print => ci_print + end type component_info + + type :: component_registry_type + private + type(component_info), allocatable :: components(:) + integer :: count = 0 + integer :: capacity = 100 + logical :: verbose = .true. + logical :: initialized = .false. + contains + procedure :: register => cr_register + procedure :: get => cr_get + procedure :: list_all => cr_list_all + procedure :: clear => cr_clear + procedure :: size => cr_size + procedure :: is_initialized => cr_is_initialized ! 内部方法 + end type component_registry_type + + ! Global registry instance + type(component_registry_type), save :: component_registry + +contains + + ! ==================== PUBLIC API ==================== + + ! Initialize registry + subroutine initialize_registry(initial_capacity, verbose) + integer, optional, intent(in) :: initial_capacity + logical, optional, intent(in) :: verbose + + if (component_registry%initialized) then + if (component_registry%verbose) then + print *, "[INFO] Registry already initialized" + end if + return + end if + + if (present(initial_capacity)) then + component_registry%capacity = max(10, initial_capacity) + end if + + if (present(verbose)) then + component_registry%verbose = verbose + end if + + ! Allocate array + allocate(component_registry%components(component_registry%capacity)) + + component_registry%initialized = .true. + component_registry%count = 0 + + if (component_registry%verbose) then + print *, "[INIT] Registry initialized, capacity:", component_registry%capacity + end if + end subroutine initialize_registry + + ! Cleanup registry + subroutine cleanup_registry + call component_registry%clear() + if (component_registry%verbose) then + print *, "[CLEANUP] Registry cleaned up" + end if + end subroutine cleanup_registry + + ! Simple registration (no factory) + subroutine register_component_simple(category, name) + character(len=*), intent(in) :: category, name + + type(component_info) :: info + + info%category = to_lower(trim(adjustl(category))) + info%name = to_lower(trim(adjustl(name))) + info%order = 0 + + call component_registry%register(info) + end subroutine register_component_simple + + ! Check if component exists + function has_component(category, name) result(found) + character(len=*), intent(in) :: category, name + logical :: found + + type(component_info) :: info + character(len=32) :: cat_lower, name_lower + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + info = component_registry%get(cat_lower, name_lower) + found = (len_trim(info%category) > 0) + end function has_component + + ! Get available components in a category + subroutine get_available_components(category, names, orders) + character(len=*), intent(in) :: category + character(len=:), allocatable, intent(out), optional :: names(:) + integer, allocatable, intent(out), optional :: orders(:) + + character(len=32) :: cat_lower + integer :: i, count, idx + type(component_info) :: info + + cat_lower = to_lower(trim(adjustl(category))) + + ! Count components in this category + count = 0 + do i = 1, component_registry%count + if (component_registry%components(i)%category == cat_lower) then + count = count + 1 + end if + end do + + ! Allocate arrays if requested + if (present(names)) then + allocate(character(len=32) :: names(count)) + end if + + if (present(orders)) then + allocate(orders(count)) + end if + + ! Fill arrays + idx = 1 + do i = 1, component_registry%count + if (component_registry%components(i)%category == cat_lower) then + info = component_registry%components(i) + if (present(names)) then + names(idx) = info%name + end if + if (present(orders)) then + orders(idx) = info%order + end if + idx = idx + 1 + end if + end do + end subroutine get_available_components + + ! Public function to check if registry is initialized + function registry_is_initialized() result(is_initialized) + logical :: is_initialized + is_initialized = component_registry%is_initialized() + end function registry_is_initialized + + ! Public function to get registry size + function registry_get_size() result(size_val) + integer :: size_val + size_val = component_registry%size() + end function registry_get_size + + ! ==================== COMPONENT INFO METHODS ==================== + + subroutine ci_print(this) + class(component_info), intent(in) :: this + + if (this%order > 0) then + print *, " [", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ")]" + else + print *, " [", trim(this%category), ".", trim(this%name), "]" + end if + end subroutine ci_print + + ! ==================== REGISTRY INTERNAL METHODS ==================== + + subroutine cr_register(this, info) + class(component_registry_type), intent(inout) :: this + type(component_info), intent(in) :: info + + type(component_info), allocatable :: temp(:) + integer :: i + + if (.not. this%initialized) then + error stop "[ERROR] Registry not initialized, call initialize_registry first" + end if + + ! Check if already exists + do i = 1, this%count + if (this%components(i)%category == info%category .and. & + this%components(i)%name == info%name) then + if (this%verbose) then + print *, "[WARN] Overwriting: ", & + trim(info%category), ".", trim(info%name) + end if + this%components(i) = info + return + end if + end do + + ! Expand array if needed + if (this%count >= this%capacity) then + this%capacity = this%capacity * 2 + allocate(temp(this%capacity)) + temp(1:this%count) = this%components(1:this%count) + call move_alloc(temp, this%components) + + if (this%verbose) then + print *, "[INFO] Registry expanded to capacity:", this%capacity + end if + end if + + ! Add component + this%count = this%count + 1 + this%components(this%count) = info + + if (this%verbose) then + print *, "[OK] Registered: ", trim(info%category), ".", trim(info%name) + end if + end subroutine cr_register + + function cr_get(this, category, name) result(info) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category, name + type(component_info) :: info + + integer :: i + + ! Initialize return value as empty + info%category = "" + info%name = "" + info%order = 0 + + if (.not. this%initialized) then + return + end if + + do i = 1, this%count + if (this%components(i)%category == category .and. & + this%components(i)%name == name) then + info = this%components(i) + return + end if + end do + end function cr_get + + subroutine cr_list_all(this) + class(component_registry_type), intent(in) :: this + integer :: i + + if (.not. this%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + print *, "=== Registry Contents (", this%count, " components) ===" + + if (this%count == 0) then + print *, " (empty)" + return + end if + + ! Show components grouped by category + do i = 1, this%count + call this%components(i)%print() + end do + + print *, "===========================================" + end subroutine cr_list_all + + subroutine cr_clear(this) + class(component_registry_type), intent(inout) :: this + + if (allocated(this%components)) then + deallocate(this%components) + end if + + this%count = 0 + this%capacity = 100 + this%initialized = .false. + end subroutine cr_clear + + integer function cr_size(this) + class(component_registry_type), intent(in) :: this + cr_size = this%count + end function cr_size + + logical function cr_is_initialized(this) + class(component_registry_type), intent(in) :: this + cr_is_initialized = this%initialized + end function cr_is_initialized + + ! ==================== UTILITY FUNCTIONS ==================== + + function to_lower(str) result(lower_str) + character(len=*), intent(in) :: str + character(len=len(str)) :: lower_str + integer :: i + + do i = 1, len(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + lower_str(i:i) = char(ichar(str(i:i)) + 32) + else + lower_str(i:i) = str(i:i) + end if + end do + end function to_lower + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01f/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01f/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..2b72c5484 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01f/src/infrastructure/CMakeLists.txt @@ -0,0 +1,20 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "配置基础设施模块...") + +add_library(infrastructure + config.f90 + mesh.f90 +) + +target_link_libraries(infrastructure PUBLIC core) + +target_include_directories(infrastructure PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS infrastructure + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "基础设施模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01f/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/01f/src/infrastructure/config.f90 new file mode 100644 index 000000000..34019f2f5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01f/src/infrastructure/config.f90 @@ -0,0 +1,90 @@ +! src/infrastructure/config.f90 +module config_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, cfd_config, config_print, config_with_reconstruction + + ! CFD configuration type + type :: cfd_config + character(len=20) :: ic_type = "step" + character(len=20) :: recon_scheme = "eno" + character(len=20) :: flux_type = "rusanov" + integer :: rk_order = 1 + real(real64) :: wave_speed = 1.0_real64 + real(real64) :: final_time = 0.625_real64 + real(real64) :: dt = 0.025_real64 + character(len=20) :: boundary_type = "periodic" + real(real64) :: left_boundary_value = 1.0_real64 + real(real64) :: right_boundary_value = 2.0_real64 + integer :: spatial_order = 2 + logical :: verbose = .true. + end type cfd_config + +contains + + subroutine config_print(this) + type(cfd_config), intent(in) :: this + + print *, "=== CFD Configuration ===" + print *, "Initial condition: ", trim(this%ic_type) + print *, "Reconstruction: ", trim(this%recon_scheme), " (order:", this%spatial_order, ")" + print *, "Flux type: ", trim(this%flux_type) + print *, "Time integration: RK", this%rk_order + print *, "Wave speed: ", this%wave_speed + print *, "Final time: ", this%final_time + print *, "Time step: ", this%dt + print *, "Boundary: ", trim(this%boundary_type) + if (trim(this%boundary_type) == 'dirichlet') then + print *, " Dirichlet values: [", this%left_boundary_value, ", ", & + this%right_boundary_value, "]" + end if + print *, "===============================" + end subroutine config_print + + subroutine config_with_reconstruction(this, scheme, order) + type(cfd_config), intent(inout) :: this + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + character(len=20) :: scheme_lower + + ! Convert to lowercase + scheme_lower = scheme + call to_lower_inplace(scheme_lower) + this%recon_scheme = trim(adjustl(scheme_lower)) + + ! Set order + if (present(order)) then + this%spatial_order = order + else + ! Smart defaults + if (index(this%recon_scheme, 'weno') > 0) then + this%spatial_order = 5 + else if (trim(this%recon_scheme) == 'eno') then + this%spatial_order = 3 + else + print *, "[ERROR] Unsupported reconstruction scheme: ", trim(this%recon_scheme) + return + end if + end if + + if (this%verbose) then + print *, "[CONFIG] Reconstruction: ", trim(this%recon_scheme), & + " Order: ", this%spatial_order + end if + end subroutine config_with_reconstruction + + subroutine to_lower_inplace(str) + character(len=*), intent(inout) :: str + integer :: i + + do i = 1, len_trim(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + str(i:i) = char(ichar(str(i:i)) + 32) + end if + end do + end subroutine to_lower_inplace + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01f/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/01f/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..65a45dcdf --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01f/src/infrastructure/mesh.f90 @@ -0,0 +1,74 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, mesh_type, mesh_init, mesh_print_info + + ! 网格类型 + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer :: ncells = 40 + integer :: nnodes + integer :: nx + real(wp), allocatable :: x(:) ! 节点坐标 + real(wp), allocatable :: xcc(:) ! 单元中心坐标 + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer, optional, intent(in) :: ncells + + integer :: i + + ! 设置参数 + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! 计算派生参数 + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! 分配数组 + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! 生成节点坐标 + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! 生成单元中心坐标 + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== 网格信息 ===" + print *, "计算域: [", this%xmin, ", ", this%xmax, "]" + print *, "单元数: ", this%ncells + print *, "节点数: ", this%nnodes + print *, "网格尺寸 dx: ", this%dx + print *, "域长度 L: ", this%L + print *, "==========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..66874a7ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/CMakeLists.txt @@ -0,0 +1,7 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..b2617f290 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,20 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +# Start with just the base module +add_library(flux + base.f90 +) + +target_link_libraries(flux PUBLIC core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/flux/base.f90 new file mode 100644 index 000000000..d9bd640c2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/flux/base.f90 @@ -0,0 +1,24 @@ +! src/numerics/flux/base.f90 +module flux_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, flux_calculator_base + + ! Base flux calculator type + type :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure :: info => flux_info + end type flux_calculator_base + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "Flux calculator information:" + print *, " Name: ", trim(this%name) + end subroutine flux_info + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/flux/rusanov.f90 b/example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/flux/rusanov.f90 new file mode 100644 index 000000000..414f783da --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/flux/rusanov.f90 @@ -0,0 +1,25 @@ +! src/numerics/flux/rusanov.f90 +module rusanov_flux_module + use, intrinsic :: iso_fortran_env, only: real64 + use flux_base_module, only: flux_calculator_base + implicit none + + private + public :: real64, rusanov_flux + + type, extends(flux_calculator_base) :: rusanov_flux + real(real64) :: wave_speed_default = 1.0_real64 + contains + procedure :: info => rusanov_info + end type rusanov_flux + +contains + + subroutine rusanov_info(this) + class(rusanov_flux), intent(in) :: this + call flux_info(this) + print *, " Type: Rusanov flux" + print *, " Default wave speed: ", this%wave_speed_default + end subroutine rusanov_info + +end module rusanov_flux_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..77808c1ad --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,20 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "Configuring reconstructor module...") + +# Start with just the base module +add_library(reconstructor + base.f90 +) + +target_link_libraries(reconstructor PUBLIC core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Reconstructor module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..8d3119ee7 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/reconstructor/base.f90 @@ -0,0 +1,27 @@ +! src/numerics/reconstructor/base.f90 +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, reconstructor_base + + ! Base reconstructor type + type :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + contains + procedure :: info => reconstructor_info + end type reconstructor_base + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + + print *, "Reconstructor information:" + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_info + +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/reconstructor/eno.f90 b/example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/reconstructor/eno.f90 new file mode 100644 index 000000000..19029b6dc --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/reconstructor/eno.f90 @@ -0,0 +1,25 @@ +! src/numerics/reconstructor/eno.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, eno_reconstructor + + type, extends(reconstructor_base) :: eno_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => eno_info + end type eno_reconstructor + +contains + + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + call reconstructor_info(this) + print *, " Type: ENO reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/reconstructor/weno3.f90 b/example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/reconstructor/weno3.f90 new file mode 100644 index 000000000..4f268ac9a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01f/src/numerics/reconstructor/weno3.f90 @@ -0,0 +1,25 @@ +! src/numerics/reconstructor/weno3.f90 +module weno3_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno3_reconstructor + + type, extends(reconstructor_base) :: weno3_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno3_info + end type weno3_reconstructor + +contains + + subroutine weno3_info(this) + class(weno3_reconstructor), intent(in) :: this + call reconstructor_info(this) + print *, " Type: WENO-3 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno3_info + +end module weno3_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01f/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01f/tests/CMakeLists.txt new file mode 100644 index 000000000..a98581897 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01f/tests/CMakeLists.txt @@ -0,0 +1,16 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +# Simple functionality test +add_executable(test_simple + test_minimal_simple.f90 +) +target_link_libraries(test_simple + core + infrastructure +) +target_include_directories(test_simple PRIVATE + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Tests configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01f/tests/test_factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/01f/tests/test_factory.f90 new file mode 100644 index 000000000..8968b9c28 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01f/tests/test_factory.f90 @@ -0,0 +1,154 @@ +! tests/test_factory.f90 +program test_factory + use registry_module + use config_module + use mesh_module + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + class(*), allocatable :: instance + class(reconstructor_base), allocatable :: recon + class(flux_calculator_base), allocatable :: flux_calc + real(wp), allocatable :: q(:), qL(:), qR(:), flux(:) + integer :: i, n + + print *, "=== Factory Pattern Test ===" + print *, "" + + ! Initialize systems + call initialize_registry(verbose=.true.) + + ! Register components with factories + print *, "1. Registering components with factories..." + call register_component_with_factory("reconstructor", "eno", create_eno, 3) + call register_component_with_factory("reconstructor", "weno3", create_weno3, 3) + call register_component_with_factory("flux", "rusanov", create_rusanov) + + call component_registry%list_all() + print *, "" + + ! Test creating ENO reconstructor + print *, "2. Creating ENO reconstructor..." + call create_component("reconstructor", "eno", instance) + + if (allocated(instance)) then + select type (inst => instance) + type is (eno_reconstructor) + allocate(recon, source=inst) + print *, "ENO reconstructor created successfully" + call recon%info() + print *, "" + + ! Test reconstruction + n = 10 + allocate(q(0:n+1), qL(n), qR(n)) + + ! Initialize test data (sine wave) + do i = 0, n+1 + q(i) = sin(2.0_wp * 3.141592653589793_wp * real(i-1, wp) / real(n, wp)) + end do + + print *, "Testing ENO reconstruction..." + call recon%reconstruct(q, qL, qR) + + print *, "q (internal):" + do i = 1, n + write(*, '(I3, F10.6)') i, q(i) + end do + + print *, "qL (left interface values):" + do i = 1, n + write(*, '(I3, F10.6)') i, qL(i) + end do + + deallocate(q, qL, qR) + class default + print *, "[ERROR] Wrong type for ENO reconstructor" + end select + else + print *, "[ERROR] Failed to create ENO reconstructor" + end if + print *, "" + + ! Test creating WENO3 reconstructor + print *, "3. Creating WENO3 reconstructor..." + if (allocated(instance)) deallocate(instance) + call create_component("reconstructor", "weno3", instance) + + if (allocated(instance)) then + select type (inst => instance) + type is (weno3_reconstructor) + if (allocated(recon)) deallocate(recon) + allocate(recon, source=inst) + print *, "WENO3 reconstructor created successfully" + call recon%info() + class default + print *, "[ERROR] Wrong type for WENO3 reconstructor" + end select + else + print *, "[ERROR] Failed to create WENO3 reconstructor" + end if + print *, "" + + ! Test creating Rusanov flux calculator + print *, "4. Creating Rusanov flux calculator..." + if (allocated(instance)) deallocate(instance) + call create_component("flux", "rusanov", instance) + + if (allocated(instance)) then + select type (inst => instance) + type is (rusanov_flux) + allocate(flux_calc, source=inst) + print *, "Rusanov flux calculator created successfully" + call flux_calc%info() + print *, "" + + ! Test flux computation + n = 5 + allocate(qL(n), qR(n), flux(n)) + + ! Initialize test data + do i = 1, n + qL(i) = 1.0_wp + 0.1_wp * real(i-1, wp) + qR(i) = 1.0_wp + 0.1_wp * real(i, wp) + end do + + print *, "Testing Rusanov flux computation..." + call flux_calc%compute(qL, qR, flux, 1.0_wp) + + print *, "qL:" + do i = 1, n + write(*, '(I3, F10.6)') i, qL(i) + end do + + print *, "qR:" + do i = 1, n + write(*, '(I3, F10.6)') i, qR(i) + end do + + print *, "Flux:" + do i = 1, n + write(*, '(I3, F10.6)') i, flux(i) + end do + + deallocate(qL, qR, flux) + class default + print *, "[ERROR] Wrong type for Rusanov flux" + end select + else + print *, "[ERROR] Failed to create Rusanov flux calculator" + end if + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Factory pattern test completed successfully ===" + +end program test_factory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01f/tests/test_minimal.f90 b/example/1d-linear-convection/weno3/fortran/registry/01f/tests/test_minimal.f90 new file mode 100644 index 000000000..bb4b7cda9 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01f/tests/test_minimal.f90 @@ -0,0 +1,108 @@ +! tests/test_minimal.f90 +program test_minimal + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i ! Declare loop variable here + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", component_registry%size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components + print *, "5. Testing available components" + print *, "--------------------------------" + + ! Use a separate block to avoid allocation issues + call test_available_components() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Minimal test completed successfully ===" + +contains + + ! Internal subroutine for testing available components + subroutine test_available_components() + character(len=:), allocatable :: names(:) + integer, allocatable :: orders(:) + integer :: j + + call get_available_components("reconstructor", names, orders) + + if (allocated(names)) then + print *, "Reconstructors:" + do j = 1, size(names) + print *, " - ", trim(names(j)) + if (allocated(orders)) then + print *, " Order: ", orders(j) + end if + end do + else + print *, "No reconstructors found" + end if + end subroutine test_available_components + +end program test_minimal \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01f/tests/test_minimal_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/01f/tests/test_minimal_simple.f90 new file mode 100644 index 000000000..689165de8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01f/tests/test_minimal_simple.f90 @@ -0,0 +1,84 @@ +! tests/test_minimal_simple.f90 +program test_minimal_simple + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Simple Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components (simple version) + print *, "5. Testing registry functionality" + print *, "---------------------------------" + print *, "Registry initialized: ", registry_is_initialized() + print *, "Number of components: ", registry_get_size() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Simple test completed successfully ===" + +end program test_minimal_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01g/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01g/CMakeLists.txt new file mode 100644 index 000000000..ef66d5848 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01g/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01g/README.md b/example/1d-linear-convection/weno3/fortran/registry/01g/README.md new file mode 100644 index 000000000..c4889757b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01g/README.md @@ -0,0 +1,221 @@ +# Fortran CFD Project + +基于工厂模式和注册系统的CFD求解器框架。 + +## 项目结构 + +``` +fortran_cfd/ +├── CMakeLists.txt # 根CMake配置 +├── scripts/ # 构建脚本 +│ ├── build.py # Python构建脚本(推荐) +│ ├── build.bat # Batch包装脚本 +│ ├── build.ps1 # PowerShell包装脚本 +│ └── requirements.txt # Python依赖 +├── src/ # 源代码 +│ ├── CMakeLists.txt +│ ├── core/ # 核心模块(注册系统、工厂接口) +│ ├── infrastructure/ # 基础设施(配置、网格) +│ └── numerics/ # 数值方法 +├── tests/ # 测试 +│ ├── CMakeLists.txt +│ ├── test_minimal_simple.f90 # 简单测试 +│ └── test_factory.f90 # 工厂模式测试 +└── README.md # 项目说明(本文件) +``` + +## 快速开始 + +### 使用Python脚本(推荐) + +```bash +# 进入脚本目录 +cd scripts + +# 基本构建 +python build.py + +# 清理并构建 +python build.py --clean + +# 发布构建 +python build.py --build-type Release --clean + +# 并行构建(使用所有核心) +python build.py -j + +# 不运行测试 +python build.py --no-tests + +# 查看帮助 +python build.py --help +``` + +### 使用批处理脚本 + +```bash +cd scripts +.\build.bat +``` + +### 使用PowerShell脚本 + +```powershell +cd scripts +.\build.ps1 +``` + +## 手动构建 + +```bash +# 设置Intel环境 +cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && powershell' + +# 配置项目 +mkdir build +cd build +cmake -G "Visual Studio 17 2022" -A x64 -T fortran=ifx .. + +# 构建项目 +cmake --build . --config Debug + +# 运行测试 +Debug\test_simple.exe +Debug\test_factory.exe +``` + +## 功能特性 + +✅ 组件注册系统 +✅ 工厂模式 +✅ ENO/WENO重构器 +✅ Rusanov通量计算器 +✅ 可扩展架构 +✅ 自动化测试 + +## 依赖 + +- Intel oneAPI(包含ifx编译器) +- CMake 3.12+ +- Python 3.6+(仅用于构建脚本) + +## 源代码文件 + +### 核心模块 +- `src/core/registry.f90` - 组件注册系统 +- `src/core/factory_interfaces.f90` - 工厂接口 + +### 基础设施 +- `src/infrastructure/config.f90` - 配置管理 +- `src/infrastructure/mesh.f90` - 网格生成 + +### 数值方法 +- `src/numerics/reconstructor/base.f90` - 重构器基类 +- `src/numerics/reconstructor/eno.f90` - ENO重构器 +- `src/numerics/reconstructor/weno3.f90` - WENO-3重构器 +- `src/numerics/flux/base.f90` - 通量计算器基类 +- `src/numerics/flux/rusanov.f90` - Rusanov通量 + +### 测试 +- `tests/test_minimal_simple.f90` - 简单功能测试 +- `tests/test_factory.f90` - 工厂模式测试 + +## 构建脚本 + +### Python脚本 (build.py) +主构建脚本,支持: +- 自动设置Intel oneAPI环境 +- 参数化构建选项 +- 并行编译 +- 自动化测试 +- 彩色输出 + +### Batch脚本 (build.bat) +Windows批处理包装器,调用Python脚本。 + +### PowerShell脚本 (build.ps1) +PowerShell包装器,调用Python脚本。 + +## 示例输出 + +成功构建后,会看到类似输出: + +``` +============================================================ + Fortran CFD Project Builder +============================================================ + +[1/4] Setting up Intel Fortran compiler... +✓ Intel oneAPI environment configured + +[2/4] Preparing build directory... +✓ Cleaned build directory + +[3/4] Configuring project... + $ cmake -G Visual Studio 17 2022 -A x64 -T fortran=ifx -DCMAKE_BUILD_TYPE=Debug .. +✓ CMake configuration successful + +[4/4] Building project... + $ cmake --build . --config Debug +✓ Build completed in 12.3 seconds + +============================================================ + Running Tests +============================================================ + +[TEST] Simple functionality test... +✓ Simple test passed + +[TEST] Factory pattern test... +✓ Factory test passed + +============================================================ + Build Summary +============================================================ +✓ Build directory: D:\path\to\build +✓ Build type: Debug +✓ Total time: 15.2s +``` + +## 开发指南 + +### 添加新组件 + +1. 在相应模块中创建Fortran源文件 +2. 实现工厂函数 +3. 使用`register_component_with_factory`注册组件 +4. 添加测试 + +### 扩展架构 + +项目采用模块化设计: +1. **注册系统** - 管理所有可用的组件 +2. **工厂模式** - 动态创建组件实例 +3. **抽象接口** - 确保组件兼容性 +4. **配置驱动** - 通过配置文件控制行为 + +## 故障排除 + +### 常见问题 + +1. **Intel环境未找到** + - 检查Intel oneAPI安装路径 + - 手动运行`setvars.bat` + +2. **CMake配置失败** + - 确保使用正确的生成器:`Visual Studio 17 2022` + - 检查Fortran编译器是否可用 + +3. **构建失败** + - 检查依赖项是否完整 + - 查看详细的错误信息 + +### 调试建议 + +- 使用`--verbose`参数获取详细输出 +- 检查`build/CMakeCache.txt`了解配置详情 +- 查看编译器错误日志 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01g/scripts/build.bat b/example/1d-linear-convection/weno3/fortran/registry/01g/scripts/build.bat new file mode 100644 index 000000000..d243695b0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01g/scripts/build.bat @@ -0,0 +1,26 @@ +@echo off +echo ======================================== +echo Fortran CFD Project Builder +echo (Wrapper for Python script) +echo ======================================== +echo. + +REM 检查Python +where python >nul 2>nul +if %errorlevel% neq 0 ( + echo [ERROR] Python not found. Please install Python 3. + pause + exit /b 1 +) + +REM 运行Python构建脚本 +echo [INFO] Running Python build script... +python build.py %* + +if %errorlevel% neq 0 ( + echo [ERROR] Build failed + pause + exit /b 1 +) + +pause \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01g/scripts/build.ps1 b/example/1d-linear-convection/weno3/fortran/registry/01g/scripts/build.ps1 new file mode 100644 index 000000000..4497c5ebd --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01g/scripts/build.ps1 @@ -0,0 +1,32 @@ +# Fortran CFD Project Builder (PowerShell wrapper) + +Write-Host "========================================" -ForegroundColor Cyan +Write-Host " Fortran CFD Project Builder" -ForegroundColor Cyan +Write-Host " (PowerShell wrapper for Python script)" -ForegroundColor Cyan +Write-Host "========================================" -ForegroundColor Cyan +Write-Host "" + +# 检查Python +$python = Get-Command python -ErrorAction SilentlyContinue +if (-not $python) { + $python = Get-Command python3 -ErrorAction SilentlyContinue +} + +if (-not $python) { + Write-Host "[ERROR] Python not found. Please install Python 3." -ForegroundColor Red + Read-Host "Press Enter to exit" + exit 1 +} + +# 运行Python构建脚本 +Write-Host "[INFO] Running Python build script..." -ForegroundColor Yellow +$argsString = $args -join ' ' +$command = "python build.py $argsString" + +Invoke-Expression $command + +if ($LASTEXITCODE -ne 0) { + Write-Host "[ERROR] Build failed" -ForegroundColor Red + Read-Host "Press Enter to exit" + exit 1 +} \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01g/scripts/build.py b/example/1d-linear-convection/weno3/fortran/registry/01g/scripts/build.py new file mode 100644 index 000000000..1c99c2c87 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01g/scripts/build.py @@ -0,0 +1,357 @@ +#!/usr/bin/env python3 +""" +Fortran CFD 项目构建脚本 (Python版) +支持 Intel oneAPI 环境的自动化构建 +""" + +import os +import sys +import subprocess +import shutil +from pathlib import Path +import argparse +import platform +import time + +class Colors: + """终端颜色""" + if platform.system() == "Windows": + # Windows 启用 ANSI + os.system("") + + HEADER = '\033[95m' + BLUE = '\033[94m' + CYAN = '\033[96m' + GREEN = '\033[92m' + YELLOW = '\033[93m' + RED = '\033[91m' + MAGENTA = '\033[95m' + DARK_GRAY = '\033[90m' # 添加这个 + END = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + +def print_color(text, color=Colors.END): + """彩色打印""" + print(f"{color}{text}{Colors.END}") + +def print_header(text): + """打印标题""" + print_color("\n" + "="*60, Colors.CYAN) + print_color(f" {text}", Colors.BOLD + Colors.CYAN) + print_color("="*60 + "\n", Colors.CYAN) + +def print_step(step, total, message): + """打印步骤""" + print_color(f"[{step}/{total}] {message}...", Colors.YELLOW) + +def print_success(message): + """打印成功""" + print_color(f"✓ {message}", Colors.GREEN) + +def print_error(message): + """打印错误""" + print_color(f"✗ {message}", Colors.RED) + +def print_warning(message): + """打印警告""" + print_color(f"! {message}", Colors.YELLOW) + +def run_command(cmd, cwd=None, check=True, capture=True): + """运行命令""" + print_color(f" $ {' '.join(cmd)}", Colors.BLUE) + + try: + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=capture, + text=True, + encoding='utf-8', + errors='ignore', + shell=False + ) + + if capture and result.stdout: + print(result.stdout) + if capture and result.stderr: + print_color(result.stderr, Colors.YELLOW) + + if check and result.returncode != 0: + print_error(f"Command failed with exit code: {result.returncode}") + return False + + return True + + except FileNotFoundError as e: + print_error(f"Command not found: {cmd[0]}") + if check: + raise + return False + except Exception as e: + print_error(f"Command execution failed: {e}") + return False + +def setup_intel_environment(): + """设置 Intel oneAPI 环境""" + setvars_paths = [ + r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat", + r"C:\Program Files\Intel\oneAPI\setvars.bat", + ] + + setvars_path = None + for path in setvars_paths: + if os.path.exists(path): + setvars_path = path + break + + if not setvars_path: + print_error("Intel oneAPI setvars.bat not found.") + print_warning("Please install Intel oneAPI or update the path in build.py") + return False + + # 创建临时的 batch 文件 + temp_bat = "temp_setvars.bat" + with open(temp_bat, 'w') as f: + f.write(f'@echo off\n') + f.write(f'call "{setvars_path}" > nul 2>&1\n') + f.write(f'set\n') + + try: + # 运行并捕获环境变量 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + shell=True, + encoding='utf-8', + errors='ignore' + ) + + # 解析并设置环境变量 + for line in result.stdout.split('\n'): + if '=' in line: + key, value = line.split('=', 1) + os.environ[key.strip()] = value.strip() + + os.remove(temp_bat) + print_success("Intel oneAPI environment configured") + return True + + except Exception as e: + print_error(f"Failed to setup Intel environment: {e}") + if os.path.exists(temp_bat): + os.remove(temp_bat) + return False + +def build_project(args): + """构建项目主函数""" + start_time = time.time() + + print_header(f"Fortran CFD Project Builder") + print_color(f"Build type: {args.build_type}", Colors.CYAN) + print_color(f"Run tests: {args.run_tests}", Colors.CYAN) + print() + + # 获取项目根目录(脚本所在目录的父目录) + script_dir = Path(__file__).parent + project_root = script_dir.parent + os.chdir(project_root) + + print_color(f"Project root: {project_root}", Colors.BLUE) + + # 步骤1: 设置 Intel 环境 + print_step(1, 4, "Setting up Intel Fortran compiler") + if not setup_intel_environment(): + return False + + # 步骤2: 准备构建目录 + print_step(2, 4, "Preparing build directory") + build_dir = project_root / "build" + + if args.clean and build_dir.exists(): + try: + shutil.rmtree(build_dir) + print_success("Cleaned build directory") + except Exception as e: + print_error(f"Failed to clean build directory: {e}") + if not args.force: + return False + + build_dir.mkdir(exist_ok=True) + os.chdir(build_dir) + + # 步骤3: 配置项目 + print_step(3, 4, "Configuring project") + + cmake_cmd = [ + "cmake", + "-G", "Visual Studio 17 2022", + "-A", "x64", + "-T", "fortran=ifx", + f"-DCMAKE_BUILD_TYPE={args.build_type}", + ".." + ] + + if not run_command(cmake_cmd, check=not args.force): + if not args.force: + return False + + # 步骤4: 构建项目 + print_step(4, 4, "Building project") + + build_cmd = [ + "cmake", + "--build", ".", + "--config", args.build_type, + f"-j{args.jobs}" if args.jobs > 1 else "" + ] + + print(f"build_cmd={build_cmd}") + build_cmd = [c for c in build_cmd if c] # 移除空字符串 + + if not run_command(build_cmd, check=not args.force): + if not args.force: + return False + + build_time = time.time() - start_time + print_success(f"Build completed in {build_time:.1f} seconds") + + # 运行测试 + if args.run_tests: + print_header("Running Tests") + run_tests(args.build_type) + + print_header("Build Summary") + print_color(f"Build directory: {build_dir}", Colors.GREEN) + print_color(f"Build type: {args.build_type}", Colors.GREEN) + print_color(f"Total time: {build_time:.1f}s", Colors.GREEN) + + return True + +def run_tests(build_type): + """运行测试""" + tests = [ + ("test_simple", "Simple functionality test"), + ("test_factory", "Factory pattern test"), + ] + + for test_name, description in tests: + # 修复:在这里直接访问当前循环的 test_name + _run_single_test(test_name, description, build_type) + +def _run_single_test(test_name, description, build_type): + """运行单个测试(修复作用域问题)""" + # 可能的测试可执行文件路径 + possible_paths = [ + Path(f"./{build_type}/{test_name}.exe"), + Path(f"./tests/{build_type}/{test_name}.exe"), + Path(f"./tests/{test_name}.exe"), + Path(f"{test_name}.exe"), + Path(f"./{test_name}.exe"), + ] + + test_exe = None + + # 尝试所有可能的路径 + for path in possible_paths: + if path.exists(): + test_exe = path + break + + if test_exe: + print_color(f"\n[TEST] {description}...", Colors.MAGENTA) + print_color("-" * 50, Colors.DARK_GRAY) + + try: + result = subprocess.run( + [str(test_exe)], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + + if result.stdout: + print(result.stdout) + + if result.returncode == 0: + print_success(f"{test_name} passed") + else: + print_error(f"{test_name} failed (exit code: {result.returncode})") + if result.stderr: + print_color(result.stderr, Colors.YELLOW) + + except Exception as e: + print_error(f"{test_name} failed: {e}") + else: + print_warning(f"{test_name}.exe not found. Searched paths:") + for path in possible_paths: + print_color(f" - {path}", Colors.YELLOW) + +def main(): + """主函数""" + parser = argparse.ArgumentParser(description="Fortran CFD Project Builder") + + parser.add_argument( + "--build-type", + choices=["Debug", "Release", "RelWithDebInfo", "MinSizeRel"], + default="Debug", + help="Build type (default: Debug)" + ) + + parser.add_argument( + "--clean", + action="store_true", + help="Clean build directory before building" + ) + + parser.add_argument( + "--no-tests", + action="store_true", + help="Skip running tests" + ) + + parser.add_argument( + "-j", "--jobs", + type=int, + default=0, + help="Number of parallel jobs (0 = use all cores)" + ) + + parser.add_argument( + "--force", + action="store_true", + help="Continue on errors" + ) + + parser.add_argument( + "--verbose", + action="store_true", + help="Verbose output" + ) + + args = parser.parse_args() + + if args.jobs == 0: + import multiprocessing + args.jobs = multiprocessing.cpu_count() + + args.run_tests = not args.no_tests + + try: + success = build_project(args) + if not success and not args.force: + sys.exit(1) + except KeyboardInterrupt: + print_color("\nBuild interrupted by user", Colors.YELLOW) + sys.exit(1) + except Exception as e: + print_error(f"Unexpected error: {e}") + if args.verbose: + import traceback + traceback.print_exc() + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01g/scripts/requirements.txt b/example/1d-linear-convection/weno3/fortran/registry/01g/scripts/requirements.txt new file mode 100644 index 000000000..d21a12b45 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01g/scripts/requirements.txt @@ -0,0 +1,2 @@ +# 构建脚本的Python依赖(可选) +# 目前没有特殊依赖,保持空文件或添加未来可能需要的包 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01g/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01g/src/CMakeLists.txt new file mode 100644 index 000000000..ee38952bb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01g/src/CMakeLists.txt @@ -0,0 +1,14 @@ +# ==================== src\CMakeLists.txt ==================== + +# src/CMakeLists.txt +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(core) +add_subdirectory(infrastructure) +add_subdirectory(numerics) + +message(STATUS "源代码目录配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01g/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01g/src/core/CMakeLists.txt new file mode 100644 index 000000000..376dd4fe5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01g/src/core/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/core/CMakeLists.txt +message(STATUS "配置核心模块...") + +add_library(core + registry.f90 + factory_interfaces.f90 +) + +target_include_directories(core PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 安装规则 +install(TARGETS core + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) +install(FILES + ${CMAKE_Fortran_MODULE_DIRECTORY}/registry_module.mod + ${CMAKE_Fortran_MODULE_DIRECTORY}/factory_interfaces.mod + DESTINATION include/fortran_cfd/core +) + +message(STATUS "核心模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01g/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/01g/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..19cc2c59e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01g/src/core/factory_interfaces.f90 @@ -0,0 +1,17 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, factory_procedure + + ! Factory procedure interface + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01g/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/01g/src/core/registry.f90 new file mode 100644 index 000000000..f996c89b3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01g/src/core/registry.f90 @@ -0,0 +1,318 @@ +! src/core/registry.f90 +module registry_module + use, intrinsic :: iso_fortran_env, only: real64 + use factory_interfaces, only: factory_procedure + implicit none + + private + + ! Public interface + public :: real64, component_info, component_registry + public :: register_component_simple, initialize_registry, cleanup_registry + public :: has_component, get_available_components + public :: registry_is_initialized, registry_get_size ! 添加公共访问方法 + + ! Type definitions (simplified, no factory for now) + type :: component_info + character(len=32) :: category = "" + character(len=32) :: name = "" + integer :: order = 0 + contains + procedure :: print => ci_print + end type component_info + + type :: component_registry_type + private + type(component_info), allocatable :: components(:) + integer :: count = 0 + integer :: capacity = 100 + logical :: verbose = .true. + logical :: initialized = .false. + contains + procedure :: register => cr_register + procedure :: get => cr_get + procedure :: list_all => cr_list_all + procedure :: clear => cr_clear + procedure :: size => cr_size + procedure :: is_initialized => cr_is_initialized ! 内部方法 + end type component_registry_type + + ! Global registry instance + type(component_registry_type), save :: component_registry + +contains + + ! ==================== PUBLIC API ==================== + + ! Initialize registry + subroutine initialize_registry(initial_capacity, verbose) + integer, optional, intent(in) :: initial_capacity + logical, optional, intent(in) :: verbose + + if (component_registry%initialized) then + if (component_registry%verbose) then + print *, "[INFO] Registry already initialized" + end if + return + end if + + if (present(initial_capacity)) then + component_registry%capacity = max(10, initial_capacity) + end if + + if (present(verbose)) then + component_registry%verbose = verbose + end if + + ! Allocate array + allocate(component_registry%components(component_registry%capacity)) + + component_registry%initialized = .true. + component_registry%count = 0 + + if (component_registry%verbose) then + print *, "[INIT] Registry initialized, capacity:", component_registry%capacity + end if + end subroutine initialize_registry + + ! Cleanup registry + subroutine cleanup_registry + call component_registry%clear() + if (component_registry%verbose) then + print *, "[CLEANUP] Registry cleaned up" + end if + end subroutine cleanup_registry + + ! Simple registration (no factory) + subroutine register_component_simple(category, name) + character(len=*), intent(in) :: category, name + + type(component_info) :: info + + info%category = to_lower(trim(adjustl(category))) + info%name = to_lower(trim(adjustl(name))) + info%order = 0 + + call component_registry%register(info) + end subroutine register_component_simple + + ! Check if component exists + function has_component(category, name) result(found) + character(len=*), intent(in) :: category, name + logical :: found + + type(component_info) :: info + character(len=32) :: cat_lower, name_lower + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + info = component_registry%get(cat_lower, name_lower) + found = (len_trim(info%category) > 0) + end function has_component + + ! Get available components in a category + subroutine get_available_components(category, names, orders) + character(len=*), intent(in) :: category + character(len=:), allocatable, intent(out), optional :: names(:) + integer, allocatable, intent(out), optional :: orders(:) + + character(len=32) :: cat_lower + integer :: i, count, idx + type(component_info) :: info + + cat_lower = to_lower(trim(adjustl(category))) + + ! Count components in this category + count = 0 + do i = 1, component_registry%count + if (component_registry%components(i)%category == cat_lower) then + count = count + 1 + end if + end do + + ! Allocate arrays if requested + if (present(names)) then + allocate(character(len=32) :: names(count)) + end if + + if (present(orders)) then + allocate(orders(count)) + end if + + ! Fill arrays + idx = 1 + do i = 1, component_registry%count + if (component_registry%components(i)%category == cat_lower) then + info = component_registry%components(i) + if (present(names)) then + names(idx) = info%name + end if + if (present(orders)) then + orders(idx) = info%order + end if + idx = idx + 1 + end if + end do + end subroutine get_available_components + + ! Public function to check if registry is initialized + function registry_is_initialized() result(is_initialized) + logical :: is_initialized + is_initialized = component_registry%is_initialized() + end function registry_is_initialized + + ! Public function to get registry size + function registry_get_size() result(size_val) + integer :: size_val + size_val = component_registry%size() + end function registry_get_size + + ! ==================== COMPONENT INFO METHODS ==================== + + subroutine ci_print(this) + class(component_info), intent(in) :: this + + if (this%order > 0) then + print *, " [", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ")]" + else + print *, " [", trim(this%category), ".", trim(this%name), "]" + end if + end subroutine ci_print + + ! ==================== REGISTRY INTERNAL METHODS ==================== + + subroutine cr_register(this, info) + class(component_registry_type), intent(inout) :: this + type(component_info), intent(in) :: info + + type(component_info), allocatable :: temp(:) + integer :: i + + if (.not. this%initialized) then + error stop "[ERROR] Registry not initialized, call initialize_registry first" + end if + + ! Check if already exists + do i = 1, this%count + if (this%components(i)%category == info%category .and. & + this%components(i)%name == info%name) then + if (this%verbose) then + print *, "[WARN] Overwriting: ", & + trim(info%category), ".", trim(info%name) + end if + this%components(i) = info + return + end if + end do + + ! Expand array if needed + if (this%count >= this%capacity) then + this%capacity = this%capacity * 2 + allocate(temp(this%capacity)) + temp(1:this%count) = this%components(1:this%count) + call move_alloc(temp, this%components) + + if (this%verbose) then + print *, "[INFO] Registry expanded to capacity:", this%capacity + end if + end if + + ! Add component + this%count = this%count + 1 + this%components(this%count) = info + + if (this%verbose) then + print *, "[OK] Registered: ", trim(info%category), ".", trim(info%name) + end if + end subroutine cr_register + + function cr_get(this, category, name) result(info) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category, name + type(component_info) :: info + + integer :: i + + ! Initialize return value as empty + info%category = "" + info%name = "" + info%order = 0 + + if (.not. this%initialized) then + return + end if + + do i = 1, this%count + if (this%components(i)%category == category .and. & + this%components(i)%name == name) then + info = this%components(i) + return + end if + end do + end function cr_get + + subroutine cr_list_all(this) + class(component_registry_type), intent(in) :: this + integer :: i + + if (.not. this%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + print *, "=== Registry Contents (", this%count, " components) ===" + + if (this%count == 0) then + print *, " (empty)" + return + end if + + ! Show components grouped by category + do i = 1, this%count + call this%components(i)%print() + end do + + print *, "===========================================" + end subroutine cr_list_all + + subroutine cr_clear(this) + class(component_registry_type), intent(inout) :: this + + if (allocated(this%components)) then + deallocate(this%components) + end if + + this%count = 0 + this%capacity = 100 + this%initialized = .false. + end subroutine cr_clear + + integer function cr_size(this) + class(component_registry_type), intent(in) :: this + cr_size = this%count + end function cr_size + + logical function cr_is_initialized(this) + class(component_registry_type), intent(in) :: this + cr_is_initialized = this%initialized + end function cr_is_initialized + + ! ==================== UTILITY FUNCTIONS ==================== + + function to_lower(str) result(lower_str) + character(len=*), intent(in) :: str + character(len=len(str)) :: lower_str + integer :: i + + do i = 1, len(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + lower_str(i:i) = char(ichar(str(i:i)) + 32) + else + lower_str(i:i) = str(i:i) + end if + end do + end function to_lower + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01g/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01g/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..2b72c5484 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01g/src/infrastructure/CMakeLists.txt @@ -0,0 +1,20 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "配置基础设施模块...") + +add_library(infrastructure + config.f90 + mesh.f90 +) + +target_link_libraries(infrastructure PUBLIC core) + +target_include_directories(infrastructure PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS infrastructure + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "基础设施模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01g/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/01g/src/infrastructure/config.f90 new file mode 100644 index 000000000..34019f2f5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01g/src/infrastructure/config.f90 @@ -0,0 +1,90 @@ +! src/infrastructure/config.f90 +module config_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, cfd_config, config_print, config_with_reconstruction + + ! CFD configuration type + type :: cfd_config + character(len=20) :: ic_type = "step" + character(len=20) :: recon_scheme = "eno" + character(len=20) :: flux_type = "rusanov" + integer :: rk_order = 1 + real(real64) :: wave_speed = 1.0_real64 + real(real64) :: final_time = 0.625_real64 + real(real64) :: dt = 0.025_real64 + character(len=20) :: boundary_type = "periodic" + real(real64) :: left_boundary_value = 1.0_real64 + real(real64) :: right_boundary_value = 2.0_real64 + integer :: spatial_order = 2 + logical :: verbose = .true. + end type cfd_config + +contains + + subroutine config_print(this) + type(cfd_config), intent(in) :: this + + print *, "=== CFD Configuration ===" + print *, "Initial condition: ", trim(this%ic_type) + print *, "Reconstruction: ", trim(this%recon_scheme), " (order:", this%spatial_order, ")" + print *, "Flux type: ", trim(this%flux_type) + print *, "Time integration: RK", this%rk_order + print *, "Wave speed: ", this%wave_speed + print *, "Final time: ", this%final_time + print *, "Time step: ", this%dt + print *, "Boundary: ", trim(this%boundary_type) + if (trim(this%boundary_type) == 'dirichlet') then + print *, " Dirichlet values: [", this%left_boundary_value, ", ", & + this%right_boundary_value, "]" + end if + print *, "===============================" + end subroutine config_print + + subroutine config_with_reconstruction(this, scheme, order) + type(cfd_config), intent(inout) :: this + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + character(len=20) :: scheme_lower + + ! Convert to lowercase + scheme_lower = scheme + call to_lower_inplace(scheme_lower) + this%recon_scheme = trim(adjustl(scheme_lower)) + + ! Set order + if (present(order)) then + this%spatial_order = order + else + ! Smart defaults + if (index(this%recon_scheme, 'weno') > 0) then + this%spatial_order = 5 + else if (trim(this%recon_scheme) == 'eno') then + this%spatial_order = 3 + else + print *, "[ERROR] Unsupported reconstruction scheme: ", trim(this%recon_scheme) + return + end if + end if + + if (this%verbose) then + print *, "[CONFIG] Reconstruction: ", trim(this%recon_scheme), & + " Order: ", this%spatial_order + end if + end subroutine config_with_reconstruction + + subroutine to_lower_inplace(str) + character(len=*), intent(inout) :: str + integer :: i + + do i = 1, len_trim(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + str(i:i) = char(ichar(str(i:i)) + 32) + end if + end do + end subroutine to_lower_inplace + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01g/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/01g/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..65a45dcdf --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01g/src/infrastructure/mesh.f90 @@ -0,0 +1,74 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, mesh_type, mesh_init, mesh_print_info + + ! 网格类型 + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer :: ncells = 40 + integer :: nnodes + integer :: nx + real(wp), allocatable :: x(:) ! 节点坐标 + real(wp), allocatable :: xcc(:) ! 单元中心坐标 + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer, optional, intent(in) :: ncells + + integer :: i + + ! 设置参数 + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! 计算派生参数 + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! 分配数组 + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! 生成节点坐标 + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! 生成单元中心坐标 + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== 网格信息 ===" + print *, "计算域: [", this%xmin, ", ", this%xmax, "]" + print *, "单元数: ", this%ncells + print *, "节点数: ", this%nnodes + print *, "网格尺寸 dx: ", this%dx + print *, "域长度 L: ", this%L + print *, "==========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..66874a7ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/CMakeLists.txt @@ -0,0 +1,7 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..b2617f290 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,20 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +# Start with just the base module +add_library(flux + base.f90 +) + +target_link_libraries(flux PUBLIC core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/flux/base.f90 new file mode 100644 index 000000000..d9bd640c2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/flux/base.f90 @@ -0,0 +1,24 @@ +! src/numerics/flux/base.f90 +module flux_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, flux_calculator_base + + ! Base flux calculator type + type :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure :: info => flux_info + end type flux_calculator_base + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "Flux calculator information:" + print *, " Name: ", trim(this%name) + end subroutine flux_info + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/flux/rusanov.f90 b/example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/flux/rusanov.f90 new file mode 100644 index 000000000..4c1054ab3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/flux/rusanov.f90 @@ -0,0 +1,48 @@ +! src/numerics/flux/rusanov.f90 +module rusanov_flux_module + use, intrinsic :: iso_fortran_env, only: real64 + use flux_base_module, only: flux_calculator_base + implicit none + + private + public :: real64, rusanov_flux + + type, extends(flux_calculator_base) :: rusanov_flux + real(real64) :: wave_speed_default = 1.0_real64 + contains + procedure :: info => rusanov_info + end type rusanov_flux + + ! 添加构造函数接口 + interface rusanov_flux + module procedure create_rusanov_flux + end interface + +contains + + ! 构造函数 + type(rusanov_flux) function create_rusanov_flux(name, wave_speed_default) result(this) + character(len=*), optional, intent(in) :: name + real(real64), optional, intent(in) :: wave_speed_default + + if (present(name)) then + this%name = name + else + this%name = "Rusanov" + end if + + if (present(wave_speed_default)) then + this%wave_speed_default = wave_speed_default + else + this%wave_speed_default = 1.0_real64 + end if + end function create_rusanov_flux + + subroutine rusanov_info(this) + class(rusanov_flux), intent(in) :: this + call flux_info(this) + print *, " Type: Rusanov flux" + print *, " Default wave speed: ", this%wave_speed_default + end subroutine rusanov_info + +end module rusanov_flux_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..77808c1ad --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,20 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "Configuring reconstructor module...") + +# Start with just the base module +add_library(reconstructor + base.f90 +) + +target_link_libraries(reconstructor PUBLIC core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Reconstructor module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..404535d1e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/reconstructor/base.f90 @@ -0,0 +1,40 @@ +! src/numerics/reconstructor/base.f90 +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, reconstructor_base + + ! Base reconstructor type + type :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + contains + procedure :: info => reconstructor_info + procedure :: reconstruct => reconstruct_default ! 添加这个方法 + end type reconstructor_base + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + print *, "Reconstructor information:" + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_info + + ! 默认的重构方法 + subroutine reconstruct_default(this, q, qL, qR) + class(reconstructor_base), intent(in) :: this + real(real64), intent(in) :: q(0:) ! 包含ghost cells + real(real64), intent(out) :: qL(:), qR(:) + integer :: i, n + + n = size(qL) + do i = 1, n + qL(i) = q(i) ! 简单的一阶重构 + qR(i) = q(i+1) + end do + end subroutine reconstruct_default +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/reconstructor/eno.f90 b/example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/reconstructor/eno.f90 new file mode 100644 index 000000000..8bc206ac7 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/reconstructor/eno.f90 @@ -0,0 +1,56 @@ +! src/numerics/reconstructor/eno.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, eno_reconstructor + + type, extends(reconstructor_base) :: eno_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => eno_info + end type eno_reconstructor + + ! 添加构造函数接口 + interface eno_reconstructor + module procedure create_eno_reconstructor + end interface + +contains + + ! 构造函数 + type(eno_reconstructor) function create_eno_reconstructor(name, order, epsilon) result(this) + character(len=*), optional, intent(in) :: name + integer, optional, intent(in) :: order + real(real64), optional, intent(in) :: epsilon + + ! 设置默认值 + if (present(name)) then + this%name = name + else + this%name = "ENO" + end if + + if (present(order)) then + this%order = order + else + this%order = 3 + end if + + if (present(epsilon)) then + this%epsilon = epsilon + else + this%epsilon = 1.0e-6_real64 + end if + end function create_eno_reconstructor + + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + call reconstructor_info(this) + print *, " Type: ENO reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/reconstructor/weno3.f90 b/example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/reconstructor/weno3.f90 new file mode 100644 index 000000000..ffd500163 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01g/src/numerics/reconstructor/weno3.f90 @@ -0,0 +1,55 @@ +! src/numerics/reconstructor/weno3.f90 +module weno3_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno3_reconstructor + + type, extends(reconstructor_base) :: weno3_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno3_info + end type weno3_reconstructor + + ! 添加构造函数接口 + interface weno3_reconstructor + module procedure create_weno3_reconstructor + end interface + +contains + + ! 构造函数 + type(weno3_reconstructor) function create_weno3_reconstructor(name, order, epsilon) result(this) + character(len=*), optional, intent(in) :: name + integer, optional, intent(in) :: order + real(real64), optional, intent(in) :: epsilon + + if (present(name)) then + this%name = name + else + this%name = "WENO3" + end if + + if (present(order)) then + this%order = order + else + this%order = 3 + end if + + if (present(epsilon)) then + this%epsilon = epsilon + else + this%epsilon = 1.0e-6_real64 + end if + end function create_weno3_reconstructor + + subroutine weno3_info(this) + class(weno3_reconstructor), intent(in) :: this + call reconstructor_info(this) + print *, " Type: WENO-3 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno3_info + +end module weno3_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01g/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01g/tests/CMakeLists.txt new file mode 100644 index 000000000..d469614d9 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01g/tests/CMakeLists.txt @@ -0,0 +1,4 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +add_executable(test_minimal_simple test_minimal_simple.f90) diff --git a/example/1d-linear-convection/weno3/fortran/registry/01g/tests/test_factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/01g/tests/test_factory.f90 new file mode 100644 index 000000000..8968b9c28 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01g/tests/test_factory.f90 @@ -0,0 +1,154 @@ +! tests/test_factory.f90 +program test_factory + use registry_module + use config_module + use mesh_module + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + class(*), allocatable :: instance + class(reconstructor_base), allocatable :: recon + class(flux_calculator_base), allocatable :: flux_calc + real(wp), allocatable :: q(:), qL(:), qR(:), flux(:) + integer :: i, n + + print *, "=== Factory Pattern Test ===" + print *, "" + + ! Initialize systems + call initialize_registry(verbose=.true.) + + ! Register components with factories + print *, "1. Registering components with factories..." + call register_component_with_factory("reconstructor", "eno", create_eno, 3) + call register_component_with_factory("reconstructor", "weno3", create_weno3, 3) + call register_component_with_factory("flux", "rusanov", create_rusanov) + + call component_registry%list_all() + print *, "" + + ! Test creating ENO reconstructor + print *, "2. Creating ENO reconstructor..." + call create_component("reconstructor", "eno", instance) + + if (allocated(instance)) then + select type (inst => instance) + type is (eno_reconstructor) + allocate(recon, source=inst) + print *, "ENO reconstructor created successfully" + call recon%info() + print *, "" + + ! Test reconstruction + n = 10 + allocate(q(0:n+1), qL(n), qR(n)) + + ! Initialize test data (sine wave) + do i = 0, n+1 + q(i) = sin(2.0_wp * 3.141592653589793_wp * real(i-1, wp) / real(n, wp)) + end do + + print *, "Testing ENO reconstruction..." + call recon%reconstruct(q, qL, qR) + + print *, "q (internal):" + do i = 1, n + write(*, '(I3, F10.6)') i, q(i) + end do + + print *, "qL (left interface values):" + do i = 1, n + write(*, '(I3, F10.6)') i, qL(i) + end do + + deallocate(q, qL, qR) + class default + print *, "[ERROR] Wrong type for ENO reconstructor" + end select + else + print *, "[ERROR] Failed to create ENO reconstructor" + end if + print *, "" + + ! Test creating WENO3 reconstructor + print *, "3. Creating WENO3 reconstructor..." + if (allocated(instance)) deallocate(instance) + call create_component("reconstructor", "weno3", instance) + + if (allocated(instance)) then + select type (inst => instance) + type is (weno3_reconstructor) + if (allocated(recon)) deallocate(recon) + allocate(recon, source=inst) + print *, "WENO3 reconstructor created successfully" + call recon%info() + class default + print *, "[ERROR] Wrong type for WENO3 reconstructor" + end select + else + print *, "[ERROR] Failed to create WENO3 reconstructor" + end if + print *, "" + + ! Test creating Rusanov flux calculator + print *, "4. Creating Rusanov flux calculator..." + if (allocated(instance)) deallocate(instance) + call create_component("flux", "rusanov", instance) + + if (allocated(instance)) then + select type (inst => instance) + type is (rusanov_flux) + allocate(flux_calc, source=inst) + print *, "Rusanov flux calculator created successfully" + call flux_calc%info() + print *, "" + + ! Test flux computation + n = 5 + allocate(qL(n), qR(n), flux(n)) + + ! Initialize test data + do i = 1, n + qL(i) = 1.0_wp + 0.1_wp * real(i-1, wp) + qR(i) = 1.0_wp + 0.1_wp * real(i, wp) + end do + + print *, "Testing Rusanov flux computation..." + call flux_calc%compute(qL, qR, flux, 1.0_wp) + + print *, "qL:" + do i = 1, n + write(*, '(I3, F10.6)') i, qL(i) + end do + + print *, "qR:" + do i = 1, n + write(*, '(I3, F10.6)') i, qR(i) + end do + + print *, "Flux:" + do i = 1, n + write(*, '(I3, F10.6)') i, flux(i) + end do + + deallocate(qL, qR, flux) + class default + print *, "[ERROR] Wrong type for Rusanov flux" + end select + else + print *, "[ERROR] Failed to create Rusanov flux calculator" + end if + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Factory pattern test completed successfully ===" + +end program test_factory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01g/tests/test_factory_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/01g/tests/test_factory_simple.f90 new file mode 100644 index 000000000..c0dcde275 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01g/tests/test_factory_simple.f90 @@ -0,0 +1,106 @@ +! tests/test_factory_simple.f90 +program test_factory_simple + use registry_module + use config_module + use mesh_module + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(eno_reconstructor) :: eno + type(weno3_reconstructor) :: weno3 + type(rusanov_flux) :: rusanov + integer :: i + + print *, "=== Factory Pattern Simple Test ===" + print *, "" + + ! Test 1: Configuration and mesh + print *, "1. Testing basic systems..." + print *, "-----------------------------" + call config_print(config) + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 2: Creating reconstructors directly + print *, "2. Creating reconstructors..." + print *, "------------------------------" + eno = eno_reconstructor(name="ENO", order=3, epsilon=1.0e-6_wp) + weno3 = weno3_reconstructor(name="WENO3", order=3, epsilon=1.0e-6_wp) + + call eno%info() + call weno3%info() + print *, "" + + ! Test 3: Creating flux calculator directly + print *, "3. Creating flux calculator..." + print *, "-------------------------------" + rusanov = rusanov_flux(name="Rusanov", wave_speed_default=1.0_wp) + call rusanov%info() + print *, "" + + ! Test 4: Registry integration + print *, "4. Testing registry..." + print *, "----------------------" + call initialize_registry(verbose=.true.) + + ! Register with simple method + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("flux", "rusanov") + + ! Check if registered + if (has_component("reconstructor", "eno")) then + print *, "✓ ENO reconstructor registered successfully" + else + print *, "✗ ENO reconstructor registration failed" + end if + + if (has_component("flux", "rusanov")) then + print *, "✓ Rusanov flux registered successfully" + else + print *, "✗ Rusanov flux registration failed" + end if + + print *, "" + call component_registry%list_all() + print *, "" + + ! Test getting available components + print *, "5. Testing component listing..." + print *, "--------------------------------" + call test_component_listing() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Factory pattern simple test completed successfully ===" + +contains + + subroutine test_component_listing() + character(len=:), allocatable :: recon_names(:) + integer, allocatable :: recon_orders(:) + integer :: i + + print *, "Available reconstructors:" + call get_available_components("reconstructor", recon_names, recon_orders) + + if (allocated(recon_names)) then + do i = 1, size(recon_names) + print *, " - ", trim(recon_names(i)) + end do + print *, "Total reconstructors: ", size(recon_names) + else + print *, " (no reconstructors found)" + end if + end subroutine test_component_listing + +end program test_factory_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01g/tests/test_minimal.f90 b/example/1d-linear-convection/weno3/fortran/registry/01g/tests/test_minimal.f90 new file mode 100644 index 000000000..bb4b7cda9 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01g/tests/test_minimal.f90 @@ -0,0 +1,108 @@ +! tests/test_minimal.f90 +program test_minimal + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i ! Declare loop variable here + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", component_registry%size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components + print *, "5. Testing available components" + print *, "--------------------------------" + + ! Use a separate block to avoid allocation issues + call test_available_components() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Minimal test completed successfully ===" + +contains + + ! Internal subroutine for testing available components + subroutine test_available_components() + character(len=:), allocatable :: names(:) + integer, allocatable :: orders(:) + integer :: j + + call get_available_components("reconstructor", names, orders) + + if (allocated(names)) then + print *, "Reconstructors:" + do j = 1, size(names) + print *, " - ", trim(names(j)) + if (allocated(orders)) then + print *, " Order: ", orders(j) + end if + end do + else + print *, "No reconstructors found" + end if + end subroutine test_available_components + +end program test_minimal \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01g/tests/test_minimal_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/01g/tests/test_minimal_simple.f90 new file mode 100644 index 000000000..bd6368cdc --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01g/tests/test_minimal_simple.f90 @@ -0,0 +1,5 @@ +! tests/test_minimal_simple.f90 +program test_minimal_simple + implicit none + +end program test_minimal_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01g/tests/test_minimal_simpleBAK.f90 b/example/1d-linear-convection/weno3/fortran/registry/01g/tests/test_minimal_simpleBAK.f90 new file mode 100644 index 000000000..689165de8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01g/tests/test_minimal_simpleBAK.f90 @@ -0,0 +1,84 @@ +! tests/test_minimal_simple.f90 +program test_minimal_simple + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Simple Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components (simple version) + print *, "5. Testing registry functionality" + print *, "---------------------------------" + print *, "Registry initialized: ", registry_is_initialized() + print *, "Number of components: ", registry_get_size() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Simple test completed successfully ===" + +end program test_minimal_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01h/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01h/CMakeLists.txt new file mode 100644 index 000000000..ef66d5848 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01h/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01h/README.md b/example/1d-linear-convection/weno3/fortran/registry/01h/README.md new file mode 100644 index 000000000..c4889757b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01h/README.md @@ -0,0 +1,221 @@ +# Fortran CFD Project + +基于工厂模式和注册系统的CFD求解器框架。 + +## 项目结构 + +``` +fortran_cfd/ +├── CMakeLists.txt # 根CMake配置 +├── scripts/ # 构建脚本 +│ ├── build.py # Python构建脚本(推荐) +│ ├── build.bat # Batch包装脚本 +│ ├── build.ps1 # PowerShell包装脚本 +│ └── requirements.txt # Python依赖 +├── src/ # 源代码 +│ ├── CMakeLists.txt +│ ├── core/ # 核心模块(注册系统、工厂接口) +│ ├── infrastructure/ # 基础设施(配置、网格) +│ └── numerics/ # 数值方法 +├── tests/ # 测试 +│ ├── CMakeLists.txt +│ ├── test_minimal_simple.f90 # 简单测试 +│ └── test_factory.f90 # 工厂模式测试 +└── README.md # 项目说明(本文件) +``` + +## 快速开始 + +### 使用Python脚本(推荐) + +```bash +# 进入脚本目录 +cd scripts + +# 基本构建 +python build.py + +# 清理并构建 +python build.py --clean + +# 发布构建 +python build.py --build-type Release --clean + +# 并行构建(使用所有核心) +python build.py -j + +# 不运行测试 +python build.py --no-tests + +# 查看帮助 +python build.py --help +``` + +### 使用批处理脚本 + +```bash +cd scripts +.\build.bat +``` + +### 使用PowerShell脚本 + +```powershell +cd scripts +.\build.ps1 +``` + +## 手动构建 + +```bash +# 设置Intel环境 +cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && powershell' + +# 配置项目 +mkdir build +cd build +cmake -G "Visual Studio 17 2022" -A x64 -T fortran=ifx .. + +# 构建项目 +cmake --build . --config Debug + +# 运行测试 +Debug\test_simple.exe +Debug\test_factory.exe +``` + +## 功能特性 + +✅ 组件注册系统 +✅ 工厂模式 +✅ ENO/WENO重构器 +✅ Rusanov通量计算器 +✅ 可扩展架构 +✅ 自动化测试 + +## 依赖 + +- Intel oneAPI(包含ifx编译器) +- CMake 3.12+ +- Python 3.6+(仅用于构建脚本) + +## 源代码文件 + +### 核心模块 +- `src/core/registry.f90` - 组件注册系统 +- `src/core/factory_interfaces.f90` - 工厂接口 + +### 基础设施 +- `src/infrastructure/config.f90` - 配置管理 +- `src/infrastructure/mesh.f90` - 网格生成 + +### 数值方法 +- `src/numerics/reconstructor/base.f90` - 重构器基类 +- `src/numerics/reconstructor/eno.f90` - ENO重构器 +- `src/numerics/reconstructor/weno3.f90` - WENO-3重构器 +- `src/numerics/flux/base.f90` - 通量计算器基类 +- `src/numerics/flux/rusanov.f90` - Rusanov通量 + +### 测试 +- `tests/test_minimal_simple.f90` - 简单功能测试 +- `tests/test_factory.f90` - 工厂模式测试 + +## 构建脚本 + +### Python脚本 (build.py) +主构建脚本,支持: +- 自动设置Intel oneAPI环境 +- 参数化构建选项 +- 并行编译 +- 自动化测试 +- 彩色输出 + +### Batch脚本 (build.bat) +Windows批处理包装器,调用Python脚本。 + +### PowerShell脚本 (build.ps1) +PowerShell包装器,调用Python脚本。 + +## 示例输出 + +成功构建后,会看到类似输出: + +``` +============================================================ + Fortran CFD Project Builder +============================================================ + +[1/4] Setting up Intel Fortran compiler... +✓ Intel oneAPI environment configured + +[2/4] Preparing build directory... +✓ Cleaned build directory + +[3/4] Configuring project... + $ cmake -G Visual Studio 17 2022 -A x64 -T fortran=ifx -DCMAKE_BUILD_TYPE=Debug .. +✓ CMake configuration successful + +[4/4] Building project... + $ cmake --build . --config Debug +✓ Build completed in 12.3 seconds + +============================================================ + Running Tests +============================================================ + +[TEST] Simple functionality test... +✓ Simple test passed + +[TEST] Factory pattern test... +✓ Factory test passed + +============================================================ + Build Summary +============================================================ +✓ Build directory: D:\path\to\build +✓ Build type: Debug +✓ Total time: 15.2s +``` + +## 开发指南 + +### 添加新组件 + +1. 在相应模块中创建Fortran源文件 +2. 实现工厂函数 +3. 使用`register_component_with_factory`注册组件 +4. 添加测试 + +### 扩展架构 + +项目采用模块化设计: +1. **注册系统** - 管理所有可用的组件 +2. **工厂模式** - 动态创建组件实例 +3. **抽象接口** - 确保组件兼容性 +4. **配置驱动** - 通过配置文件控制行为 + +## 故障排除 + +### 常见问题 + +1. **Intel环境未找到** + - 检查Intel oneAPI安装路径 + - 手动运行`setvars.bat` + +2. **CMake配置失败** + - 确保使用正确的生成器:`Visual Studio 17 2022` + - 检查Fortran编译器是否可用 + +3. **构建失败** + - 检查依赖项是否完整 + - 查看详细的错误信息 + +### 调试建议 + +- 使用`--verbose`参数获取详细输出 +- 检查`build/CMakeCache.txt`了解配置详情 +- 查看编译器错误日志 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01h/scripts/build.bat b/example/1d-linear-convection/weno3/fortran/registry/01h/scripts/build.bat new file mode 100644 index 000000000..d243695b0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01h/scripts/build.bat @@ -0,0 +1,26 @@ +@echo off +echo ======================================== +echo Fortran CFD Project Builder +echo (Wrapper for Python script) +echo ======================================== +echo. + +REM 检查Python +where python >nul 2>nul +if %errorlevel% neq 0 ( + echo [ERROR] Python not found. Please install Python 3. + pause + exit /b 1 +) + +REM 运行Python构建脚本 +echo [INFO] Running Python build script... +python build.py %* + +if %errorlevel% neq 0 ( + echo [ERROR] Build failed + pause + exit /b 1 +) + +pause \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01h/scripts/build.ps1 b/example/1d-linear-convection/weno3/fortran/registry/01h/scripts/build.ps1 new file mode 100644 index 000000000..4497c5ebd --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01h/scripts/build.ps1 @@ -0,0 +1,32 @@ +# Fortran CFD Project Builder (PowerShell wrapper) + +Write-Host "========================================" -ForegroundColor Cyan +Write-Host " Fortran CFD Project Builder" -ForegroundColor Cyan +Write-Host " (PowerShell wrapper for Python script)" -ForegroundColor Cyan +Write-Host "========================================" -ForegroundColor Cyan +Write-Host "" + +# 检查Python +$python = Get-Command python -ErrorAction SilentlyContinue +if (-not $python) { + $python = Get-Command python3 -ErrorAction SilentlyContinue +} + +if (-not $python) { + Write-Host "[ERROR] Python not found. Please install Python 3." -ForegroundColor Red + Read-Host "Press Enter to exit" + exit 1 +} + +# 运行Python构建脚本 +Write-Host "[INFO] Running Python build script..." -ForegroundColor Yellow +$argsString = $args -join ' ' +$command = "python build.py $argsString" + +Invoke-Expression $command + +if ($LASTEXITCODE -ne 0) { + Write-Host "[ERROR] Build failed" -ForegroundColor Red + Read-Host "Press Enter to exit" + exit 1 +} \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01h/scripts/build.py b/example/1d-linear-convection/weno3/fortran/registry/01h/scripts/build.py new file mode 100644 index 000000000..1c99c2c87 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01h/scripts/build.py @@ -0,0 +1,357 @@ +#!/usr/bin/env python3 +""" +Fortran CFD 项目构建脚本 (Python版) +支持 Intel oneAPI 环境的自动化构建 +""" + +import os +import sys +import subprocess +import shutil +from pathlib import Path +import argparse +import platform +import time + +class Colors: + """终端颜色""" + if platform.system() == "Windows": + # Windows 启用 ANSI + os.system("") + + HEADER = '\033[95m' + BLUE = '\033[94m' + CYAN = '\033[96m' + GREEN = '\033[92m' + YELLOW = '\033[93m' + RED = '\033[91m' + MAGENTA = '\033[95m' + DARK_GRAY = '\033[90m' # 添加这个 + END = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + +def print_color(text, color=Colors.END): + """彩色打印""" + print(f"{color}{text}{Colors.END}") + +def print_header(text): + """打印标题""" + print_color("\n" + "="*60, Colors.CYAN) + print_color(f" {text}", Colors.BOLD + Colors.CYAN) + print_color("="*60 + "\n", Colors.CYAN) + +def print_step(step, total, message): + """打印步骤""" + print_color(f"[{step}/{total}] {message}...", Colors.YELLOW) + +def print_success(message): + """打印成功""" + print_color(f"✓ {message}", Colors.GREEN) + +def print_error(message): + """打印错误""" + print_color(f"✗ {message}", Colors.RED) + +def print_warning(message): + """打印警告""" + print_color(f"! {message}", Colors.YELLOW) + +def run_command(cmd, cwd=None, check=True, capture=True): + """运行命令""" + print_color(f" $ {' '.join(cmd)}", Colors.BLUE) + + try: + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=capture, + text=True, + encoding='utf-8', + errors='ignore', + shell=False + ) + + if capture and result.stdout: + print(result.stdout) + if capture and result.stderr: + print_color(result.stderr, Colors.YELLOW) + + if check and result.returncode != 0: + print_error(f"Command failed with exit code: {result.returncode}") + return False + + return True + + except FileNotFoundError as e: + print_error(f"Command not found: {cmd[0]}") + if check: + raise + return False + except Exception as e: + print_error(f"Command execution failed: {e}") + return False + +def setup_intel_environment(): + """设置 Intel oneAPI 环境""" + setvars_paths = [ + r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat", + r"C:\Program Files\Intel\oneAPI\setvars.bat", + ] + + setvars_path = None + for path in setvars_paths: + if os.path.exists(path): + setvars_path = path + break + + if not setvars_path: + print_error("Intel oneAPI setvars.bat not found.") + print_warning("Please install Intel oneAPI or update the path in build.py") + return False + + # 创建临时的 batch 文件 + temp_bat = "temp_setvars.bat" + with open(temp_bat, 'w') as f: + f.write(f'@echo off\n') + f.write(f'call "{setvars_path}" > nul 2>&1\n') + f.write(f'set\n') + + try: + # 运行并捕获环境变量 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + shell=True, + encoding='utf-8', + errors='ignore' + ) + + # 解析并设置环境变量 + for line in result.stdout.split('\n'): + if '=' in line: + key, value = line.split('=', 1) + os.environ[key.strip()] = value.strip() + + os.remove(temp_bat) + print_success("Intel oneAPI environment configured") + return True + + except Exception as e: + print_error(f"Failed to setup Intel environment: {e}") + if os.path.exists(temp_bat): + os.remove(temp_bat) + return False + +def build_project(args): + """构建项目主函数""" + start_time = time.time() + + print_header(f"Fortran CFD Project Builder") + print_color(f"Build type: {args.build_type}", Colors.CYAN) + print_color(f"Run tests: {args.run_tests}", Colors.CYAN) + print() + + # 获取项目根目录(脚本所在目录的父目录) + script_dir = Path(__file__).parent + project_root = script_dir.parent + os.chdir(project_root) + + print_color(f"Project root: {project_root}", Colors.BLUE) + + # 步骤1: 设置 Intel 环境 + print_step(1, 4, "Setting up Intel Fortran compiler") + if not setup_intel_environment(): + return False + + # 步骤2: 准备构建目录 + print_step(2, 4, "Preparing build directory") + build_dir = project_root / "build" + + if args.clean and build_dir.exists(): + try: + shutil.rmtree(build_dir) + print_success("Cleaned build directory") + except Exception as e: + print_error(f"Failed to clean build directory: {e}") + if not args.force: + return False + + build_dir.mkdir(exist_ok=True) + os.chdir(build_dir) + + # 步骤3: 配置项目 + print_step(3, 4, "Configuring project") + + cmake_cmd = [ + "cmake", + "-G", "Visual Studio 17 2022", + "-A", "x64", + "-T", "fortran=ifx", + f"-DCMAKE_BUILD_TYPE={args.build_type}", + ".." + ] + + if not run_command(cmake_cmd, check=not args.force): + if not args.force: + return False + + # 步骤4: 构建项目 + print_step(4, 4, "Building project") + + build_cmd = [ + "cmake", + "--build", ".", + "--config", args.build_type, + f"-j{args.jobs}" if args.jobs > 1 else "" + ] + + print(f"build_cmd={build_cmd}") + build_cmd = [c for c in build_cmd if c] # 移除空字符串 + + if not run_command(build_cmd, check=not args.force): + if not args.force: + return False + + build_time = time.time() - start_time + print_success(f"Build completed in {build_time:.1f} seconds") + + # 运行测试 + if args.run_tests: + print_header("Running Tests") + run_tests(args.build_type) + + print_header("Build Summary") + print_color(f"Build directory: {build_dir}", Colors.GREEN) + print_color(f"Build type: {args.build_type}", Colors.GREEN) + print_color(f"Total time: {build_time:.1f}s", Colors.GREEN) + + return True + +def run_tests(build_type): + """运行测试""" + tests = [ + ("test_simple", "Simple functionality test"), + ("test_factory", "Factory pattern test"), + ] + + for test_name, description in tests: + # 修复:在这里直接访问当前循环的 test_name + _run_single_test(test_name, description, build_type) + +def _run_single_test(test_name, description, build_type): + """运行单个测试(修复作用域问题)""" + # 可能的测试可执行文件路径 + possible_paths = [ + Path(f"./{build_type}/{test_name}.exe"), + Path(f"./tests/{build_type}/{test_name}.exe"), + Path(f"./tests/{test_name}.exe"), + Path(f"{test_name}.exe"), + Path(f"./{test_name}.exe"), + ] + + test_exe = None + + # 尝试所有可能的路径 + for path in possible_paths: + if path.exists(): + test_exe = path + break + + if test_exe: + print_color(f"\n[TEST] {description}...", Colors.MAGENTA) + print_color("-" * 50, Colors.DARK_GRAY) + + try: + result = subprocess.run( + [str(test_exe)], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + + if result.stdout: + print(result.stdout) + + if result.returncode == 0: + print_success(f"{test_name} passed") + else: + print_error(f"{test_name} failed (exit code: {result.returncode})") + if result.stderr: + print_color(result.stderr, Colors.YELLOW) + + except Exception as e: + print_error(f"{test_name} failed: {e}") + else: + print_warning(f"{test_name}.exe not found. Searched paths:") + for path in possible_paths: + print_color(f" - {path}", Colors.YELLOW) + +def main(): + """主函数""" + parser = argparse.ArgumentParser(description="Fortran CFD Project Builder") + + parser.add_argument( + "--build-type", + choices=["Debug", "Release", "RelWithDebInfo", "MinSizeRel"], + default="Debug", + help="Build type (default: Debug)" + ) + + parser.add_argument( + "--clean", + action="store_true", + help="Clean build directory before building" + ) + + parser.add_argument( + "--no-tests", + action="store_true", + help="Skip running tests" + ) + + parser.add_argument( + "-j", "--jobs", + type=int, + default=0, + help="Number of parallel jobs (0 = use all cores)" + ) + + parser.add_argument( + "--force", + action="store_true", + help="Continue on errors" + ) + + parser.add_argument( + "--verbose", + action="store_true", + help="Verbose output" + ) + + args = parser.parse_args() + + if args.jobs == 0: + import multiprocessing + args.jobs = multiprocessing.cpu_count() + + args.run_tests = not args.no_tests + + try: + success = build_project(args) + if not success and not args.force: + sys.exit(1) + except KeyboardInterrupt: + print_color("\nBuild interrupted by user", Colors.YELLOW) + sys.exit(1) + except Exception as e: + print_error(f"Unexpected error: {e}") + if args.verbose: + import traceback + traceback.print_exc() + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01h/scripts/requirements.txt b/example/1d-linear-convection/weno3/fortran/registry/01h/scripts/requirements.txt new file mode 100644 index 000000000..d21a12b45 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01h/scripts/requirements.txt @@ -0,0 +1,2 @@ +# 构建脚本的Python依赖(可选) +# 目前没有特殊依赖,保持空文件或添加未来可能需要的包 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01h/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01h/src/CMakeLists.txt new file mode 100644 index 000000000..ee38952bb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01h/src/CMakeLists.txt @@ -0,0 +1,14 @@ +# ==================== src\CMakeLists.txt ==================== + +# src/CMakeLists.txt +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(core) +add_subdirectory(infrastructure) +add_subdirectory(numerics) + +message(STATUS "源代码目录配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01h/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01h/src/core/CMakeLists.txt new file mode 100644 index 000000000..376dd4fe5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01h/src/core/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/core/CMakeLists.txt +message(STATUS "配置核心模块...") + +add_library(core + registry.f90 + factory_interfaces.f90 +) + +target_include_directories(core PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 安装规则 +install(TARGETS core + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) +install(FILES + ${CMAKE_Fortran_MODULE_DIRECTORY}/registry_module.mod + ${CMAKE_Fortran_MODULE_DIRECTORY}/factory_interfaces.mod + DESTINATION include/fortran_cfd/core +) + +message(STATUS "核心模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01h/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/01h/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..19cc2c59e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01h/src/core/factory_interfaces.f90 @@ -0,0 +1,17 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, factory_procedure + + ! Factory procedure interface + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01h/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/01h/src/core/registry.f90 new file mode 100644 index 000000000..f996c89b3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01h/src/core/registry.f90 @@ -0,0 +1,318 @@ +! src/core/registry.f90 +module registry_module + use, intrinsic :: iso_fortran_env, only: real64 + use factory_interfaces, only: factory_procedure + implicit none + + private + + ! Public interface + public :: real64, component_info, component_registry + public :: register_component_simple, initialize_registry, cleanup_registry + public :: has_component, get_available_components + public :: registry_is_initialized, registry_get_size ! 添加公共访问方法 + + ! Type definitions (simplified, no factory for now) + type :: component_info + character(len=32) :: category = "" + character(len=32) :: name = "" + integer :: order = 0 + contains + procedure :: print => ci_print + end type component_info + + type :: component_registry_type + private + type(component_info), allocatable :: components(:) + integer :: count = 0 + integer :: capacity = 100 + logical :: verbose = .true. + logical :: initialized = .false. + contains + procedure :: register => cr_register + procedure :: get => cr_get + procedure :: list_all => cr_list_all + procedure :: clear => cr_clear + procedure :: size => cr_size + procedure :: is_initialized => cr_is_initialized ! 内部方法 + end type component_registry_type + + ! Global registry instance + type(component_registry_type), save :: component_registry + +contains + + ! ==================== PUBLIC API ==================== + + ! Initialize registry + subroutine initialize_registry(initial_capacity, verbose) + integer, optional, intent(in) :: initial_capacity + logical, optional, intent(in) :: verbose + + if (component_registry%initialized) then + if (component_registry%verbose) then + print *, "[INFO] Registry already initialized" + end if + return + end if + + if (present(initial_capacity)) then + component_registry%capacity = max(10, initial_capacity) + end if + + if (present(verbose)) then + component_registry%verbose = verbose + end if + + ! Allocate array + allocate(component_registry%components(component_registry%capacity)) + + component_registry%initialized = .true. + component_registry%count = 0 + + if (component_registry%verbose) then + print *, "[INIT] Registry initialized, capacity:", component_registry%capacity + end if + end subroutine initialize_registry + + ! Cleanup registry + subroutine cleanup_registry + call component_registry%clear() + if (component_registry%verbose) then + print *, "[CLEANUP] Registry cleaned up" + end if + end subroutine cleanup_registry + + ! Simple registration (no factory) + subroutine register_component_simple(category, name) + character(len=*), intent(in) :: category, name + + type(component_info) :: info + + info%category = to_lower(trim(adjustl(category))) + info%name = to_lower(trim(adjustl(name))) + info%order = 0 + + call component_registry%register(info) + end subroutine register_component_simple + + ! Check if component exists + function has_component(category, name) result(found) + character(len=*), intent(in) :: category, name + logical :: found + + type(component_info) :: info + character(len=32) :: cat_lower, name_lower + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + info = component_registry%get(cat_lower, name_lower) + found = (len_trim(info%category) > 0) + end function has_component + + ! Get available components in a category + subroutine get_available_components(category, names, orders) + character(len=*), intent(in) :: category + character(len=:), allocatable, intent(out), optional :: names(:) + integer, allocatable, intent(out), optional :: orders(:) + + character(len=32) :: cat_lower + integer :: i, count, idx + type(component_info) :: info + + cat_lower = to_lower(trim(adjustl(category))) + + ! Count components in this category + count = 0 + do i = 1, component_registry%count + if (component_registry%components(i)%category == cat_lower) then + count = count + 1 + end if + end do + + ! Allocate arrays if requested + if (present(names)) then + allocate(character(len=32) :: names(count)) + end if + + if (present(orders)) then + allocate(orders(count)) + end if + + ! Fill arrays + idx = 1 + do i = 1, component_registry%count + if (component_registry%components(i)%category == cat_lower) then + info = component_registry%components(i) + if (present(names)) then + names(idx) = info%name + end if + if (present(orders)) then + orders(idx) = info%order + end if + idx = idx + 1 + end if + end do + end subroutine get_available_components + + ! Public function to check if registry is initialized + function registry_is_initialized() result(is_initialized) + logical :: is_initialized + is_initialized = component_registry%is_initialized() + end function registry_is_initialized + + ! Public function to get registry size + function registry_get_size() result(size_val) + integer :: size_val + size_val = component_registry%size() + end function registry_get_size + + ! ==================== COMPONENT INFO METHODS ==================== + + subroutine ci_print(this) + class(component_info), intent(in) :: this + + if (this%order > 0) then + print *, " [", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ")]" + else + print *, " [", trim(this%category), ".", trim(this%name), "]" + end if + end subroutine ci_print + + ! ==================== REGISTRY INTERNAL METHODS ==================== + + subroutine cr_register(this, info) + class(component_registry_type), intent(inout) :: this + type(component_info), intent(in) :: info + + type(component_info), allocatable :: temp(:) + integer :: i + + if (.not. this%initialized) then + error stop "[ERROR] Registry not initialized, call initialize_registry first" + end if + + ! Check if already exists + do i = 1, this%count + if (this%components(i)%category == info%category .and. & + this%components(i)%name == info%name) then + if (this%verbose) then + print *, "[WARN] Overwriting: ", & + trim(info%category), ".", trim(info%name) + end if + this%components(i) = info + return + end if + end do + + ! Expand array if needed + if (this%count >= this%capacity) then + this%capacity = this%capacity * 2 + allocate(temp(this%capacity)) + temp(1:this%count) = this%components(1:this%count) + call move_alloc(temp, this%components) + + if (this%verbose) then + print *, "[INFO] Registry expanded to capacity:", this%capacity + end if + end if + + ! Add component + this%count = this%count + 1 + this%components(this%count) = info + + if (this%verbose) then + print *, "[OK] Registered: ", trim(info%category), ".", trim(info%name) + end if + end subroutine cr_register + + function cr_get(this, category, name) result(info) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category, name + type(component_info) :: info + + integer :: i + + ! Initialize return value as empty + info%category = "" + info%name = "" + info%order = 0 + + if (.not. this%initialized) then + return + end if + + do i = 1, this%count + if (this%components(i)%category == category .and. & + this%components(i)%name == name) then + info = this%components(i) + return + end if + end do + end function cr_get + + subroutine cr_list_all(this) + class(component_registry_type), intent(in) :: this + integer :: i + + if (.not. this%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + print *, "=== Registry Contents (", this%count, " components) ===" + + if (this%count == 0) then + print *, " (empty)" + return + end if + + ! Show components grouped by category + do i = 1, this%count + call this%components(i)%print() + end do + + print *, "===========================================" + end subroutine cr_list_all + + subroutine cr_clear(this) + class(component_registry_type), intent(inout) :: this + + if (allocated(this%components)) then + deallocate(this%components) + end if + + this%count = 0 + this%capacity = 100 + this%initialized = .false. + end subroutine cr_clear + + integer function cr_size(this) + class(component_registry_type), intent(in) :: this + cr_size = this%count + end function cr_size + + logical function cr_is_initialized(this) + class(component_registry_type), intent(in) :: this + cr_is_initialized = this%initialized + end function cr_is_initialized + + ! ==================== UTILITY FUNCTIONS ==================== + + function to_lower(str) result(lower_str) + character(len=*), intent(in) :: str + character(len=len(str)) :: lower_str + integer :: i + + do i = 1, len(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + lower_str(i:i) = char(ichar(str(i:i)) + 32) + else + lower_str(i:i) = str(i:i) + end if + end do + end function to_lower + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01h/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01h/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..2b72c5484 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01h/src/infrastructure/CMakeLists.txt @@ -0,0 +1,20 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "配置基础设施模块...") + +add_library(infrastructure + config.f90 + mesh.f90 +) + +target_link_libraries(infrastructure PUBLIC core) + +target_include_directories(infrastructure PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS infrastructure + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "基础设施模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01h/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/01h/src/infrastructure/config.f90 new file mode 100644 index 000000000..34019f2f5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01h/src/infrastructure/config.f90 @@ -0,0 +1,90 @@ +! src/infrastructure/config.f90 +module config_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, cfd_config, config_print, config_with_reconstruction + + ! CFD configuration type + type :: cfd_config + character(len=20) :: ic_type = "step" + character(len=20) :: recon_scheme = "eno" + character(len=20) :: flux_type = "rusanov" + integer :: rk_order = 1 + real(real64) :: wave_speed = 1.0_real64 + real(real64) :: final_time = 0.625_real64 + real(real64) :: dt = 0.025_real64 + character(len=20) :: boundary_type = "periodic" + real(real64) :: left_boundary_value = 1.0_real64 + real(real64) :: right_boundary_value = 2.0_real64 + integer :: spatial_order = 2 + logical :: verbose = .true. + end type cfd_config + +contains + + subroutine config_print(this) + type(cfd_config), intent(in) :: this + + print *, "=== CFD Configuration ===" + print *, "Initial condition: ", trim(this%ic_type) + print *, "Reconstruction: ", trim(this%recon_scheme), " (order:", this%spatial_order, ")" + print *, "Flux type: ", trim(this%flux_type) + print *, "Time integration: RK", this%rk_order + print *, "Wave speed: ", this%wave_speed + print *, "Final time: ", this%final_time + print *, "Time step: ", this%dt + print *, "Boundary: ", trim(this%boundary_type) + if (trim(this%boundary_type) == 'dirichlet') then + print *, " Dirichlet values: [", this%left_boundary_value, ", ", & + this%right_boundary_value, "]" + end if + print *, "===============================" + end subroutine config_print + + subroutine config_with_reconstruction(this, scheme, order) + type(cfd_config), intent(inout) :: this + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + character(len=20) :: scheme_lower + + ! Convert to lowercase + scheme_lower = scheme + call to_lower_inplace(scheme_lower) + this%recon_scheme = trim(adjustl(scheme_lower)) + + ! Set order + if (present(order)) then + this%spatial_order = order + else + ! Smart defaults + if (index(this%recon_scheme, 'weno') > 0) then + this%spatial_order = 5 + else if (trim(this%recon_scheme) == 'eno') then + this%spatial_order = 3 + else + print *, "[ERROR] Unsupported reconstruction scheme: ", trim(this%recon_scheme) + return + end if + end if + + if (this%verbose) then + print *, "[CONFIG] Reconstruction: ", trim(this%recon_scheme), & + " Order: ", this%spatial_order + end if + end subroutine config_with_reconstruction + + subroutine to_lower_inplace(str) + character(len=*), intent(inout) :: str + integer :: i + + do i = 1, len_trim(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + str(i:i) = char(ichar(str(i:i)) + 32) + end if + end do + end subroutine to_lower_inplace + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01h/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/01h/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..65a45dcdf --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01h/src/infrastructure/mesh.f90 @@ -0,0 +1,74 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, mesh_type, mesh_init, mesh_print_info + + ! 网格类型 + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer :: ncells = 40 + integer :: nnodes + integer :: nx + real(wp), allocatable :: x(:) ! 节点坐标 + real(wp), allocatable :: xcc(:) ! 单元中心坐标 + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer, optional, intent(in) :: ncells + + integer :: i + + ! 设置参数 + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! 计算派生参数 + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! 分配数组 + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! 生成节点坐标 + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! 生成单元中心坐标 + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== 网格信息 ===" + print *, "计算域: [", this%xmin, ", ", this%xmax, "]" + print *, "单元数: ", this%ncells + print *, "节点数: ", this%nnodes + print *, "网格尺寸 dx: ", this%dx + print *, "域长度 L: ", this%L + print *, "==========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..66874a7ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/CMakeLists.txt @@ -0,0 +1,7 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..b2617f290 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,20 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +# Start with just the base module +add_library(flux + base.f90 +) + +target_link_libraries(flux PUBLIC core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/flux/base.f90 new file mode 100644 index 000000000..d9bd640c2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/flux/base.f90 @@ -0,0 +1,24 @@ +! src/numerics/flux/base.f90 +module flux_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, flux_calculator_base + + ! Base flux calculator type + type :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure :: info => flux_info + end type flux_calculator_base + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "Flux calculator information:" + print *, " Name: ", trim(this%name) + end subroutine flux_info + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/flux/rusanov.f90 b/example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/flux/rusanov.f90 new file mode 100644 index 000000000..4c1054ab3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/flux/rusanov.f90 @@ -0,0 +1,48 @@ +! src/numerics/flux/rusanov.f90 +module rusanov_flux_module + use, intrinsic :: iso_fortran_env, only: real64 + use flux_base_module, only: flux_calculator_base + implicit none + + private + public :: real64, rusanov_flux + + type, extends(flux_calculator_base) :: rusanov_flux + real(real64) :: wave_speed_default = 1.0_real64 + contains + procedure :: info => rusanov_info + end type rusanov_flux + + ! 添加构造函数接口 + interface rusanov_flux + module procedure create_rusanov_flux + end interface + +contains + + ! 构造函数 + type(rusanov_flux) function create_rusanov_flux(name, wave_speed_default) result(this) + character(len=*), optional, intent(in) :: name + real(real64), optional, intent(in) :: wave_speed_default + + if (present(name)) then + this%name = name + else + this%name = "Rusanov" + end if + + if (present(wave_speed_default)) then + this%wave_speed_default = wave_speed_default + else + this%wave_speed_default = 1.0_real64 + end if + end function create_rusanov_flux + + subroutine rusanov_info(this) + class(rusanov_flux), intent(in) :: this + call flux_info(this) + print *, " Type: Rusanov flux" + print *, " Default wave speed: ", this%wave_speed_default + end subroutine rusanov_info + +end module rusanov_flux_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..77808c1ad --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,20 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "Configuring reconstructor module...") + +# Start with just the base module +add_library(reconstructor + base.f90 +) + +target_link_libraries(reconstructor PUBLIC core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Reconstructor module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..404535d1e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/reconstructor/base.f90 @@ -0,0 +1,40 @@ +! src/numerics/reconstructor/base.f90 +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, reconstructor_base + + ! Base reconstructor type + type :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + contains + procedure :: info => reconstructor_info + procedure :: reconstruct => reconstruct_default ! 添加这个方法 + end type reconstructor_base + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + print *, "Reconstructor information:" + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_info + + ! 默认的重构方法 + subroutine reconstruct_default(this, q, qL, qR) + class(reconstructor_base), intent(in) :: this + real(real64), intent(in) :: q(0:) ! 包含ghost cells + real(real64), intent(out) :: qL(:), qR(:) + integer :: i, n + + n = size(qL) + do i = 1, n + qL(i) = q(i) ! 简单的一阶重构 + qR(i) = q(i+1) + end do + end subroutine reconstruct_default +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/reconstructor/eno.f90 b/example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/reconstructor/eno.f90 new file mode 100644 index 000000000..8bc206ac7 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/reconstructor/eno.f90 @@ -0,0 +1,56 @@ +! src/numerics/reconstructor/eno.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, eno_reconstructor + + type, extends(reconstructor_base) :: eno_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => eno_info + end type eno_reconstructor + + ! 添加构造函数接口 + interface eno_reconstructor + module procedure create_eno_reconstructor + end interface + +contains + + ! 构造函数 + type(eno_reconstructor) function create_eno_reconstructor(name, order, epsilon) result(this) + character(len=*), optional, intent(in) :: name + integer, optional, intent(in) :: order + real(real64), optional, intent(in) :: epsilon + + ! 设置默认值 + if (present(name)) then + this%name = name + else + this%name = "ENO" + end if + + if (present(order)) then + this%order = order + else + this%order = 3 + end if + + if (present(epsilon)) then + this%epsilon = epsilon + else + this%epsilon = 1.0e-6_real64 + end if + end function create_eno_reconstructor + + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + call reconstructor_info(this) + print *, " Type: ENO reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/reconstructor/weno3.f90 b/example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/reconstructor/weno3.f90 new file mode 100644 index 000000000..ffd500163 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01h/src/numerics/reconstructor/weno3.f90 @@ -0,0 +1,55 @@ +! src/numerics/reconstructor/weno3.f90 +module weno3_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno3_reconstructor + + type, extends(reconstructor_base) :: weno3_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno3_info + end type weno3_reconstructor + + ! 添加构造函数接口 + interface weno3_reconstructor + module procedure create_weno3_reconstructor + end interface + +contains + + ! 构造函数 + type(weno3_reconstructor) function create_weno3_reconstructor(name, order, epsilon) result(this) + character(len=*), optional, intent(in) :: name + integer, optional, intent(in) :: order + real(real64), optional, intent(in) :: epsilon + + if (present(name)) then + this%name = name + else + this%name = "WENO3" + end if + + if (present(order)) then + this%order = order + else + this%order = 3 + end if + + if (present(epsilon)) then + this%epsilon = epsilon + else + this%epsilon = 1.0e-6_real64 + end if + end function create_weno3_reconstructor + + subroutine weno3_info(this) + class(weno3_reconstructor), intent(in) :: this + call reconstructor_info(this) + print *, " Type: WENO-3 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno3_info + +end module weno3_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01h/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01h/tests/CMakeLists.txt new file mode 100644 index 000000000..2df858db5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01h/tests/CMakeLists.txt @@ -0,0 +1,17 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +add_executable(test_minimal_simple test_minimal_simple.f90) + +message(STATUS "CMAKE_Fortran_MODULE_DIRECTORY=${CMAKE_Fortran_MODULE_DIRECTORY}") + +#target_include_directories( test_minimal_simple +# PRIVATE +# ${CMAKE_Fortran_MODULE_DIRECTORY} +#) + +target_link_libraries( test_minimal_simple + PRIVATE + infrastructure +) + diff --git a/example/1d-linear-convection/weno3/fortran/registry/01h/tests/test_factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/01h/tests/test_factory.f90 new file mode 100644 index 000000000..8968b9c28 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01h/tests/test_factory.f90 @@ -0,0 +1,154 @@ +! tests/test_factory.f90 +program test_factory + use registry_module + use config_module + use mesh_module + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + class(*), allocatable :: instance + class(reconstructor_base), allocatable :: recon + class(flux_calculator_base), allocatable :: flux_calc + real(wp), allocatable :: q(:), qL(:), qR(:), flux(:) + integer :: i, n + + print *, "=== Factory Pattern Test ===" + print *, "" + + ! Initialize systems + call initialize_registry(verbose=.true.) + + ! Register components with factories + print *, "1. Registering components with factories..." + call register_component_with_factory("reconstructor", "eno", create_eno, 3) + call register_component_with_factory("reconstructor", "weno3", create_weno3, 3) + call register_component_with_factory("flux", "rusanov", create_rusanov) + + call component_registry%list_all() + print *, "" + + ! Test creating ENO reconstructor + print *, "2. Creating ENO reconstructor..." + call create_component("reconstructor", "eno", instance) + + if (allocated(instance)) then + select type (inst => instance) + type is (eno_reconstructor) + allocate(recon, source=inst) + print *, "ENO reconstructor created successfully" + call recon%info() + print *, "" + + ! Test reconstruction + n = 10 + allocate(q(0:n+1), qL(n), qR(n)) + + ! Initialize test data (sine wave) + do i = 0, n+1 + q(i) = sin(2.0_wp * 3.141592653589793_wp * real(i-1, wp) / real(n, wp)) + end do + + print *, "Testing ENO reconstruction..." + call recon%reconstruct(q, qL, qR) + + print *, "q (internal):" + do i = 1, n + write(*, '(I3, F10.6)') i, q(i) + end do + + print *, "qL (left interface values):" + do i = 1, n + write(*, '(I3, F10.6)') i, qL(i) + end do + + deallocate(q, qL, qR) + class default + print *, "[ERROR] Wrong type for ENO reconstructor" + end select + else + print *, "[ERROR] Failed to create ENO reconstructor" + end if + print *, "" + + ! Test creating WENO3 reconstructor + print *, "3. Creating WENO3 reconstructor..." + if (allocated(instance)) deallocate(instance) + call create_component("reconstructor", "weno3", instance) + + if (allocated(instance)) then + select type (inst => instance) + type is (weno3_reconstructor) + if (allocated(recon)) deallocate(recon) + allocate(recon, source=inst) + print *, "WENO3 reconstructor created successfully" + call recon%info() + class default + print *, "[ERROR] Wrong type for WENO3 reconstructor" + end select + else + print *, "[ERROR] Failed to create WENO3 reconstructor" + end if + print *, "" + + ! Test creating Rusanov flux calculator + print *, "4. Creating Rusanov flux calculator..." + if (allocated(instance)) deallocate(instance) + call create_component("flux", "rusanov", instance) + + if (allocated(instance)) then + select type (inst => instance) + type is (rusanov_flux) + allocate(flux_calc, source=inst) + print *, "Rusanov flux calculator created successfully" + call flux_calc%info() + print *, "" + + ! Test flux computation + n = 5 + allocate(qL(n), qR(n), flux(n)) + + ! Initialize test data + do i = 1, n + qL(i) = 1.0_wp + 0.1_wp * real(i-1, wp) + qR(i) = 1.0_wp + 0.1_wp * real(i, wp) + end do + + print *, "Testing Rusanov flux computation..." + call flux_calc%compute(qL, qR, flux, 1.0_wp) + + print *, "qL:" + do i = 1, n + write(*, '(I3, F10.6)') i, qL(i) + end do + + print *, "qR:" + do i = 1, n + write(*, '(I3, F10.6)') i, qR(i) + end do + + print *, "Flux:" + do i = 1, n + write(*, '(I3, F10.6)') i, flux(i) + end do + + deallocate(qL, qR, flux) + class default + print *, "[ERROR] Wrong type for Rusanov flux" + end select + else + print *, "[ERROR] Failed to create Rusanov flux calculator" + end if + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Factory pattern test completed successfully ===" + +end program test_factory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01h/tests/test_factory_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/01h/tests/test_factory_simple.f90 new file mode 100644 index 000000000..c0dcde275 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01h/tests/test_factory_simple.f90 @@ -0,0 +1,106 @@ +! tests/test_factory_simple.f90 +program test_factory_simple + use registry_module + use config_module + use mesh_module + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(eno_reconstructor) :: eno + type(weno3_reconstructor) :: weno3 + type(rusanov_flux) :: rusanov + integer :: i + + print *, "=== Factory Pattern Simple Test ===" + print *, "" + + ! Test 1: Configuration and mesh + print *, "1. Testing basic systems..." + print *, "-----------------------------" + call config_print(config) + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 2: Creating reconstructors directly + print *, "2. Creating reconstructors..." + print *, "------------------------------" + eno = eno_reconstructor(name="ENO", order=3, epsilon=1.0e-6_wp) + weno3 = weno3_reconstructor(name="WENO3", order=3, epsilon=1.0e-6_wp) + + call eno%info() + call weno3%info() + print *, "" + + ! Test 3: Creating flux calculator directly + print *, "3. Creating flux calculator..." + print *, "-------------------------------" + rusanov = rusanov_flux(name="Rusanov", wave_speed_default=1.0_wp) + call rusanov%info() + print *, "" + + ! Test 4: Registry integration + print *, "4. Testing registry..." + print *, "----------------------" + call initialize_registry(verbose=.true.) + + ! Register with simple method + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("flux", "rusanov") + + ! Check if registered + if (has_component("reconstructor", "eno")) then + print *, "✓ ENO reconstructor registered successfully" + else + print *, "✗ ENO reconstructor registration failed" + end if + + if (has_component("flux", "rusanov")) then + print *, "✓ Rusanov flux registered successfully" + else + print *, "✗ Rusanov flux registration failed" + end if + + print *, "" + call component_registry%list_all() + print *, "" + + ! Test getting available components + print *, "5. Testing component listing..." + print *, "--------------------------------" + call test_component_listing() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Factory pattern simple test completed successfully ===" + +contains + + subroutine test_component_listing() + character(len=:), allocatable :: recon_names(:) + integer, allocatable :: recon_orders(:) + integer :: i + + print *, "Available reconstructors:" + call get_available_components("reconstructor", recon_names, recon_orders) + + if (allocated(recon_names)) then + do i = 1, size(recon_names) + print *, " - ", trim(recon_names(i)) + end do + print *, "Total reconstructors: ", size(recon_names) + else + print *, " (no reconstructors found)" + end if + end subroutine test_component_listing + +end program test_factory_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01h/tests/test_minimal.f90 b/example/1d-linear-convection/weno3/fortran/registry/01h/tests/test_minimal.f90 new file mode 100644 index 000000000..bb4b7cda9 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01h/tests/test_minimal.f90 @@ -0,0 +1,108 @@ +! tests/test_minimal.f90 +program test_minimal + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i ! Declare loop variable here + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", component_registry%size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components + print *, "5. Testing available components" + print *, "--------------------------------" + + ! Use a separate block to avoid allocation issues + call test_available_components() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Minimal test completed successfully ===" + +contains + + ! Internal subroutine for testing available components + subroutine test_available_components() + character(len=:), allocatable :: names(:) + integer, allocatable :: orders(:) + integer :: j + + call get_available_components("reconstructor", names, orders) + + if (allocated(names)) then + print *, "Reconstructors:" + do j = 1, size(names) + print *, " - ", trim(names(j)) + if (allocated(orders)) then + print *, " Order: ", orders(j) + end if + end do + else + print *, "No reconstructors found" + end if + end subroutine test_available_components + +end program test_minimal \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01h/tests/test_minimal_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/01h/tests/test_minimal_simple.f90 new file mode 100644 index 000000000..689165de8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01h/tests/test_minimal_simple.f90 @@ -0,0 +1,84 @@ +! tests/test_minimal_simple.f90 +program test_minimal_simple + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Simple Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components (simple version) + print *, "5. Testing registry functionality" + print *, "---------------------------------" + print *, "Registry initialized: ", registry_is_initialized() + print *, "Number of components: ", registry_get_size() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Simple test completed successfully ===" + +end program test_minimal_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01h/tests/test_minimal_simpleBAK.f90 b/example/1d-linear-convection/weno3/fortran/registry/01h/tests/test_minimal_simpleBAK.f90 new file mode 100644 index 000000000..689165de8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01h/tests/test_minimal_simpleBAK.f90 @@ -0,0 +1,84 @@ +! tests/test_minimal_simple.f90 +program test_minimal_simple + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Simple Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components (simple version) + print *, "5. Testing registry functionality" + print *, "---------------------------------" + print *, "Registry initialized: ", registry_is_initialized() + print *, "Number of components: ", registry_get_size() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Simple test completed successfully ===" + +end program test_minimal_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01i/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01i/CMakeLists.txt new file mode 100644 index 000000000..ef66d5848 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01i/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01i/README.md b/example/1d-linear-convection/weno3/fortran/registry/01i/README.md new file mode 100644 index 000000000..c4889757b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01i/README.md @@ -0,0 +1,221 @@ +# Fortran CFD Project + +基于工厂模式和注册系统的CFD求解器框架。 + +## 项目结构 + +``` +fortran_cfd/ +├── CMakeLists.txt # 根CMake配置 +├── scripts/ # 构建脚本 +│ ├── build.py # Python构建脚本(推荐) +│ ├── build.bat # Batch包装脚本 +│ ├── build.ps1 # PowerShell包装脚本 +│ └── requirements.txt # Python依赖 +├── src/ # 源代码 +│ ├── CMakeLists.txt +│ ├── core/ # 核心模块(注册系统、工厂接口) +│ ├── infrastructure/ # 基础设施(配置、网格) +│ └── numerics/ # 数值方法 +├── tests/ # 测试 +│ ├── CMakeLists.txt +│ ├── test_minimal_simple.f90 # 简单测试 +│ └── test_factory.f90 # 工厂模式测试 +└── README.md # 项目说明(本文件) +``` + +## 快速开始 + +### 使用Python脚本(推荐) + +```bash +# 进入脚本目录 +cd scripts + +# 基本构建 +python build.py + +# 清理并构建 +python build.py --clean + +# 发布构建 +python build.py --build-type Release --clean + +# 并行构建(使用所有核心) +python build.py -j + +# 不运行测试 +python build.py --no-tests + +# 查看帮助 +python build.py --help +``` + +### 使用批处理脚本 + +```bash +cd scripts +.\build.bat +``` + +### 使用PowerShell脚本 + +```powershell +cd scripts +.\build.ps1 +``` + +## 手动构建 + +```bash +# 设置Intel环境 +cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && powershell' + +# 配置项目 +mkdir build +cd build +cmake -G "Visual Studio 17 2022" -A x64 -T fortran=ifx .. + +# 构建项目 +cmake --build . --config Debug + +# 运行测试 +Debug\test_simple.exe +Debug\test_factory.exe +``` + +## 功能特性 + +✅ 组件注册系统 +✅ 工厂模式 +✅ ENO/WENO重构器 +✅ Rusanov通量计算器 +✅ 可扩展架构 +✅ 自动化测试 + +## 依赖 + +- Intel oneAPI(包含ifx编译器) +- CMake 3.12+ +- Python 3.6+(仅用于构建脚本) + +## 源代码文件 + +### 核心模块 +- `src/core/registry.f90` - 组件注册系统 +- `src/core/factory_interfaces.f90` - 工厂接口 + +### 基础设施 +- `src/infrastructure/config.f90` - 配置管理 +- `src/infrastructure/mesh.f90` - 网格生成 + +### 数值方法 +- `src/numerics/reconstructor/base.f90` - 重构器基类 +- `src/numerics/reconstructor/eno.f90` - ENO重构器 +- `src/numerics/reconstructor/weno3.f90` - WENO-3重构器 +- `src/numerics/flux/base.f90` - 通量计算器基类 +- `src/numerics/flux/rusanov.f90` - Rusanov通量 + +### 测试 +- `tests/test_minimal_simple.f90` - 简单功能测试 +- `tests/test_factory.f90` - 工厂模式测试 + +## 构建脚本 + +### Python脚本 (build.py) +主构建脚本,支持: +- 自动设置Intel oneAPI环境 +- 参数化构建选项 +- 并行编译 +- 自动化测试 +- 彩色输出 + +### Batch脚本 (build.bat) +Windows批处理包装器,调用Python脚本。 + +### PowerShell脚本 (build.ps1) +PowerShell包装器,调用Python脚本。 + +## 示例输出 + +成功构建后,会看到类似输出: + +``` +============================================================ + Fortran CFD Project Builder +============================================================ + +[1/4] Setting up Intel Fortran compiler... +✓ Intel oneAPI environment configured + +[2/4] Preparing build directory... +✓ Cleaned build directory + +[3/4] Configuring project... + $ cmake -G Visual Studio 17 2022 -A x64 -T fortran=ifx -DCMAKE_BUILD_TYPE=Debug .. +✓ CMake configuration successful + +[4/4] Building project... + $ cmake --build . --config Debug +✓ Build completed in 12.3 seconds + +============================================================ + Running Tests +============================================================ + +[TEST] Simple functionality test... +✓ Simple test passed + +[TEST] Factory pattern test... +✓ Factory test passed + +============================================================ + Build Summary +============================================================ +✓ Build directory: D:\path\to\build +✓ Build type: Debug +✓ Total time: 15.2s +``` + +## 开发指南 + +### 添加新组件 + +1. 在相应模块中创建Fortran源文件 +2. 实现工厂函数 +3. 使用`register_component_with_factory`注册组件 +4. 添加测试 + +### 扩展架构 + +项目采用模块化设计: +1. **注册系统** - 管理所有可用的组件 +2. **工厂模式** - 动态创建组件实例 +3. **抽象接口** - 确保组件兼容性 +4. **配置驱动** - 通过配置文件控制行为 + +## 故障排除 + +### 常见问题 + +1. **Intel环境未找到** + - 检查Intel oneAPI安装路径 + - 手动运行`setvars.bat` + +2. **CMake配置失败** + - 确保使用正确的生成器:`Visual Studio 17 2022` + - 检查Fortran编译器是否可用 + +3. **构建失败** + - 检查依赖项是否完整 + - 查看详细的错误信息 + +### 调试建议 + +- 使用`--verbose`参数获取详细输出 +- 检查`build/CMakeCache.txt`了解配置详情 +- 查看编译器错误日志 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01i/scripts/build.bat b/example/1d-linear-convection/weno3/fortran/registry/01i/scripts/build.bat new file mode 100644 index 000000000..d243695b0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01i/scripts/build.bat @@ -0,0 +1,26 @@ +@echo off +echo ======================================== +echo Fortran CFD Project Builder +echo (Wrapper for Python script) +echo ======================================== +echo. + +REM 检查Python +where python >nul 2>nul +if %errorlevel% neq 0 ( + echo [ERROR] Python not found. Please install Python 3. + pause + exit /b 1 +) + +REM 运行Python构建脚本 +echo [INFO] Running Python build script... +python build.py %* + +if %errorlevel% neq 0 ( + echo [ERROR] Build failed + pause + exit /b 1 +) + +pause \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01i/scripts/build.ps1 b/example/1d-linear-convection/weno3/fortran/registry/01i/scripts/build.ps1 new file mode 100644 index 000000000..4497c5ebd --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01i/scripts/build.ps1 @@ -0,0 +1,32 @@ +# Fortran CFD Project Builder (PowerShell wrapper) + +Write-Host "========================================" -ForegroundColor Cyan +Write-Host " Fortran CFD Project Builder" -ForegroundColor Cyan +Write-Host " (PowerShell wrapper for Python script)" -ForegroundColor Cyan +Write-Host "========================================" -ForegroundColor Cyan +Write-Host "" + +# 检查Python +$python = Get-Command python -ErrorAction SilentlyContinue +if (-not $python) { + $python = Get-Command python3 -ErrorAction SilentlyContinue +} + +if (-not $python) { + Write-Host "[ERROR] Python not found. Please install Python 3." -ForegroundColor Red + Read-Host "Press Enter to exit" + exit 1 +} + +# 运行Python构建脚本 +Write-Host "[INFO] Running Python build script..." -ForegroundColor Yellow +$argsString = $args -join ' ' +$command = "python build.py $argsString" + +Invoke-Expression $command + +if ($LASTEXITCODE -ne 0) { + Write-Host "[ERROR] Build failed" -ForegroundColor Red + Read-Host "Press Enter to exit" + exit 1 +} \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01i/scripts/build.py b/example/1d-linear-convection/weno3/fortran/registry/01i/scripts/build.py new file mode 100644 index 000000000..1c99c2c87 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01i/scripts/build.py @@ -0,0 +1,357 @@ +#!/usr/bin/env python3 +""" +Fortran CFD 项目构建脚本 (Python版) +支持 Intel oneAPI 环境的自动化构建 +""" + +import os +import sys +import subprocess +import shutil +from pathlib import Path +import argparse +import platform +import time + +class Colors: + """终端颜色""" + if platform.system() == "Windows": + # Windows 启用 ANSI + os.system("") + + HEADER = '\033[95m' + BLUE = '\033[94m' + CYAN = '\033[96m' + GREEN = '\033[92m' + YELLOW = '\033[93m' + RED = '\033[91m' + MAGENTA = '\033[95m' + DARK_GRAY = '\033[90m' # 添加这个 + END = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + +def print_color(text, color=Colors.END): + """彩色打印""" + print(f"{color}{text}{Colors.END}") + +def print_header(text): + """打印标题""" + print_color("\n" + "="*60, Colors.CYAN) + print_color(f" {text}", Colors.BOLD + Colors.CYAN) + print_color("="*60 + "\n", Colors.CYAN) + +def print_step(step, total, message): + """打印步骤""" + print_color(f"[{step}/{total}] {message}...", Colors.YELLOW) + +def print_success(message): + """打印成功""" + print_color(f"✓ {message}", Colors.GREEN) + +def print_error(message): + """打印错误""" + print_color(f"✗ {message}", Colors.RED) + +def print_warning(message): + """打印警告""" + print_color(f"! {message}", Colors.YELLOW) + +def run_command(cmd, cwd=None, check=True, capture=True): + """运行命令""" + print_color(f" $ {' '.join(cmd)}", Colors.BLUE) + + try: + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=capture, + text=True, + encoding='utf-8', + errors='ignore', + shell=False + ) + + if capture and result.stdout: + print(result.stdout) + if capture and result.stderr: + print_color(result.stderr, Colors.YELLOW) + + if check and result.returncode != 0: + print_error(f"Command failed with exit code: {result.returncode}") + return False + + return True + + except FileNotFoundError as e: + print_error(f"Command not found: {cmd[0]}") + if check: + raise + return False + except Exception as e: + print_error(f"Command execution failed: {e}") + return False + +def setup_intel_environment(): + """设置 Intel oneAPI 环境""" + setvars_paths = [ + r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat", + r"C:\Program Files\Intel\oneAPI\setvars.bat", + ] + + setvars_path = None + for path in setvars_paths: + if os.path.exists(path): + setvars_path = path + break + + if not setvars_path: + print_error("Intel oneAPI setvars.bat not found.") + print_warning("Please install Intel oneAPI or update the path in build.py") + return False + + # 创建临时的 batch 文件 + temp_bat = "temp_setvars.bat" + with open(temp_bat, 'w') as f: + f.write(f'@echo off\n') + f.write(f'call "{setvars_path}" > nul 2>&1\n') + f.write(f'set\n') + + try: + # 运行并捕获环境变量 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + shell=True, + encoding='utf-8', + errors='ignore' + ) + + # 解析并设置环境变量 + for line in result.stdout.split('\n'): + if '=' in line: + key, value = line.split('=', 1) + os.environ[key.strip()] = value.strip() + + os.remove(temp_bat) + print_success("Intel oneAPI environment configured") + return True + + except Exception as e: + print_error(f"Failed to setup Intel environment: {e}") + if os.path.exists(temp_bat): + os.remove(temp_bat) + return False + +def build_project(args): + """构建项目主函数""" + start_time = time.time() + + print_header(f"Fortran CFD Project Builder") + print_color(f"Build type: {args.build_type}", Colors.CYAN) + print_color(f"Run tests: {args.run_tests}", Colors.CYAN) + print() + + # 获取项目根目录(脚本所在目录的父目录) + script_dir = Path(__file__).parent + project_root = script_dir.parent + os.chdir(project_root) + + print_color(f"Project root: {project_root}", Colors.BLUE) + + # 步骤1: 设置 Intel 环境 + print_step(1, 4, "Setting up Intel Fortran compiler") + if not setup_intel_environment(): + return False + + # 步骤2: 准备构建目录 + print_step(2, 4, "Preparing build directory") + build_dir = project_root / "build" + + if args.clean and build_dir.exists(): + try: + shutil.rmtree(build_dir) + print_success("Cleaned build directory") + except Exception as e: + print_error(f"Failed to clean build directory: {e}") + if not args.force: + return False + + build_dir.mkdir(exist_ok=True) + os.chdir(build_dir) + + # 步骤3: 配置项目 + print_step(3, 4, "Configuring project") + + cmake_cmd = [ + "cmake", + "-G", "Visual Studio 17 2022", + "-A", "x64", + "-T", "fortran=ifx", + f"-DCMAKE_BUILD_TYPE={args.build_type}", + ".." + ] + + if not run_command(cmake_cmd, check=not args.force): + if not args.force: + return False + + # 步骤4: 构建项目 + print_step(4, 4, "Building project") + + build_cmd = [ + "cmake", + "--build", ".", + "--config", args.build_type, + f"-j{args.jobs}" if args.jobs > 1 else "" + ] + + print(f"build_cmd={build_cmd}") + build_cmd = [c for c in build_cmd if c] # 移除空字符串 + + if not run_command(build_cmd, check=not args.force): + if not args.force: + return False + + build_time = time.time() - start_time + print_success(f"Build completed in {build_time:.1f} seconds") + + # 运行测试 + if args.run_tests: + print_header("Running Tests") + run_tests(args.build_type) + + print_header("Build Summary") + print_color(f"Build directory: {build_dir}", Colors.GREEN) + print_color(f"Build type: {args.build_type}", Colors.GREEN) + print_color(f"Total time: {build_time:.1f}s", Colors.GREEN) + + return True + +def run_tests(build_type): + """运行测试""" + tests = [ + ("test_simple", "Simple functionality test"), + ("test_factory", "Factory pattern test"), + ] + + for test_name, description in tests: + # 修复:在这里直接访问当前循环的 test_name + _run_single_test(test_name, description, build_type) + +def _run_single_test(test_name, description, build_type): + """运行单个测试(修复作用域问题)""" + # 可能的测试可执行文件路径 + possible_paths = [ + Path(f"./{build_type}/{test_name}.exe"), + Path(f"./tests/{build_type}/{test_name}.exe"), + Path(f"./tests/{test_name}.exe"), + Path(f"{test_name}.exe"), + Path(f"./{test_name}.exe"), + ] + + test_exe = None + + # 尝试所有可能的路径 + for path in possible_paths: + if path.exists(): + test_exe = path + break + + if test_exe: + print_color(f"\n[TEST] {description}...", Colors.MAGENTA) + print_color("-" * 50, Colors.DARK_GRAY) + + try: + result = subprocess.run( + [str(test_exe)], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + + if result.stdout: + print(result.stdout) + + if result.returncode == 0: + print_success(f"{test_name} passed") + else: + print_error(f"{test_name} failed (exit code: {result.returncode})") + if result.stderr: + print_color(result.stderr, Colors.YELLOW) + + except Exception as e: + print_error(f"{test_name} failed: {e}") + else: + print_warning(f"{test_name}.exe not found. Searched paths:") + for path in possible_paths: + print_color(f" - {path}", Colors.YELLOW) + +def main(): + """主函数""" + parser = argparse.ArgumentParser(description="Fortran CFD Project Builder") + + parser.add_argument( + "--build-type", + choices=["Debug", "Release", "RelWithDebInfo", "MinSizeRel"], + default="Debug", + help="Build type (default: Debug)" + ) + + parser.add_argument( + "--clean", + action="store_true", + help="Clean build directory before building" + ) + + parser.add_argument( + "--no-tests", + action="store_true", + help="Skip running tests" + ) + + parser.add_argument( + "-j", "--jobs", + type=int, + default=0, + help="Number of parallel jobs (0 = use all cores)" + ) + + parser.add_argument( + "--force", + action="store_true", + help="Continue on errors" + ) + + parser.add_argument( + "--verbose", + action="store_true", + help="Verbose output" + ) + + args = parser.parse_args() + + if args.jobs == 0: + import multiprocessing + args.jobs = multiprocessing.cpu_count() + + args.run_tests = not args.no_tests + + try: + success = build_project(args) + if not success and not args.force: + sys.exit(1) + except KeyboardInterrupt: + print_color("\nBuild interrupted by user", Colors.YELLOW) + sys.exit(1) + except Exception as e: + print_error(f"Unexpected error: {e}") + if args.verbose: + import traceback + traceback.print_exc() + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01i/scripts/requirements.txt b/example/1d-linear-convection/weno3/fortran/registry/01i/scripts/requirements.txt new file mode 100644 index 000000000..d21a12b45 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01i/scripts/requirements.txt @@ -0,0 +1,2 @@ +# 构建脚本的Python依赖(可选) +# 目前没有特殊依赖,保持空文件或添加未来可能需要的包 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01i/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01i/src/CMakeLists.txt new file mode 100644 index 000000000..ee38952bb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01i/src/CMakeLists.txt @@ -0,0 +1,14 @@ +# ==================== src\CMakeLists.txt ==================== + +# src/CMakeLists.txt +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(core) +add_subdirectory(infrastructure) +add_subdirectory(numerics) + +message(STATUS "源代码目录配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01i/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01i/src/core/CMakeLists.txt new file mode 100644 index 000000000..bcfd024d8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01i/src/core/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/core/CMakeLists.txt +message(STATUS "配置核心模块...") + +add_library(core STATIC + registry.f90 + factory_interfaces.f90 +) + +target_include_directories(core PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 安装规则 +install(TARGETS core + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) +install(FILES + ${CMAKE_Fortran_MODULE_DIRECTORY}/registry_module.mod + ${CMAKE_Fortran_MODULE_DIRECTORY}/factory_interfaces.mod + DESTINATION include/fortran_cfd/core +) + +message(STATUS "核心模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01i/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/01i/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..19cc2c59e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01i/src/core/factory_interfaces.f90 @@ -0,0 +1,17 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, factory_procedure + + ! Factory procedure interface + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01i/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/01i/src/core/registry.f90 new file mode 100644 index 000000000..49a30b61a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01i/src/core/registry.f90 @@ -0,0 +1,318 @@ +! src/core/registry.f90 +module registry_module + use, intrinsic :: iso_fortran_env, only: real64 + use factory_interfaces, only: factory_procedure + implicit none + + private + + ! Public interface + public :: real64, component_info, component_registry + public :: register_component_simple, initialize_registry, cleanup_registry + public :: has_component, get_available_components + public :: registry_is_initialized, registry_get_size ! 添加公共访问方法 + + ! Type definitions (simplified, no factory for now) + type :: component_info + character(len=32) :: category = "" + character(len=32) :: name = "" + integer :: order = 0 + contains + procedure :: print => ci_print + end type component_info + + type :: component_registry_type + private + type(component_info), allocatable :: components(:) + integer :: count = 0 + integer :: capacity = 100 + logical :: verbose = .true. + logical :: initialized = .false. + contains + procedure :: register => cr_register + procedure :: get => cr_get + procedure :: list_all => cr_list_all + procedure :: clear => cr_clear + procedure :: size => cr_size + procedure :: is_initialized => cr_is_initialized ! 内部方法 + end type component_registry_type + + ! Global registry instance + type(component_registry_type), save :: component_registry + +contains + + ! ==================== PUBLIC API ==================== + + ! Initialize registry + subroutine initialize_registry(initial_capacity, verbose) + integer, optional, intent(in) :: initial_capacity + logical, optional, intent(in) :: verbose + + if (component_registry%initialized) then + if (component_registry%verbose) then + print *, "[INFO] Registry already initialized" + end if + return + end if + + if (present(initial_capacity)) then + component_registry%capacity = max(10, initial_capacity) + end if + + if (present(verbose)) then + component_registry%verbose = verbose + end if + + ! Allocate array + allocate(component_registry%components(component_registry%capacity)) + + component_registry%initialized = .true. + component_registry%count = 0 + + if (component_registry%verbose) then + print *, "[INIT] Registry initialized, capacity:", component_registry%capacity + end if + end subroutine initialize_registry + + ! Cleanup registry + subroutine cleanup_registry + call component_registry%clear() + if (component_registry%verbose) then + print *, "[CLEANUP] Registry cleaned up" + end if + end subroutine cleanup_registry + + ! Simple registration (no factory) + subroutine register_component_simple(category, name) + character(len=*), intent(in) :: category, name + + type(component_info) :: info + + info%category = to_lower(trim(adjustl(category))) + info%name = to_lower(trim(adjustl(name))) + info%order = 0 + + call component_registry%register(info) + end subroutine register_component_simple + + ! Check if component exists + function has_component(category, name) result(found) + character(len=*), intent(in) :: category, name + logical :: found + + type(component_info) :: info + character(len=32) :: cat_lower, name_lower + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + info = component_registry%get(cat_lower, name_lower) + found = (len_trim(info%category) > 0) + end function has_component + + ! Get available components in a category + subroutine get_available_components(category, names, orders) + character(len=*), intent(in) :: category + character(len=:), allocatable, intent(out), optional :: names(:) + integer, allocatable, intent(out), optional :: orders(:) + + character(len=32) :: cat_lower + integer :: i, count, idx + type(component_info) :: info + + cat_lower = to_lower(trim(adjustl(category))) + + ! Count components in this category + count = 0 + do i = 1, component_registry%count + if (component_registry%components(i)%category == cat_lower) then + count = count + 1 + end if + end do + + ! Allocate arrays if requested + if (present(names)) then + allocate(character(len=32) :: names(count)) + end if + + if (present(orders)) then + allocate(orders(count)) + end if + + ! Fill arrays + idx = 1 + do i = 1, component_registry%count + if (component_registry%components(i)%category == cat_lower) then + info = component_registry%components(i) + if (present(names)) then + names(idx) = info%name + end if + if (present(orders)) then + orders(idx) = info%order + end if + idx = idx + 1 + end if + end do + end subroutine get_available_components + + ! Public function to check if registry is initialized + function registry_is_initialized() result(is_initialized) + logical :: is_initialized + is_initialized = component_registry%is_initialized() + end function registry_is_initialized + + ! Public function to get registry size + function registry_get_size() result(size_val) + integer :: size_val + size_val = component_registry%size() + end function registry_get_size + + ! ==================== COMPONENT INFO METHODS ==================== + + subroutine ci_print(this) + class(component_info), intent(in) :: this + + if (this%order > 0) then + print *, " [", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ")]" + else + print *, " [", trim(this%category), ".", trim(this%name), "]" + end if + end subroutine ci_print + + ! ==================== REGISTRY INTERNAL METHODS ==================== + + subroutine cr_register(this, info) + class(component_registry_type), intent(inout) :: this + type(component_info), intent(in) :: info + + type(component_info), allocatable :: temp(:) + integer :: i + + if (.not. this%initialized) then + error stop "[ERROR] Registry not initialized, call initialize_registry first" + end if + + ! Check if already exists + do i = 1, this%count + if (this%components(i)%category == info%category .and. & + this%components(i)%name == info%name) then + if (this%verbose) then + print *, "[WARN] Overwriting: ", & + trim(info%category), ".", trim(info%name) + end if + this%components(i) = info + return + end if + end do + + ! Expand array if needed + if (this%count >= this%capacity) then + this%capacity = this%capacity * 2 + allocate(temp(this%capacity)) + temp(1:this%count) = this%components(1:this%count) + call move_alloc(temp, this%components) + + if (this%verbose) then + print *, "[INFO] Registry expanded to capacity:", this%capacity + end if + end if + + ! Add component + this%count = this%count + 1 + this%components(this%count) = info + + if (this%verbose) then + print *, "[OK] Registered: ", trim(info%category), ".", trim(info%name) + end if + end subroutine cr_register + + function cr_get(this, category, name) result(info) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category, name + type(component_info) :: info + + integer :: i + + ! Initialize return value as empty + info%category = "" + info%name = "" + info%order = 0 + + if (.not. this%initialized) then + return + end if + + do i = 1, this%count + if (this%components(i)%category == category .and. & + this%components(i)%name == name) then + info = this%components(i) + return + end if + end do + end function cr_get + + subroutine cr_list_all(this) + class(component_registry_type), intent(in) :: this + integer :: i + + if (.not. this%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + print *, "=== Registry Contents (", this%count, " components) ===" + + if (this%count == 0) then + print *, " (empty)" + return + end if + + ! Show components grouped by category + do i = 1, this%count + call this%components(i)%print() + end do + + print *, "===========================================" + end subroutine cr_list_all + + subroutine cr_clear(this) + class(component_registry_type), intent(inout) :: this + + if (allocated(this%components)) then + deallocate(this%components) + end if + + this%count = 0 + this%capacity = 100 + this%initialized = .false. + end subroutine cr_clear + + integer function cr_size(this) + class(component_registry_type), intent(in) :: this + cr_size = this%count + end function cr_size + + logical function cr_is_initialized(this) + class(component_registry_type), intent(in) :: this + cr_is_initialized = this%initialized + end function cr_is_initialized + + ! ==================== UTILITY FUNCTIONS ==================== + + function to_lower(str) result(lower_str) + character(len=*), intent(in) :: str + character(len=len(str)) :: lower_str + integer :: i + + do i = 1, len(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + lower_str(i:i) = char(ichar(str(i:i)) + 32) + else + lower_str(i:i) = str(i:i) + end if + end do + end function to_lower + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01i/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01i/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..46964ce79 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01i/src/infrastructure/CMakeLists.txt @@ -0,0 +1,20 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "配置基础设施模块...") + +add_library(infrastructure STATIC + config.f90 + mesh.f90 +) + +target_link_libraries(infrastructure PRIVATE core) + +target_include_directories(infrastructure PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS infrastructure + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "基础设施模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01i/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/01i/src/infrastructure/config.f90 new file mode 100644 index 000000000..34019f2f5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01i/src/infrastructure/config.f90 @@ -0,0 +1,90 @@ +! src/infrastructure/config.f90 +module config_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, cfd_config, config_print, config_with_reconstruction + + ! CFD configuration type + type :: cfd_config + character(len=20) :: ic_type = "step" + character(len=20) :: recon_scheme = "eno" + character(len=20) :: flux_type = "rusanov" + integer :: rk_order = 1 + real(real64) :: wave_speed = 1.0_real64 + real(real64) :: final_time = 0.625_real64 + real(real64) :: dt = 0.025_real64 + character(len=20) :: boundary_type = "periodic" + real(real64) :: left_boundary_value = 1.0_real64 + real(real64) :: right_boundary_value = 2.0_real64 + integer :: spatial_order = 2 + logical :: verbose = .true. + end type cfd_config + +contains + + subroutine config_print(this) + type(cfd_config), intent(in) :: this + + print *, "=== CFD Configuration ===" + print *, "Initial condition: ", trim(this%ic_type) + print *, "Reconstruction: ", trim(this%recon_scheme), " (order:", this%spatial_order, ")" + print *, "Flux type: ", trim(this%flux_type) + print *, "Time integration: RK", this%rk_order + print *, "Wave speed: ", this%wave_speed + print *, "Final time: ", this%final_time + print *, "Time step: ", this%dt + print *, "Boundary: ", trim(this%boundary_type) + if (trim(this%boundary_type) == 'dirichlet') then + print *, " Dirichlet values: [", this%left_boundary_value, ", ", & + this%right_boundary_value, "]" + end if + print *, "===============================" + end subroutine config_print + + subroutine config_with_reconstruction(this, scheme, order) + type(cfd_config), intent(inout) :: this + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + character(len=20) :: scheme_lower + + ! Convert to lowercase + scheme_lower = scheme + call to_lower_inplace(scheme_lower) + this%recon_scheme = trim(adjustl(scheme_lower)) + + ! Set order + if (present(order)) then + this%spatial_order = order + else + ! Smart defaults + if (index(this%recon_scheme, 'weno') > 0) then + this%spatial_order = 5 + else if (trim(this%recon_scheme) == 'eno') then + this%spatial_order = 3 + else + print *, "[ERROR] Unsupported reconstruction scheme: ", trim(this%recon_scheme) + return + end if + end if + + if (this%verbose) then + print *, "[CONFIG] Reconstruction: ", trim(this%recon_scheme), & + " Order: ", this%spatial_order + end if + end subroutine config_with_reconstruction + + subroutine to_lower_inplace(str) + character(len=*), intent(inout) :: str + integer :: i + + do i = 1, len_trim(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + str(i:i) = char(ichar(str(i:i)) + 32) + end if + end do + end subroutine to_lower_inplace + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01i/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/01i/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..bf1ca7eba --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01i/src/infrastructure/mesh.f90 @@ -0,0 +1,74 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, mesh_type, mesh_init, mesh_print_info + + ! mesh类型 + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer :: ncells = 40 + integer :: nnodes + integer :: nx + real(wp), allocatable :: x(:) ! 节点坐标 + real(wp), allocatable :: xcc(:) ! 单元中心坐标 + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer, optional, intent(in) :: ncells + + integer :: i + + ! Set参数 + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! computation派生参数 + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! 分配数组 + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! 生成node坐标 + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! 生成cell中心坐标 + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== 网格信息 ===" + print *, "计算域: [", this%xmin, ", ", this%xmax, "]" + print *, "单元数: ", this%ncells + print *, "节点数: ", this%nnodes + print *, "网格尺寸 dx: ", this%dx + print *, "域长度 L: ", this%L + print *, "==========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..66874a7ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/CMakeLists.txt @@ -0,0 +1,7 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..3fde7746b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,28 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +# Start with just the base module +add_library(flux STATIC + base.f90 + rusanov.f90 +) + +#target_link_libraries(flux PRIVATE core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 明确设置不使用 /Qm64 选项 +if(CMAKE_Fortran_COMPILER_ID MATCHES "IntelLLVM|Intel") + set_target_properties(flux PROPERTIES + Fortran_FLAGS "${CMAKE_Fortran_FLAGS} /Qm64" # 明确设置,避免继承问题 + ) +endif() + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/flux/base.f90 new file mode 100644 index 000000000..1ed008383 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/flux/base.f90 @@ -0,0 +1,23 @@ +! src/numerics/flux/base.f90 +module flux_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, flux_calculator_base + + type :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure :: info => flux_info + end type flux_calculator_base + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "Flux calculator information:" + print *, " Name: ", trim(this%name) + end subroutine flux_info + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/flux/rusanov.f90 b/example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/flux/rusanov.f90 new file mode 100644 index 000000000..0600bb53b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/flux/rusanov.f90 @@ -0,0 +1,46 @@ +! src/numerics/flux/rusanov.f90 +module rusanov_flux_module + use, intrinsic :: iso_fortran_env, only: real64 + use flux_base_module, only: flux_calculator_base + implicit none + + private + public :: real64, rusanov_flux + + type, extends(flux_calculator_base) :: rusanov_flux + real(real64) :: wave_speed_default = 1.0_real64 + contains + procedure :: info => rusanov_info + end type rusanov_flux + + interface rusanov_flux + module procedure create_rusanov_flux + end interface + +contains + + type(rusanov_flux) function create_rusanov_flux(name, wave_speed_default) result(this) + character(len=*), optional, intent(in) :: name + real(real64), optional, intent(in) :: wave_speed_default + + if (present(name)) then + this%name = name + else + this%name = "Rusanov" + end if + + if (present(wave_speed_default)) then + this%wave_speed_default = wave_speed_default + else + this%wave_speed_default = 1.0_real64 + end if + end function create_rusanov_flux + + subroutine rusanov_info(this) + class(rusanov_flux), intent(in) :: this + call flux_info(this) + print *, " Type: Rusanov flux" + print *, " Default wave speed: ", this%wave_speed_default + end subroutine rusanov_info + +end module rusanov_flux_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..5e4b938d5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,22 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "Configuring reconstructor module...") + +# Start with just the base module +add_library(reconstructor STATIC + base.f90 + eno.f90 + weno3.f90 +) + +target_link_libraries(reconstructor PRIVATE core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Reconstructor module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..a1ef781a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/reconstructor/base.f90 @@ -0,0 +1,40 @@ +! src/numerics/reconstructor/base.f90 +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, reconstructor_base + + ! Base reconstructor type + type :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + contains + procedure :: info => reconstructor_info + procedure :: reconstruct => reconstruct_default ! 添加这个方法 + end type reconstructor_base + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + print *, "Reconstructor information:" + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_info + + ! 默认的reconstructionmethod + subroutine reconstruct_default(this, q, qL, qR) + class(reconstructor_base), intent(in) :: this + real(real64), intent(in) :: q(0:) ! 包含ghost cells + real(real64), intent(out) :: qL(:), qR(:) + integer :: i, n + + n = size(qL) + do i = 1, n + qL(i) = q(i) ! 简单的一阶重构 + qR(i) = q(i+1) + end do + end subroutine reconstruct_default +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/reconstructor/eno.f90 b/example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/reconstructor/eno.f90 new file mode 100644 index 000000000..62a35cf87 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/reconstructor/eno.f90 @@ -0,0 +1,56 @@ +! src/numerics/reconstructor/eno.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, eno_reconstructor + + type, extends(reconstructor_base) :: eno_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => eno_info + end type eno_reconstructor + + ! Add构造函数接口 + interface eno_reconstructor + module procedure create_eno_reconstructor + end interface + +contains + + ! 构造函数 + type(eno_reconstructor) function create_eno_reconstructor(name, order, epsilon) result(this) + character(len=*), optional, intent(in) :: name + integer, optional, intent(in) :: order + real(real64), optional, intent(in) :: epsilon + + ! Set默认值 + if (present(name)) then + this%name = name + else + this%name = "ENO" + end if + + if (present(order)) then + this%order = order + else + this%order = 3 + end if + + if (present(epsilon)) then + this%epsilon = epsilon + else + this%epsilon = 1.0e-6_real64 + end if + end function create_eno_reconstructor + + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + call reconstructor_info(this) + print *, " Type: ENO reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/reconstructor/weno3.f90 b/example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/reconstructor/weno3.f90 new file mode 100644 index 000000000..c1ea9de09 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01i/src/numerics/reconstructor/weno3.f90 @@ -0,0 +1,55 @@ +! src/numerics/reconstructor/weno3.f90 +module weno3_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno3_reconstructor + + type, extends(reconstructor_base) :: weno3_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno3_info + end type weno3_reconstructor + + ! Add构造函数接口 + interface weno3_reconstructor + module procedure create_weno3_reconstructor + end interface + +contains + + ! 构造函数 + type(weno3_reconstructor) function create_weno3_reconstructor(name, order, epsilon) result(this) + character(len=*), optional, intent(in) :: name + integer, optional, intent(in) :: order + real(real64), optional, intent(in) :: epsilon + + if (present(name)) then + this%name = name + else + this%name = "WENO3" + end if + + if (present(order)) then + this%order = order + else + this%order = 3 + end if + + if (present(epsilon)) then + this%epsilon = epsilon + else + this%epsilon = 1.0e-6_real64 + end if + end function create_weno3_reconstructor + + subroutine weno3_info(this) + class(weno3_reconstructor), intent(in) :: this + call reconstructor_info(this) + print *, " Type: WENO-3 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno3_info + +end module weno3_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01i/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01i/tests/CMakeLists.txt new file mode 100644 index 000000000..08f6884e0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01i/tests/CMakeLists.txt @@ -0,0 +1,29 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +add_executable(test_minimal_simple test_minimal_simple.f90) + +message(STATUS "CMAKE_Fortran_MODULE_DIRECTORY=${CMAKE_Fortran_MODULE_DIRECTORY}") + +#target_include_directories( test_minimal_simple +# PRIVATE +# ${CMAKE_Fortran_MODULE_DIRECTORY} +#) + +target_link_libraries( test_minimal_simple + PRIVATE + infrastructure +) + + +add_executable(test_factory_simple test_factory_simple.f90) + +#target_link_libraries(test_factory_simple core infrastructure reconstructor flux) + +target_link_libraries( test_factory_simple + PRIVATE + core + infrastructure + reconstructor + flux +) diff --git a/example/1d-linear-convection/weno3/fortran/registry/01i/tests/test_factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/01i/tests/test_factory.f90 new file mode 100644 index 000000000..8968b9c28 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01i/tests/test_factory.f90 @@ -0,0 +1,154 @@ +! tests/test_factory.f90 +program test_factory + use registry_module + use config_module + use mesh_module + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + class(*), allocatable :: instance + class(reconstructor_base), allocatable :: recon + class(flux_calculator_base), allocatable :: flux_calc + real(wp), allocatable :: q(:), qL(:), qR(:), flux(:) + integer :: i, n + + print *, "=== Factory Pattern Test ===" + print *, "" + + ! Initialize systems + call initialize_registry(verbose=.true.) + + ! Register components with factories + print *, "1. Registering components with factories..." + call register_component_with_factory("reconstructor", "eno", create_eno, 3) + call register_component_with_factory("reconstructor", "weno3", create_weno3, 3) + call register_component_with_factory("flux", "rusanov", create_rusanov) + + call component_registry%list_all() + print *, "" + + ! Test creating ENO reconstructor + print *, "2. Creating ENO reconstructor..." + call create_component("reconstructor", "eno", instance) + + if (allocated(instance)) then + select type (inst => instance) + type is (eno_reconstructor) + allocate(recon, source=inst) + print *, "ENO reconstructor created successfully" + call recon%info() + print *, "" + + ! Test reconstruction + n = 10 + allocate(q(0:n+1), qL(n), qR(n)) + + ! Initialize test data (sine wave) + do i = 0, n+1 + q(i) = sin(2.0_wp * 3.141592653589793_wp * real(i-1, wp) / real(n, wp)) + end do + + print *, "Testing ENO reconstruction..." + call recon%reconstruct(q, qL, qR) + + print *, "q (internal):" + do i = 1, n + write(*, '(I3, F10.6)') i, q(i) + end do + + print *, "qL (left interface values):" + do i = 1, n + write(*, '(I3, F10.6)') i, qL(i) + end do + + deallocate(q, qL, qR) + class default + print *, "[ERROR] Wrong type for ENO reconstructor" + end select + else + print *, "[ERROR] Failed to create ENO reconstructor" + end if + print *, "" + + ! Test creating WENO3 reconstructor + print *, "3. Creating WENO3 reconstructor..." + if (allocated(instance)) deallocate(instance) + call create_component("reconstructor", "weno3", instance) + + if (allocated(instance)) then + select type (inst => instance) + type is (weno3_reconstructor) + if (allocated(recon)) deallocate(recon) + allocate(recon, source=inst) + print *, "WENO3 reconstructor created successfully" + call recon%info() + class default + print *, "[ERROR] Wrong type for WENO3 reconstructor" + end select + else + print *, "[ERROR] Failed to create WENO3 reconstructor" + end if + print *, "" + + ! Test creating Rusanov flux calculator + print *, "4. Creating Rusanov flux calculator..." + if (allocated(instance)) deallocate(instance) + call create_component("flux", "rusanov", instance) + + if (allocated(instance)) then + select type (inst => instance) + type is (rusanov_flux) + allocate(flux_calc, source=inst) + print *, "Rusanov flux calculator created successfully" + call flux_calc%info() + print *, "" + + ! Test flux computation + n = 5 + allocate(qL(n), qR(n), flux(n)) + + ! Initialize test data + do i = 1, n + qL(i) = 1.0_wp + 0.1_wp * real(i-1, wp) + qR(i) = 1.0_wp + 0.1_wp * real(i, wp) + end do + + print *, "Testing Rusanov flux computation..." + call flux_calc%compute(qL, qR, flux, 1.0_wp) + + print *, "qL:" + do i = 1, n + write(*, '(I3, F10.6)') i, qL(i) + end do + + print *, "qR:" + do i = 1, n + write(*, '(I3, F10.6)') i, qR(i) + end do + + print *, "Flux:" + do i = 1, n + write(*, '(I3, F10.6)') i, flux(i) + end do + + deallocate(qL, qR, flux) + class default + print *, "[ERROR] Wrong type for Rusanov flux" + end select + else + print *, "[ERROR] Failed to create Rusanov flux calculator" + end if + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Factory pattern test completed successfully ===" + +end program test_factory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01i/tests/test_factory_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/01i/tests/test_factory_simple.f90 new file mode 100644 index 000000000..7f0460eb2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01i/tests/test_factory_simple.f90 @@ -0,0 +1,4 @@ +! tests/test_factory_simple.f90 +program test_factory_simple + +end program test_factory_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01i/tests/test_factory_simpleBAK.f90 b/example/1d-linear-convection/weno3/fortran/registry/01i/tests/test_factory_simpleBAK.f90 new file mode 100644 index 000000000..07a035473 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01i/tests/test_factory_simpleBAK.f90 @@ -0,0 +1,106 @@ +! tests/test_factory_simple.f90 +program test_factory_simple + use registry_module + use config_module + use mesh_module + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(eno_reconstructor) :: eno + type(weno3_reconstructor) :: weno3 + type(rusanov_flux) :: rusanov + integer :: i + + print *, "=== Factory Pattern Simple Test ===" + print *, "" + + ! Test 1: Configuration and mesh + print *, "1. Testing basic systems..." + print *, "-----------------------------" + call config_print(config) + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 2: Creating reconstructors directly + print *, "2. Creating reconstructors..." + print *, "------------------------------" + eno = eno_reconstructor(name="ENO", order=3, epsilon=1.0e-6_wp) + weno3 = weno3_reconstructor(name="WENO3", order=3, epsilon=1.0e-6_wp) + + call eno%info() + call weno3%info() + print *, "" + + ! Test 3: Creating flux calculator directly + print *, "3. Creating flux calculator..." + print *, "-------------------------------" + rusanov = rusanov_flux(name="Rusanov", wave_speed_default=1.0_wp) + call rusanov%info() + print *, "" + + ! Test 4: Registry integration + print *, "4. Testing registry..." + print *, "----------------------" + call initialize_registry(verbose=.true.) + + ! Register with simple method + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("flux", "rusanov") + + ! Check if registered + if (has_component("reconstructor", "eno")) then + print *, "[OK] ENO reconstructor registered successfully" + else + print *, "[ERROR] ENO reconstructor registration failed" + end if + + if (has_component("flux", "rusanov")) then + print *, "[OK] Rusanov flux registered successfully" + else + print *, "[ERROR] Rusanov flux registration failed" + end if + + print *, "" + call component_registry%list_all() + print *, "" + + ! Test getting available components + print *, "5. Testing component listing..." + print *, "--------------------------------" + call test_component_listing() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Factory pattern simple test completed successfully ===" + +contains + + subroutine test_component_listing() + character(len=:), allocatable :: recon_names(:) + integer, allocatable :: recon_orders(:) + integer :: i + + print *, "Available reconstructors:" + call get_available_components("reconstructor", recon_names, recon_orders) + + if (allocated(recon_names)) then + do i = 1, size(recon_names) + print *, " - ", trim(recon_names(i)) + end do + print *, "Total reconstructors: ", size(recon_names) + else + print *, " (no reconstructors found)" + end if + end subroutine test_component_listing + +end program test_factory_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01i/tests/test_minimal.f90 b/example/1d-linear-convection/weno3/fortran/registry/01i/tests/test_minimal.f90 new file mode 100644 index 000000000..bb4b7cda9 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01i/tests/test_minimal.f90 @@ -0,0 +1,108 @@ +! tests/test_minimal.f90 +program test_minimal + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i ! Declare loop variable here + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", component_registry%size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components + print *, "5. Testing available components" + print *, "--------------------------------" + + ! Use a separate block to avoid allocation issues + call test_available_components() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Minimal test completed successfully ===" + +contains + + ! Internal subroutine for testing available components + subroutine test_available_components() + character(len=:), allocatable :: names(:) + integer, allocatable :: orders(:) + integer :: j + + call get_available_components("reconstructor", names, orders) + + if (allocated(names)) then + print *, "Reconstructors:" + do j = 1, size(names) + print *, " - ", trim(names(j)) + if (allocated(orders)) then + print *, " Order: ", orders(j) + end if + end do + else + print *, "No reconstructors found" + end if + end subroutine test_available_components + +end program test_minimal \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01i/tests/test_minimal_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/01i/tests/test_minimal_simple.f90 new file mode 100644 index 000000000..689165de8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01i/tests/test_minimal_simple.f90 @@ -0,0 +1,84 @@ +! tests/test_minimal_simple.f90 +program test_minimal_simple + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Simple Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components (simple version) + print *, "5. Testing registry functionality" + print *, "---------------------------------" + print *, "Registry initialized: ", registry_is_initialized() + print *, "Number of components: ", registry_get_size() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Simple test completed successfully ===" + +end program test_minimal_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01i/tests/test_minimal_simpleBAK.f90 b/example/1d-linear-convection/weno3/fortran/registry/01i/tests/test_minimal_simpleBAK.f90 new file mode 100644 index 000000000..689165de8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01i/tests/test_minimal_simpleBAK.f90 @@ -0,0 +1,84 @@ +! tests/test_minimal_simple.f90 +program test_minimal_simple + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Simple Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components (simple version) + print *, "5. Testing registry functionality" + print *, "---------------------------------" + print *, "Registry initialized: ", registry_is_initialized() + print *, "Number of components: ", registry_get_size() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Simple test completed successfully ===" + +end program test_minimal_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01j/CMakeLists.txt new file mode 100644 index 000000000..ef66d5848 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/README.md b/example/1d-linear-convection/weno3/fortran/registry/01j/README.md new file mode 100644 index 000000000..c4889757b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/README.md @@ -0,0 +1,221 @@ +# Fortran CFD Project + +基于工厂模式和注册系统的CFD求解器框架。 + +## 项目结构 + +``` +fortran_cfd/ +├── CMakeLists.txt # 根CMake配置 +├── scripts/ # 构建脚本 +│ ├── build.py # Python构建脚本(推荐) +│ ├── build.bat # Batch包装脚本 +│ ├── build.ps1 # PowerShell包装脚本 +│ └── requirements.txt # Python依赖 +├── src/ # 源代码 +│ ├── CMakeLists.txt +│ ├── core/ # 核心模块(注册系统、工厂接口) +│ ├── infrastructure/ # 基础设施(配置、网格) +│ └── numerics/ # 数值方法 +├── tests/ # 测试 +│ ├── CMakeLists.txt +│ ├── test_minimal_simple.f90 # 简单测试 +│ └── test_factory.f90 # 工厂模式测试 +└── README.md # 项目说明(本文件) +``` + +## 快速开始 + +### 使用Python脚本(推荐) + +```bash +# 进入脚本目录 +cd scripts + +# 基本构建 +python build.py + +# 清理并构建 +python build.py --clean + +# 发布构建 +python build.py --build-type Release --clean + +# 并行构建(使用所有核心) +python build.py -j + +# 不运行测试 +python build.py --no-tests + +# 查看帮助 +python build.py --help +``` + +### 使用批处理脚本 + +```bash +cd scripts +.\build.bat +``` + +### 使用PowerShell脚本 + +```powershell +cd scripts +.\build.ps1 +``` + +## 手动构建 + +```bash +# 设置Intel环境 +cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && powershell' + +# 配置项目 +mkdir build +cd build +cmake -G "Visual Studio 17 2022" -A x64 -T fortran=ifx .. + +# 构建项目 +cmake --build . --config Debug + +# 运行测试 +Debug\test_simple.exe +Debug\test_factory.exe +``` + +## 功能特性 + +✅ 组件注册系统 +✅ 工厂模式 +✅ ENO/WENO重构器 +✅ Rusanov通量计算器 +✅ 可扩展架构 +✅ 自动化测试 + +## 依赖 + +- Intel oneAPI(包含ifx编译器) +- CMake 3.12+ +- Python 3.6+(仅用于构建脚本) + +## 源代码文件 + +### 核心模块 +- `src/core/registry.f90` - 组件注册系统 +- `src/core/factory_interfaces.f90` - 工厂接口 + +### 基础设施 +- `src/infrastructure/config.f90` - 配置管理 +- `src/infrastructure/mesh.f90` - 网格生成 + +### 数值方法 +- `src/numerics/reconstructor/base.f90` - 重构器基类 +- `src/numerics/reconstructor/eno.f90` - ENO重构器 +- `src/numerics/reconstructor/weno3.f90` - WENO-3重构器 +- `src/numerics/flux/base.f90` - 通量计算器基类 +- `src/numerics/flux/rusanov.f90` - Rusanov通量 + +### 测试 +- `tests/test_minimal_simple.f90` - 简单功能测试 +- `tests/test_factory.f90` - 工厂模式测试 + +## 构建脚本 + +### Python脚本 (build.py) +主构建脚本,支持: +- 自动设置Intel oneAPI环境 +- 参数化构建选项 +- 并行编译 +- 自动化测试 +- 彩色输出 + +### Batch脚本 (build.bat) +Windows批处理包装器,调用Python脚本。 + +### PowerShell脚本 (build.ps1) +PowerShell包装器,调用Python脚本。 + +## 示例输出 + +成功构建后,会看到类似输出: + +``` +============================================================ + Fortran CFD Project Builder +============================================================ + +[1/4] Setting up Intel Fortran compiler... +✓ Intel oneAPI environment configured + +[2/4] Preparing build directory... +✓ Cleaned build directory + +[3/4] Configuring project... + $ cmake -G Visual Studio 17 2022 -A x64 -T fortran=ifx -DCMAKE_BUILD_TYPE=Debug .. +✓ CMake configuration successful + +[4/4] Building project... + $ cmake --build . --config Debug +✓ Build completed in 12.3 seconds + +============================================================ + Running Tests +============================================================ + +[TEST] Simple functionality test... +✓ Simple test passed + +[TEST] Factory pattern test... +✓ Factory test passed + +============================================================ + Build Summary +============================================================ +✓ Build directory: D:\path\to\build +✓ Build type: Debug +✓ Total time: 15.2s +``` + +## 开发指南 + +### 添加新组件 + +1. 在相应模块中创建Fortran源文件 +2. 实现工厂函数 +3. 使用`register_component_with_factory`注册组件 +4. 添加测试 + +### 扩展架构 + +项目采用模块化设计: +1. **注册系统** - 管理所有可用的组件 +2. **工厂模式** - 动态创建组件实例 +3. **抽象接口** - 确保组件兼容性 +4. **配置驱动** - 通过配置文件控制行为 + +## 故障排除 + +### 常见问题 + +1. **Intel环境未找到** + - 检查Intel oneAPI安装路径 + - 手动运行`setvars.bat` + +2. **CMake配置失败** + - 确保使用正确的生成器:`Visual Studio 17 2022` + - 检查Fortran编译器是否可用 + +3. **构建失败** + - 检查依赖项是否完整 + - 查看详细的错误信息 + +### 调试建议 + +- 使用`--verbose`参数获取详细输出 +- 检查`build/CMakeCache.txt`了解配置详情 +- 查看编译器错误日志 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/scripts/build.bat b/example/1d-linear-convection/weno3/fortran/registry/01j/scripts/build.bat new file mode 100644 index 000000000..d243695b0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/scripts/build.bat @@ -0,0 +1,26 @@ +@echo off +echo ======================================== +echo Fortran CFD Project Builder +echo (Wrapper for Python script) +echo ======================================== +echo. + +REM 检查Python +where python >nul 2>nul +if %errorlevel% neq 0 ( + echo [ERROR] Python not found. Please install Python 3. + pause + exit /b 1 +) + +REM 运行Python构建脚本 +echo [INFO] Running Python build script... +python build.py %* + +if %errorlevel% neq 0 ( + echo [ERROR] Build failed + pause + exit /b 1 +) + +pause \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/scripts/build.ps1 b/example/1d-linear-convection/weno3/fortran/registry/01j/scripts/build.ps1 new file mode 100644 index 000000000..4497c5ebd --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/scripts/build.ps1 @@ -0,0 +1,32 @@ +# Fortran CFD Project Builder (PowerShell wrapper) + +Write-Host "========================================" -ForegroundColor Cyan +Write-Host " Fortran CFD Project Builder" -ForegroundColor Cyan +Write-Host " (PowerShell wrapper for Python script)" -ForegroundColor Cyan +Write-Host "========================================" -ForegroundColor Cyan +Write-Host "" + +# 检查Python +$python = Get-Command python -ErrorAction SilentlyContinue +if (-not $python) { + $python = Get-Command python3 -ErrorAction SilentlyContinue +} + +if (-not $python) { + Write-Host "[ERROR] Python not found. Please install Python 3." -ForegroundColor Red + Read-Host "Press Enter to exit" + exit 1 +} + +# 运行Python构建脚本 +Write-Host "[INFO] Running Python build script..." -ForegroundColor Yellow +$argsString = $args -join ' ' +$command = "python build.py $argsString" + +Invoke-Expression $command + +if ($LASTEXITCODE -ne 0) { + Write-Host "[ERROR] Build failed" -ForegroundColor Red + Read-Host "Press Enter to exit" + exit 1 +} \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/scripts/build.py b/example/1d-linear-convection/weno3/fortran/registry/01j/scripts/build.py new file mode 100644 index 000000000..1c99c2c87 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/scripts/build.py @@ -0,0 +1,357 @@ +#!/usr/bin/env python3 +""" +Fortran CFD 项目构建脚本 (Python版) +支持 Intel oneAPI 环境的自动化构建 +""" + +import os +import sys +import subprocess +import shutil +from pathlib import Path +import argparse +import platform +import time + +class Colors: + """终端颜色""" + if platform.system() == "Windows": + # Windows 启用 ANSI + os.system("") + + HEADER = '\033[95m' + BLUE = '\033[94m' + CYAN = '\033[96m' + GREEN = '\033[92m' + YELLOW = '\033[93m' + RED = '\033[91m' + MAGENTA = '\033[95m' + DARK_GRAY = '\033[90m' # 添加这个 + END = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + +def print_color(text, color=Colors.END): + """彩色打印""" + print(f"{color}{text}{Colors.END}") + +def print_header(text): + """打印标题""" + print_color("\n" + "="*60, Colors.CYAN) + print_color(f" {text}", Colors.BOLD + Colors.CYAN) + print_color("="*60 + "\n", Colors.CYAN) + +def print_step(step, total, message): + """打印步骤""" + print_color(f"[{step}/{total}] {message}...", Colors.YELLOW) + +def print_success(message): + """打印成功""" + print_color(f"✓ {message}", Colors.GREEN) + +def print_error(message): + """打印错误""" + print_color(f"✗ {message}", Colors.RED) + +def print_warning(message): + """打印警告""" + print_color(f"! {message}", Colors.YELLOW) + +def run_command(cmd, cwd=None, check=True, capture=True): + """运行命令""" + print_color(f" $ {' '.join(cmd)}", Colors.BLUE) + + try: + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=capture, + text=True, + encoding='utf-8', + errors='ignore', + shell=False + ) + + if capture and result.stdout: + print(result.stdout) + if capture and result.stderr: + print_color(result.stderr, Colors.YELLOW) + + if check and result.returncode != 0: + print_error(f"Command failed with exit code: {result.returncode}") + return False + + return True + + except FileNotFoundError as e: + print_error(f"Command not found: {cmd[0]}") + if check: + raise + return False + except Exception as e: + print_error(f"Command execution failed: {e}") + return False + +def setup_intel_environment(): + """设置 Intel oneAPI 环境""" + setvars_paths = [ + r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat", + r"C:\Program Files\Intel\oneAPI\setvars.bat", + ] + + setvars_path = None + for path in setvars_paths: + if os.path.exists(path): + setvars_path = path + break + + if not setvars_path: + print_error("Intel oneAPI setvars.bat not found.") + print_warning("Please install Intel oneAPI or update the path in build.py") + return False + + # 创建临时的 batch 文件 + temp_bat = "temp_setvars.bat" + with open(temp_bat, 'w') as f: + f.write(f'@echo off\n') + f.write(f'call "{setvars_path}" > nul 2>&1\n') + f.write(f'set\n') + + try: + # 运行并捕获环境变量 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + shell=True, + encoding='utf-8', + errors='ignore' + ) + + # 解析并设置环境变量 + for line in result.stdout.split('\n'): + if '=' in line: + key, value = line.split('=', 1) + os.environ[key.strip()] = value.strip() + + os.remove(temp_bat) + print_success("Intel oneAPI environment configured") + return True + + except Exception as e: + print_error(f"Failed to setup Intel environment: {e}") + if os.path.exists(temp_bat): + os.remove(temp_bat) + return False + +def build_project(args): + """构建项目主函数""" + start_time = time.time() + + print_header(f"Fortran CFD Project Builder") + print_color(f"Build type: {args.build_type}", Colors.CYAN) + print_color(f"Run tests: {args.run_tests}", Colors.CYAN) + print() + + # 获取项目根目录(脚本所在目录的父目录) + script_dir = Path(__file__).parent + project_root = script_dir.parent + os.chdir(project_root) + + print_color(f"Project root: {project_root}", Colors.BLUE) + + # 步骤1: 设置 Intel 环境 + print_step(1, 4, "Setting up Intel Fortran compiler") + if not setup_intel_environment(): + return False + + # 步骤2: 准备构建目录 + print_step(2, 4, "Preparing build directory") + build_dir = project_root / "build" + + if args.clean and build_dir.exists(): + try: + shutil.rmtree(build_dir) + print_success("Cleaned build directory") + except Exception as e: + print_error(f"Failed to clean build directory: {e}") + if not args.force: + return False + + build_dir.mkdir(exist_ok=True) + os.chdir(build_dir) + + # 步骤3: 配置项目 + print_step(3, 4, "Configuring project") + + cmake_cmd = [ + "cmake", + "-G", "Visual Studio 17 2022", + "-A", "x64", + "-T", "fortran=ifx", + f"-DCMAKE_BUILD_TYPE={args.build_type}", + ".." + ] + + if not run_command(cmake_cmd, check=not args.force): + if not args.force: + return False + + # 步骤4: 构建项目 + print_step(4, 4, "Building project") + + build_cmd = [ + "cmake", + "--build", ".", + "--config", args.build_type, + f"-j{args.jobs}" if args.jobs > 1 else "" + ] + + print(f"build_cmd={build_cmd}") + build_cmd = [c for c in build_cmd if c] # 移除空字符串 + + if not run_command(build_cmd, check=not args.force): + if not args.force: + return False + + build_time = time.time() - start_time + print_success(f"Build completed in {build_time:.1f} seconds") + + # 运行测试 + if args.run_tests: + print_header("Running Tests") + run_tests(args.build_type) + + print_header("Build Summary") + print_color(f"Build directory: {build_dir}", Colors.GREEN) + print_color(f"Build type: {args.build_type}", Colors.GREEN) + print_color(f"Total time: {build_time:.1f}s", Colors.GREEN) + + return True + +def run_tests(build_type): + """运行测试""" + tests = [ + ("test_simple", "Simple functionality test"), + ("test_factory", "Factory pattern test"), + ] + + for test_name, description in tests: + # 修复:在这里直接访问当前循环的 test_name + _run_single_test(test_name, description, build_type) + +def _run_single_test(test_name, description, build_type): + """运行单个测试(修复作用域问题)""" + # 可能的测试可执行文件路径 + possible_paths = [ + Path(f"./{build_type}/{test_name}.exe"), + Path(f"./tests/{build_type}/{test_name}.exe"), + Path(f"./tests/{test_name}.exe"), + Path(f"{test_name}.exe"), + Path(f"./{test_name}.exe"), + ] + + test_exe = None + + # 尝试所有可能的路径 + for path in possible_paths: + if path.exists(): + test_exe = path + break + + if test_exe: + print_color(f"\n[TEST] {description}...", Colors.MAGENTA) + print_color("-" * 50, Colors.DARK_GRAY) + + try: + result = subprocess.run( + [str(test_exe)], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + + if result.stdout: + print(result.stdout) + + if result.returncode == 0: + print_success(f"{test_name} passed") + else: + print_error(f"{test_name} failed (exit code: {result.returncode})") + if result.stderr: + print_color(result.stderr, Colors.YELLOW) + + except Exception as e: + print_error(f"{test_name} failed: {e}") + else: + print_warning(f"{test_name}.exe not found. Searched paths:") + for path in possible_paths: + print_color(f" - {path}", Colors.YELLOW) + +def main(): + """主函数""" + parser = argparse.ArgumentParser(description="Fortran CFD Project Builder") + + parser.add_argument( + "--build-type", + choices=["Debug", "Release", "RelWithDebInfo", "MinSizeRel"], + default="Debug", + help="Build type (default: Debug)" + ) + + parser.add_argument( + "--clean", + action="store_true", + help="Clean build directory before building" + ) + + parser.add_argument( + "--no-tests", + action="store_true", + help="Skip running tests" + ) + + parser.add_argument( + "-j", "--jobs", + type=int, + default=0, + help="Number of parallel jobs (0 = use all cores)" + ) + + parser.add_argument( + "--force", + action="store_true", + help="Continue on errors" + ) + + parser.add_argument( + "--verbose", + action="store_true", + help="Verbose output" + ) + + args = parser.parse_args() + + if args.jobs == 0: + import multiprocessing + args.jobs = multiprocessing.cpu_count() + + args.run_tests = not args.no_tests + + try: + success = build_project(args) + if not success and not args.force: + sys.exit(1) + except KeyboardInterrupt: + print_color("\nBuild interrupted by user", Colors.YELLOW) + sys.exit(1) + except Exception as e: + print_error(f"Unexpected error: {e}") + if args.verbose: + import traceback + traceback.print_exc() + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/scripts/requirements.txt b/example/1d-linear-convection/weno3/fortran/registry/01j/scripts/requirements.txt new file mode 100644 index 000000000..d21a12b45 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/scripts/requirements.txt @@ -0,0 +1,2 @@ +# 构建脚本的Python依赖(可选) +# 目前没有特殊依赖,保持空文件或添加未来可能需要的包 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01j/src/CMakeLists.txt new file mode 100644 index 000000000..ee38952bb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/src/CMakeLists.txt @@ -0,0 +1,14 @@ +# ==================== src\CMakeLists.txt ==================== + +# src/CMakeLists.txt +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(core) +add_subdirectory(infrastructure) +add_subdirectory(numerics) + +message(STATUS "源代码目录配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01j/src/core/CMakeLists.txt new file mode 100644 index 000000000..bcfd024d8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/src/core/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/core/CMakeLists.txt +message(STATUS "配置核心模块...") + +add_library(core STATIC + registry.f90 + factory_interfaces.f90 +) + +target_include_directories(core PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 安装规则 +install(TARGETS core + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) +install(FILES + ${CMAKE_Fortran_MODULE_DIRECTORY}/registry_module.mod + ${CMAKE_Fortran_MODULE_DIRECTORY}/factory_interfaces.mod + DESTINATION include/fortran_cfd/core +) + +message(STATUS "核心模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/01j/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..a960167c3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/src/core/factory_interfaces.f90 @@ -0,0 +1,17 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, factory_procedure + + ! Factory procedure interface + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/01j/src/core/registry.f90 new file mode 100644 index 000000000..bf9028c3a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/src/core/registry.f90 @@ -0,0 +1,318 @@ +! src/core/registry.f90 +module registry_module + use, intrinsic :: iso_fortran_env, only: real64 + use factory_interfaces, only: factory_procedure + implicit none + + private + + ! Public interface + public :: real64, component_info, component_registry + public :: register_component_simple, initialize_registry, cleanup_registry + public :: has_component, get_available_components + public :: registry_is_initialized, registry_get_size ! + + ! Type definitions (simplified, no factory for now) + type :: component_info + character(len=32) :: category = "" + character(len=32) :: name = "" + integer :: order = 0 + contains + procedure :: print => ci_print + end type component_info + + type :: component_registry_type + private + type(component_info), allocatable :: components(:) + integer :: count = 0 + integer :: capacity = 100 + logical :: verbose = .true. + logical :: initialized = .false. + contains + procedure :: register => cr_register + procedure :: get => cr_get + procedure :: list_all => cr_list_all + procedure :: clear => cr_clear + procedure :: size => cr_size + procedure :: is_initialized => cr_is_initialized ! + end type component_registry_type + + ! Global registry instance + type(component_registry_type), save :: component_registry + +contains + + ! ==================== PUBLIC API ==================== + + ! Initialize registry + subroutine initialize_registry(initial_capacity, verbose) + integer, optional, intent(in) :: initial_capacity + logical, optional, intent(in) :: verbose + + if (component_registry%initialized) then + if (component_registry%verbose) then + print *, "[INFO] Registry already initialized" + end if + return + end if + + if (present(initial_capacity)) then + component_registry%capacity = max(10, initial_capacity) + end if + + if (present(verbose)) then + component_registry%verbose = verbose + end if + + ! Allocate array + allocate(component_registry%components(component_registry%capacity)) + + component_registry%initialized = .true. + component_registry%count = 0 + + if (component_registry%verbose) then + print *, "[INIT] Registry initialized, capacity:", component_registry%capacity + end if + end subroutine initialize_registry + + ! Cleanup registry + subroutine cleanup_registry + call component_registry%clear() + if (component_registry%verbose) then + print *, "[CLEANUP] Registry cleaned up" + end if + end subroutine cleanup_registry + + ! Simple registration (no factory) + subroutine register_component_simple(category, name) + character(len=*), intent(in) :: category, name + + type(component_info) :: info + + info%category = to_lower(trim(adjustl(category))) + info%name = to_lower(trim(adjustl(name))) + info%order = 0 + + call component_registry%register(info) + end subroutine register_component_simple + + ! Check if component exists + function has_component(category, name) result(found) + character(len=*), intent(in) :: category, name + logical :: found + + type(component_info) :: info + character(len=32) :: cat_lower, name_lower + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + info = component_registry%get(cat_lower, name_lower) + found = (len_trim(info%category) > 0) + end function has_component + + ! Get available components in a category + subroutine get_available_components(category, names, orders) + character(len=*), intent(in) :: category + character(len=:), allocatable, intent(out), optional :: names(:) + integer, allocatable, intent(out), optional :: orders(:) + + character(len=32) :: cat_lower + integer :: i, count, idx + type(component_info) :: info + + cat_lower = to_lower(trim(adjustl(category))) + + ! Count components in this category + count = 0 + do i = 1, component_registry%count + if (component_registry%components(i)%category == cat_lower) then + count = count + 1 + end if + end do + + ! Allocate arrays if requested + if (present(names)) then + allocate(character(len=32) :: names(count)) + end if + + if (present(orders)) then + allocate(orders(count)) + end if + + ! Fill arrays + idx = 1 + do i = 1, component_registry%count + if (component_registry%components(i)%category == cat_lower) then + info = component_registry%components(i) + if (present(names)) then + names(idx) = info%name + end if + if (present(orders)) then + orders(idx) = info%order + end if + idx = idx + 1 + end if + end do + end subroutine get_available_components + + ! Public function to check if registry is initialized + function registry_is_initialized() result(is_initialized) + logical :: is_initialized + is_initialized = component_registry%is_initialized() + end function registry_is_initialized + + ! Public function to get registry size + function registry_get_size() result(size_val) + integer :: size_val + size_val = component_registry%size() + end function registry_get_size + + ! ==================== COMPONENT INFO METHODS ==================== + + subroutine ci_print(this) + class(component_info), intent(in) :: this + + if (this%order > 0) then + print *, " [", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ")]" + else + print *, " [", trim(this%category), ".", trim(this%name), "]" + end if + end subroutine ci_print + + ! ==================== REGISTRY INTERNAL METHODS ==================== + + subroutine cr_register(this, info) + class(component_registry_type), intent(inout) :: this + type(component_info), intent(in) :: info + + type(component_info), allocatable :: temp(:) + integer :: i + + if (.not. this%initialized) then + error stop "[ERROR] Registry not initialized, call initialize_registry first" + end if + + ! Check if already exists + do i = 1, this%count + if (this%components(i)%category == info%category .and. & + this%components(i)%name == info%name) then + if (this%verbose) then + print *, "[WARN] Overwriting: ", & + trim(info%category), ".", trim(info%name) + end if + this%components(i) = info + return + end if + end do + + ! Expand array if needed + if (this%count >= this%capacity) then + this%capacity = this%capacity * 2 + allocate(temp(this%capacity)) + temp(1:this%count) = this%components(1:this%count) + call move_alloc(temp, this%components) + + if (this%verbose) then + print *, "[INFO] Registry expanded to capacity:", this%capacity + end if + end if + + ! Add component + this%count = this%count + 1 + this%components(this%count) = info + + if (this%verbose) then + print *, "[OK] Registered: ", trim(info%category), ".", trim(info%name) + end if + end subroutine cr_register + + function cr_get(this, category, name) result(info) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category, name + type(component_info) :: info + + integer :: i + + ! Initialize return value as empty + info%category = "" + info%name = "" + info%order = 0 + + if (.not. this%initialized) then + return + end if + + do i = 1, this%count + if (this%components(i)%category == category .and. & + this%components(i)%name == name) then + info = this%components(i) + return + end if + end do + end function cr_get + + subroutine cr_list_all(this) + class(component_registry_type), intent(in) :: this + integer :: i + + if (.not. this%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + print *, "=== Registry Contents (", this%count, " components) ===" + + if (this%count == 0) then + print *, " (empty)" + return + end if + + ! Show components grouped by category + do i = 1, this%count + call this%components(i)%print() + end do + + print *, "===========================================" + end subroutine cr_list_all + + subroutine cr_clear(this) + class(component_registry_type), intent(inout) :: this + + if (allocated(this%components)) then + deallocate(this%components) + end if + + this%count = 0 + this%capacity = 100 + this%initialized = .false. + end subroutine cr_clear + + integer function cr_size(this) + class(component_registry_type), intent(in) :: this + cr_size = this%count + end function cr_size + + logical function cr_is_initialized(this) + class(component_registry_type), intent(in) :: this + cr_is_initialized = this%initialized + end function cr_is_initialized + + ! ==================== UTILITY FUNCTIONS ==================== + + function to_lower(str) result(lower_str) + character(len=*), intent(in) :: str + character(len=len(str)) :: lower_str + integer :: i + + do i = 1, len(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + lower_str(i:i) = char(ichar(str(i:i)) + 32) + else + lower_str(i:i) = str(i:i) + end if + end do + end function to_lower + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01j/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..46964ce79 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/src/infrastructure/CMakeLists.txt @@ -0,0 +1,20 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "配置基础设施模块...") + +add_library(infrastructure STATIC + config.f90 + mesh.f90 +) + +target_link_libraries(infrastructure PRIVATE core) + +target_include_directories(infrastructure PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS infrastructure + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "基础设施模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/01j/src/infrastructure/config.f90 new file mode 100644 index 000000000..d3b1e0dfb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/src/infrastructure/config.f90 @@ -0,0 +1,90 @@ +! src/infrastructure/config.f90 +module config_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, cfd_config, config_print, config_with_reconstruction + + ! CFD configuration type + type :: cfd_config + character(len=20) :: ic_type = "step" + character(len=20) :: recon_scheme = "eno" + character(len=20) :: flux_type = "rusanov" + integer :: rk_order = 1 + real(real64) :: wave_speed = 1.0_real64 + real(real64) :: final_time = 0.625_real64 + real(real64) :: dt = 0.025_real64 + character(len=20) :: boundary_type = "periodic" + real(real64) :: left_boundary_value = 1.0_real64 + real(real64) :: right_boundary_value = 2.0_real64 + integer :: spatial_order = 2 + logical :: verbose = .true. + end type cfd_config + +contains + + subroutine config_print(this) + type(cfd_config), intent(in) :: this + + print *, "=== CFD Configuration ===" + print *, "Initial condition: ", trim(this%ic_type) + print *, "Reconstruction: ", trim(this%recon_scheme), " (order:", this%spatial_order, ")" + print *, "Flux type: ", trim(this%flux_type) + print *, "Time integration: RK", this%rk_order + print *, "Wave speed: ", this%wave_speed + print *, "Final time: ", this%final_time + print *, "Time step: ", this%dt + print *, "Boundary: ", trim(this%boundary_type) + if (trim(this%boundary_type) == 'dirichlet') then + print *, " Dirichlet values: [", this%left_boundary_value, ", ", & + this%right_boundary_value, "]" + end if + print *, "===============================" + end subroutine config_print + + subroutine config_with_reconstruction(this, scheme, order) + type(cfd_config), intent(inout) :: this + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + character(len=20) :: scheme_lower + + ! Convert to lowercase + scheme_lower = scheme + call to_lower_inplace(scheme_lower) + this%recon_scheme = trim(adjustl(scheme_lower)) + + ! Set order + if (present(order)) then + this%spatial_order = order + else + ! Smart defaults + if (index(this%recon_scheme, 'weno') > 0) then + this%spatial_order = 5 + else if (trim(this%recon_scheme) == 'eno') then + this%spatial_order = 3 + else + print *, "[ERROR] Unsupported reconstruction scheme: ", trim(this%recon_scheme) + return + end if + end if + + if (this%verbose) then + print *, "[CONFIG] Reconstruction: ", trim(this%recon_scheme), & + " Order: ", this%spatial_order + end if + end subroutine config_with_reconstruction + + subroutine to_lower_inplace(str) + character(len=*), intent(inout) :: str + integer :: i + + do i = 1, len_trim(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + str(i:i) = char(ichar(str(i:i)) + 32) + end if + end do + end subroutine to_lower_inplace + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/01j/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..896969bd0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/src/infrastructure/mesh.f90 @@ -0,0 +1,74 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, mesh_type, mesh_init, mesh_print_info + + ! mesh + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer :: ncells = 40 + integer :: nnodes + integer :: nx + real(wp), allocatable :: x(:) ! + real(wp), allocatable :: xcc(:) ! + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer, optional, intent(in) :: ncells + + integer :: i + + ! Set + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! computation + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! node + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! cell + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== ===" + print *, ": [", this%xmin, ", ", this%xmax, "]" + print *, ": ", this%ncells + print *, ": ", this%nnodes + print *, " dx: ", this%dx + print *, " L: ", this%L + print *, "==========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..66874a7ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/CMakeLists.txt @@ -0,0 +1,7 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..3fde7746b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,28 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +# Start with just the base module +add_library(flux STATIC + base.f90 + rusanov.f90 +) + +#target_link_libraries(flux PRIVATE core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 明确设置不使用 /Qm64 选项 +if(CMAKE_Fortran_COMPILER_ID MATCHES "IntelLLVM|Intel") + set_target_properties(flux PROPERTIES + Fortran_FLAGS "${CMAKE_Fortran_FLAGS} /Qm64" # 明确设置,避免继承问题 + ) +endif() + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/flux/base.f90 new file mode 100644 index 000000000..874396271 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/flux/base.f90 @@ -0,0 +1,23 @@ +module flux_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, flux_calculator_base + + ! Base flux calculator type + type :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure :: info => flux_info ! 这里定义了接口 + end type flux_calculator_base + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "Flux calculator information:" + print *, " Name: ", trim(this%name) + end subroutine flux_info + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/flux/rusanov.f90 b/example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/flux/rusanov.f90 new file mode 100644 index 000000000..859b6ed2b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/flux/rusanov.f90 @@ -0,0 +1,37 @@ +! src/numerics/flux/rusanov.f90 +module rusanov_flux_module + use, intrinsic :: iso_fortran_env, only: real64 + use flux_base_module, only: flux_calculator_base + implicit none + + private + public :: real64, rusanov_flux + + type, extends(flux_calculator_base) :: rusanov_flux + real(real64) :: wave_speed_default = 1.0_real64 + contains + procedure :: info => rusanov_info + end type rusanov_flux + + ! 添加构造函数接口 + interface rusanov_flux + module procedure create_rusanov_flux + end interface + +contains + + ! 构造函数 + type(rusanov_flux) function create_rusanov_flux() result(this) + this%name = "Rusanov" + this%wave_speed_default = 1.0_real64 + end function create_rusanov_flux + + subroutine rusanov_info(this) + class(rusanov_flux), intent(in) :: this + ! 必须调用父类的info方法 + call flux_info(this) ! 这会调用base模块中的flux_info + print *, " Type: Rusanov flux" + print *, " Default wave speed: ", this%wave_speed_default + end subroutine rusanov_info + +end module rusanov_flux_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..5e4b938d5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,22 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "Configuring reconstructor module...") + +# Start with just the base module +add_library(reconstructor STATIC + base.f90 + eno.f90 + weno3.f90 +) + +target_link_libraries(reconstructor PRIVATE core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Reconstructor module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..8c3372864 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/reconstructor/base.f90 @@ -0,0 +1,26 @@ +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, reconstructor_base + + ! Base reconstructor type + type :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + contains + procedure :: info => reconstructor_info ! 这里定义了接口 + end type reconstructor_base + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + + print *, "Reconstructor information:" + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_info + +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/reconstructor/eno.f90 b/example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/reconstructor/eno.f90 new file mode 100644 index 000000000..fe54d5c37 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/reconstructor/eno.f90 @@ -0,0 +1,38 @@ +! src/numerics/reconstructor/eno.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, eno_reconstructor + + type, extends(reconstructor_base) :: eno_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => eno_info + end type eno_reconstructor + + ! 必须添加构造函数接口 + interface eno_reconstructor + module procedure create_eno_reconstructor + end interface + +contains + + ! 构造函数实现 + type(eno_reconstructor) function create_eno_reconstructor() result(this) + this%name = "ENO" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_eno_reconstructor + + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + ! 必须调用父类的info方法 + call reconstructor_info(this) ! 这会调用base模块中的reconstructor_info + print *, " Type: ENO reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/reconstructor/weno3.f90 b/example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/reconstructor/weno3.f90 new file mode 100644 index 000000000..a1f2a3293 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/src/numerics/reconstructor/weno3.f90 @@ -0,0 +1,55 @@ +! src/numerics/reconstructor/weno3.f90 +module weno3_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno3_reconstructor + + type, extends(reconstructor_base) :: weno3_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno3_info + end type weno3_reconstructor + + ! Add + interface weno3_reconstructor + module procedure create_weno3_reconstructor + end interface + +contains + + ! + type(weno3_reconstructor) function create_weno3_reconstructor(name, order, epsilon) result(this) + character(len=*), optional, intent(in) :: name + integer, optional, intent(in) :: order + real(real64), optional, intent(in) :: epsilon + + if (present(name)) then + this%name = name + else + this%name = "WENO3" + end if + + if (present(order)) then + this%order = order + else + this%order = 3 + end if + + if (present(epsilon)) then + this%epsilon = epsilon + else + this%epsilon = 1.0e-6_real64 + end if + end function create_weno3_reconstructor + + subroutine weno3_info(this) + class(weno3_reconstructor), intent(in) :: this + call reconstructor_info(this) + print *, " Type: WENO-3 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno3_info + +end module weno3_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01j/tests/CMakeLists.txt new file mode 100644 index 000000000..ac77721ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/tests/CMakeLists.txt @@ -0,0 +1,34 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +add_executable(test_minimal_simple test_minimal_simple.f90) + +message(STATUS "CMAKE_Fortran_MODULE_DIRECTORY=${CMAKE_Fortran_MODULE_DIRECTORY}") + +#target_include_directories( test_minimal_simple +# PRIVATE +# ${CMAKE_Fortran_MODULE_DIRECTORY} +#) + +target_link_libraries( test_minimal_simple + PRIVATE + infrastructure +) + +add_executable(test_simple_link test_simple_link.f90) +target_link_libraries(test_simple_link + PRIVATE + reconstructor + flux +) + + +#add_executable(test_factory_simple test_factory_simple.f90) + +#target_link_libraries( test_factory_simple +# PRIVATE +# core +# infrastructure +# reconstructor +# flux +#) diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/tests/test_factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/01j/tests/test_factory.f90 new file mode 100644 index 000000000..c62d5f8ab --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/tests/test_factory.f90 @@ -0,0 +1,154 @@ +! tests/test_factory.f90 +program test_factory + use registry_module + use config_module + use mesh_module + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + class(*), allocatable :: instance + class(reconstructor_base), allocatable :: recon + class(flux_calculator_base), allocatable :: flux_calc + real(wp), allocatable :: q(:), qL(:), qR(:), flux(:) + integer :: i, n + + print *, "=== Factory Pattern Test ===" + print *, "" + + ! Initialize systems + call initialize_registry(verbose=.true.) + + ! Register components with factories + print *, "1. Registering components with factories..." + call register_component_with_factory("reconstructor", "eno", create_eno, 3) + call register_component_with_factory("reconstructor", "weno3", create_weno3, 3) + call register_component_with_factory("flux", "rusanov", create_rusanov) + + call component_registry%list_all() + print *, "" + + ! Test creating ENO reconstructor + print *, "2. Creating ENO reconstructor..." + call create_component("reconstructor", "eno", instance) + + if (allocated(instance)) then + select type (inst => instance) + type is (eno_reconstructor) + allocate(recon, source=inst) + print *, "ENO reconstructor created successfully" + call recon%info() + print *, "" + + ! Test reconstruction + n = 10 + allocate(q(0:n+1), qL(n), qR(n)) + + ! Initialize test data (sine wave) + do i = 0, n+1 + q(i) = sin(2.0_wp * 3.141592653589793_wp * real(i-1, wp) / real(n, wp)) + end do + + print *, "Testing ENO reconstruction..." + call recon%reconstruct(q, qL, qR) + + print *, "q (internal):" + do i = 1, n + write(*, '(I3, F10.6)') i, q(i) + end do + + print *, "qL (left interface values):" + do i = 1, n + write(*, '(I3, F10.6)') i, qL(i) + end do + + deallocate(q, qL, qR) + class default + print *, "[ERROR] Wrong type for ENO reconstructor" + end select + else + print *, "[ERROR] Failed to create ENO reconstructor" + end if + print *, "" + + ! Test creating WENO3 reconstructor + print *, "3. Creating WENO3 reconstructor..." + if (allocated(instance)) deallocate(instance) + call create_component("reconstructor", "weno3", instance) + + if (allocated(instance)) then + select type (inst => instance) + type is (weno3_reconstructor) + if (allocated(recon)) deallocate(recon) + allocate(recon, source=inst) + print *, "WENO3 reconstructor created successfully" + call recon%info() + class default + print *, "[ERROR] Wrong type for WENO3 reconstructor" + end select + else + print *, "[ERROR] Failed to create WENO3 reconstructor" + end if + print *, "" + + ! Test creating Rusanov flux calculator + print *, "4. Creating Rusanov flux calculator..." + if (allocated(instance)) deallocate(instance) + call create_component("flux", "rusanov", instance) + + if (allocated(instance)) then + select type (inst => instance) + type is (rusanov_flux) + allocate(flux_calc, source=inst) + print *, "Rusanov flux calculator created successfully" + call flux_calc%info() + print *, "" + + ! Test flux computation + n = 5 + allocate(qL(n), qR(n), flux(n)) + + ! Initialize test data + do i = 1, n + qL(i) = 1.0_wp + 0.1_wp * real(i-1, wp) + qR(i) = 1.0_wp + 0.1_wp * real(i, wp) + end do + + print *, "Testing Rusanov flux computation..." + call flux_calc%compute(qL, qR, flux, 1.0_wp) + + print *, "qL:" + do i = 1, n + write(*, '(I3, F10.6)') i, qL(i) + end do + + print *, "qR:" + do i = 1, n + write(*, '(I3, F10.6)') i, qR(i) + end do + + print *, "Flux:" + do i = 1, n + write(*, '(I3, F10.6)') i, flux(i) + end do + + deallocate(qL, qR, flux) + class default + print *, "[ERROR] Wrong type for Rusanov flux" + end select + else + print *, "[ERROR] Failed to create Rusanov flux calculator" + end if + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Factory pattern test completed successfully ===" + +end program test_factory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/tests/test_factory_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/01j/tests/test_factory_simple.f90 new file mode 100644 index 000000000..2a4650d4b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/tests/test_factory_simple.f90 @@ -0,0 +1,72 @@ +program test_factory_simple + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module, only: initialize_registry, cleanup_registry, & + register_component_simple, has_component + use config_module, only: cfd_config, config_print + use mesh_module, only: mesh_type + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(eno_reconstructor) :: eno + type(weno3_reconstructor) :: weno3 + type(rusanov_flux) :: rusanov + + print *, "=== Factory Pattern Simple Test ===" + print *, "" + + ! Test 1: Basic systems + print *, "1. Testing basic systems..." + call config_print(config) + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 2: Reconstructors - 必须调用info方法 + print *, "2. Testing reconstructors..." + print *, "Creating ENO reconstructor..." + + ! 创建对象 + eno = eno_reconstructor() + + ! 必须调用info方法,否则链接器会认为不需要这些符号 + call eno%info() + + print *, "Creating WENO3 reconstructor..." + weno3 = weno3_reconstructor() + call weno3%info() + print *, "" + + ! Test 3: Flux calculator + print *, "3. Testing flux calculator..." + rusanov = rusanov_flux() + call rusanov%info() + print *, "" + + ! Test 4: Registry + print *, "4. Testing registry..." + call initialize_registry(verbose=.true.) + + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("flux", "rusanov") + + if (has_component("reconstructor", "eno")) then + print *, "[OK] ENO reconstructor registered successfully" + end if + + if (has_component("flux", "rusanov")) then + print *, "[OK] Rusanov flux registered successfully" + end if + + print *, "" + call cleanup_registry() + + print *, "=== Factory pattern simple test completed successfully ===" + +end program test_factory_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/tests/test_factory_simpleBAK.f90 b/example/1d-linear-convection/weno3/fortran/registry/01j/tests/test_factory_simpleBAK.f90 new file mode 100644 index 000000000..07a035473 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/tests/test_factory_simpleBAK.f90 @@ -0,0 +1,106 @@ +! tests/test_factory_simple.f90 +program test_factory_simple + use registry_module + use config_module + use mesh_module + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(eno_reconstructor) :: eno + type(weno3_reconstructor) :: weno3 + type(rusanov_flux) :: rusanov + integer :: i + + print *, "=== Factory Pattern Simple Test ===" + print *, "" + + ! Test 1: Configuration and mesh + print *, "1. Testing basic systems..." + print *, "-----------------------------" + call config_print(config) + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 2: Creating reconstructors directly + print *, "2. Creating reconstructors..." + print *, "------------------------------" + eno = eno_reconstructor(name="ENO", order=3, epsilon=1.0e-6_wp) + weno3 = weno3_reconstructor(name="WENO3", order=3, epsilon=1.0e-6_wp) + + call eno%info() + call weno3%info() + print *, "" + + ! Test 3: Creating flux calculator directly + print *, "3. Creating flux calculator..." + print *, "-------------------------------" + rusanov = rusanov_flux(name="Rusanov", wave_speed_default=1.0_wp) + call rusanov%info() + print *, "" + + ! Test 4: Registry integration + print *, "4. Testing registry..." + print *, "----------------------" + call initialize_registry(verbose=.true.) + + ! Register with simple method + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("flux", "rusanov") + + ! Check if registered + if (has_component("reconstructor", "eno")) then + print *, "[OK] ENO reconstructor registered successfully" + else + print *, "[ERROR] ENO reconstructor registration failed" + end if + + if (has_component("flux", "rusanov")) then + print *, "[OK] Rusanov flux registered successfully" + else + print *, "[ERROR] Rusanov flux registration failed" + end if + + print *, "" + call component_registry%list_all() + print *, "" + + ! Test getting available components + print *, "5. Testing component listing..." + print *, "--------------------------------" + call test_component_listing() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Factory pattern simple test completed successfully ===" + +contains + + subroutine test_component_listing() + character(len=:), allocatable :: recon_names(:) + integer, allocatable :: recon_orders(:) + integer :: i + + print *, "Available reconstructors:" + call get_available_components("reconstructor", recon_names, recon_orders) + + if (allocated(recon_names)) then + do i = 1, size(recon_names) + print *, " - ", trim(recon_names(i)) + end do + print *, "Total reconstructors: ", size(recon_names) + else + print *, " (no reconstructors found)" + end if + end subroutine test_component_listing + +end program test_factory_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/tests/test_minimal.f90 b/example/1d-linear-convection/weno3/fortran/registry/01j/tests/test_minimal.f90 new file mode 100644 index 000000000..807d0bc2c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/tests/test_minimal.f90 @@ -0,0 +1,108 @@ +! tests/test_minimal.f90 +program test_minimal + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i ! Declare loop variable here + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", component_registry%size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components + print *, "5. Testing available components" + print *, "--------------------------------" + + ! Use a separate block to avoid allocation issues + call test_available_components() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Minimal test completed successfully ===" + +contains + + ! Internal subroutine for testing available components + subroutine test_available_components() + character(len=:), allocatable :: names(:) + integer, allocatable :: orders(:) + integer :: j + + call get_available_components("reconstructor", names, orders) + + if (allocated(names)) then + print *, "Reconstructors:" + do j = 1, size(names) + print *, " - ", trim(names(j)) + if (allocated(orders)) then + print *, " Order: ", orders(j) + end if + end do + else + print *, "No reconstructors found" + end if + end subroutine test_available_components + +end program test_minimal \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/tests/test_minimal_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/01j/tests/test_minimal_simple.f90 new file mode 100644 index 000000000..6213e8f89 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/tests/test_minimal_simple.f90 @@ -0,0 +1,84 @@ +! tests/test_minimal_simple.f90 +program test_minimal_simple + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Simple Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components (simple version) + print *, "5. Testing registry functionality" + print *, "---------------------------------" + print *, "Registry initialized: ", registry_is_initialized() + print *, "Number of components: ", registry_get_size() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Simple test completed successfully ===" + +end program test_minimal_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/tests/test_minimal_simpleBAK.f90 b/example/1d-linear-convection/weno3/fortran/registry/01j/tests/test_minimal_simpleBAK.f90 new file mode 100644 index 000000000..6213e8f89 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/tests/test_minimal_simpleBAK.f90 @@ -0,0 +1,84 @@ +! tests/test_minimal_simple.f90 +program test_minimal_simple + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Simple Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components (simple version) + print *, "5. Testing registry functionality" + print *, "---------------------------------" + print *, "Registry initialized: ", registry_is_initialized() + print *, "Number of components: ", registry_get_size() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Simple test completed successfully ===" + +end program test_minimal_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01j/tests/test_simple_link.f90 b/example/1d-linear-convection/weno3/fortran/registry/01j/tests/test_simple_link.f90 new file mode 100644 index 000000000..807d0bc2c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01j/tests/test_simple_link.f90 @@ -0,0 +1,108 @@ +! tests/test_minimal.f90 +program test_minimal + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i ! Declare loop variable here + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", component_registry%size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components + print *, "5. Testing available components" + print *, "--------------------------------" + + ! Use a separate block to avoid allocation issues + call test_available_components() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Minimal test completed successfully ===" + +contains + + ! Internal subroutine for testing available components + subroutine test_available_components() + character(len=:), allocatable :: names(:) + integer, allocatable :: orders(:) + integer :: j + + call get_available_components("reconstructor", names, orders) + + if (allocated(names)) then + print *, "Reconstructors:" + do j = 1, size(names) + print *, " - ", trim(names(j)) + if (allocated(orders)) then + print *, " Order: ", orders(j) + end if + end do + else + print *, "No reconstructors found" + end if + end subroutine test_available_components + +end program test_minimal \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01k/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01k/CMakeLists.txt new file mode 100644 index 000000000..ef66d5848 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01k/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01k/README.md b/example/1d-linear-convection/weno3/fortran/registry/01k/README.md new file mode 100644 index 000000000..c4889757b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01k/README.md @@ -0,0 +1,221 @@ +# Fortran CFD Project + +基于工厂模式和注册系统的CFD求解器框架。 + +## 项目结构 + +``` +fortran_cfd/ +├── CMakeLists.txt # 根CMake配置 +├── scripts/ # 构建脚本 +│ ├── build.py # Python构建脚本(推荐) +│ ├── build.bat # Batch包装脚本 +│ ├── build.ps1 # PowerShell包装脚本 +│ └── requirements.txt # Python依赖 +├── src/ # 源代码 +│ ├── CMakeLists.txt +│ ├── core/ # 核心模块(注册系统、工厂接口) +│ ├── infrastructure/ # 基础设施(配置、网格) +│ └── numerics/ # 数值方法 +├── tests/ # 测试 +│ ├── CMakeLists.txt +│ ├── test_minimal_simple.f90 # 简单测试 +│ └── test_factory.f90 # 工厂模式测试 +└── README.md # 项目说明(本文件) +``` + +## 快速开始 + +### 使用Python脚本(推荐) + +```bash +# 进入脚本目录 +cd scripts + +# 基本构建 +python build.py + +# 清理并构建 +python build.py --clean + +# 发布构建 +python build.py --build-type Release --clean + +# 并行构建(使用所有核心) +python build.py -j + +# 不运行测试 +python build.py --no-tests + +# 查看帮助 +python build.py --help +``` + +### 使用批处理脚本 + +```bash +cd scripts +.\build.bat +``` + +### 使用PowerShell脚本 + +```powershell +cd scripts +.\build.ps1 +``` + +## 手动构建 + +```bash +# 设置Intel环境 +cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && powershell' + +# 配置项目 +mkdir build +cd build +cmake -G "Visual Studio 17 2022" -A x64 -T fortran=ifx .. + +# 构建项目 +cmake --build . --config Debug + +# 运行测试 +Debug\test_simple.exe +Debug\test_factory.exe +``` + +## 功能特性 + +✅ 组件注册系统 +✅ 工厂模式 +✅ ENO/WENO重构器 +✅ Rusanov通量计算器 +✅ 可扩展架构 +✅ 自动化测试 + +## 依赖 + +- Intel oneAPI(包含ifx编译器) +- CMake 3.12+ +- Python 3.6+(仅用于构建脚本) + +## 源代码文件 + +### 核心模块 +- `src/core/registry.f90` - 组件注册系统 +- `src/core/factory_interfaces.f90` - 工厂接口 + +### 基础设施 +- `src/infrastructure/config.f90` - 配置管理 +- `src/infrastructure/mesh.f90` - 网格生成 + +### 数值方法 +- `src/numerics/reconstructor/base.f90` - 重构器基类 +- `src/numerics/reconstructor/eno.f90` - ENO重构器 +- `src/numerics/reconstructor/weno3.f90` - WENO-3重构器 +- `src/numerics/flux/base.f90` - 通量计算器基类 +- `src/numerics/flux/rusanov.f90` - Rusanov通量 + +### 测试 +- `tests/test_minimal_simple.f90` - 简单功能测试 +- `tests/test_factory.f90` - 工厂模式测试 + +## 构建脚本 + +### Python脚本 (build.py) +主构建脚本,支持: +- 自动设置Intel oneAPI环境 +- 参数化构建选项 +- 并行编译 +- 自动化测试 +- 彩色输出 + +### Batch脚本 (build.bat) +Windows批处理包装器,调用Python脚本。 + +### PowerShell脚本 (build.ps1) +PowerShell包装器,调用Python脚本。 + +## 示例输出 + +成功构建后,会看到类似输出: + +``` +============================================================ + Fortran CFD Project Builder +============================================================ + +[1/4] Setting up Intel Fortran compiler... +✓ Intel oneAPI environment configured + +[2/4] Preparing build directory... +✓ Cleaned build directory + +[3/4] Configuring project... + $ cmake -G Visual Studio 17 2022 -A x64 -T fortran=ifx -DCMAKE_BUILD_TYPE=Debug .. +✓ CMake configuration successful + +[4/4] Building project... + $ cmake --build . --config Debug +✓ Build completed in 12.3 seconds + +============================================================ + Running Tests +============================================================ + +[TEST] Simple functionality test... +✓ Simple test passed + +[TEST] Factory pattern test... +✓ Factory test passed + +============================================================ + Build Summary +============================================================ +✓ Build directory: D:\path\to\build +✓ Build type: Debug +✓ Total time: 15.2s +``` + +## 开发指南 + +### 添加新组件 + +1. 在相应模块中创建Fortran源文件 +2. 实现工厂函数 +3. 使用`register_component_with_factory`注册组件 +4. 添加测试 + +### 扩展架构 + +项目采用模块化设计: +1. **注册系统** - 管理所有可用的组件 +2. **工厂模式** - 动态创建组件实例 +3. **抽象接口** - 确保组件兼容性 +4. **配置驱动** - 通过配置文件控制行为 + +## 故障排除 + +### 常见问题 + +1. **Intel环境未找到** + - 检查Intel oneAPI安装路径 + - 手动运行`setvars.bat` + +2. **CMake配置失败** + - 确保使用正确的生成器:`Visual Studio 17 2022` + - 检查Fortran编译器是否可用 + +3. **构建失败** + - 检查依赖项是否完整 + - 查看详细的错误信息 + +### 调试建议 + +- 使用`--verbose`参数获取详细输出 +- 检查`build/CMakeCache.txt`了解配置详情 +- 查看编译器错误日志 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01k/scripts/build.bat b/example/1d-linear-convection/weno3/fortran/registry/01k/scripts/build.bat new file mode 100644 index 000000000..d243695b0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01k/scripts/build.bat @@ -0,0 +1,26 @@ +@echo off +echo ======================================== +echo Fortran CFD Project Builder +echo (Wrapper for Python script) +echo ======================================== +echo. + +REM 检查Python +where python >nul 2>nul +if %errorlevel% neq 0 ( + echo [ERROR] Python not found. Please install Python 3. + pause + exit /b 1 +) + +REM 运行Python构建脚本 +echo [INFO] Running Python build script... +python build.py %* + +if %errorlevel% neq 0 ( + echo [ERROR] Build failed + pause + exit /b 1 +) + +pause \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01k/scripts/build.ps1 b/example/1d-linear-convection/weno3/fortran/registry/01k/scripts/build.ps1 new file mode 100644 index 000000000..4497c5ebd --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01k/scripts/build.ps1 @@ -0,0 +1,32 @@ +# Fortran CFD Project Builder (PowerShell wrapper) + +Write-Host "========================================" -ForegroundColor Cyan +Write-Host " Fortran CFD Project Builder" -ForegroundColor Cyan +Write-Host " (PowerShell wrapper for Python script)" -ForegroundColor Cyan +Write-Host "========================================" -ForegroundColor Cyan +Write-Host "" + +# 检查Python +$python = Get-Command python -ErrorAction SilentlyContinue +if (-not $python) { + $python = Get-Command python3 -ErrorAction SilentlyContinue +} + +if (-not $python) { + Write-Host "[ERROR] Python not found. Please install Python 3." -ForegroundColor Red + Read-Host "Press Enter to exit" + exit 1 +} + +# 运行Python构建脚本 +Write-Host "[INFO] Running Python build script..." -ForegroundColor Yellow +$argsString = $args -join ' ' +$command = "python build.py $argsString" + +Invoke-Expression $command + +if ($LASTEXITCODE -ne 0) { + Write-Host "[ERROR] Build failed" -ForegroundColor Red + Read-Host "Press Enter to exit" + exit 1 +} \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01k/scripts/build.py b/example/1d-linear-convection/weno3/fortran/registry/01k/scripts/build.py new file mode 100644 index 000000000..1c99c2c87 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01k/scripts/build.py @@ -0,0 +1,357 @@ +#!/usr/bin/env python3 +""" +Fortran CFD 项目构建脚本 (Python版) +支持 Intel oneAPI 环境的自动化构建 +""" + +import os +import sys +import subprocess +import shutil +from pathlib import Path +import argparse +import platform +import time + +class Colors: + """终端颜色""" + if platform.system() == "Windows": + # Windows 启用 ANSI + os.system("") + + HEADER = '\033[95m' + BLUE = '\033[94m' + CYAN = '\033[96m' + GREEN = '\033[92m' + YELLOW = '\033[93m' + RED = '\033[91m' + MAGENTA = '\033[95m' + DARK_GRAY = '\033[90m' # 添加这个 + END = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + +def print_color(text, color=Colors.END): + """彩色打印""" + print(f"{color}{text}{Colors.END}") + +def print_header(text): + """打印标题""" + print_color("\n" + "="*60, Colors.CYAN) + print_color(f" {text}", Colors.BOLD + Colors.CYAN) + print_color("="*60 + "\n", Colors.CYAN) + +def print_step(step, total, message): + """打印步骤""" + print_color(f"[{step}/{total}] {message}...", Colors.YELLOW) + +def print_success(message): + """打印成功""" + print_color(f"✓ {message}", Colors.GREEN) + +def print_error(message): + """打印错误""" + print_color(f"✗ {message}", Colors.RED) + +def print_warning(message): + """打印警告""" + print_color(f"! {message}", Colors.YELLOW) + +def run_command(cmd, cwd=None, check=True, capture=True): + """运行命令""" + print_color(f" $ {' '.join(cmd)}", Colors.BLUE) + + try: + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=capture, + text=True, + encoding='utf-8', + errors='ignore', + shell=False + ) + + if capture and result.stdout: + print(result.stdout) + if capture and result.stderr: + print_color(result.stderr, Colors.YELLOW) + + if check and result.returncode != 0: + print_error(f"Command failed with exit code: {result.returncode}") + return False + + return True + + except FileNotFoundError as e: + print_error(f"Command not found: {cmd[0]}") + if check: + raise + return False + except Exception as e: + print_error(f"Command execution failed: {e}") + return False + +def setup_intel_environment(): + """设置 Intel oneAPI 环境""" + setvars_paths = [ + r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat", + r"C:\Program Files\Intel\oneAPI\setvars.bat", + ] + + setvars_path = None + for path in setvars_paths: + if os.path.exists(path): + setvars_path = path + break + + if not setvars_path: + print_error("Intel oneAPI setvars.bat not found.") + print_warning("Please install Intel oneAPI or update the path in build.py") + return False + + # 创建临时的 batch 文件 + temp_bat = "temp_setvars.bat" + with open(temp_bat, 'w') as f: + f.write(f'@echo off\n') + f.write(f'call "{setvars_path}" > nul 2>&1\n') + f.write(f'set\n') + + try: + # 运行并捕获环境变量 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + shell=True, + encoding='utf-8', + errors='ignore' + ) + + # 解析并设置环境变量 + for line in result.stdout.split('\n'): + if '=' in line: + key, value = line.split('=', 1) + os.environ[key.strip()] = value.strip() + + os.remove(temp_bat) + print_success("Intel oneAPI environment configured") + return True + + except Exception as e: + print_error(f"Failed to setup Intel environment: {e}") + if os.path.exists(temp_bat): + os.remove(temp_bat) + return False + +def build_project(args): + """构建项目主函数""" + start_time = time.time() + + print_header(f"Fortran CFD Project Builder") + print_color(f"Build type: {args.build_type}", Colors.CYAN) + print_color(f"Run tests: {args.run_tests}", Colors.CYAN) + print() + + # 获取项目根目录(脚本所在目录的父目录) + script_dir = Path(__file__).parent + project_root = script_dir.parent + os.chdir(project_root) + + print_color(f"Project root: {project_root}", Colors.BLUE) + + # 步骤1: 设置 Intel 环境 + print_step(1, 4, "Setting up Intel Fortran compiler") + if not setup_intel_environment(): + return False + + # 步骤2: 准备构建目录 + print_step(2, 4, "Preparing build directory") + build_dir = project_root / "build" + + if args.clean and build_dir.exists(): + try: + shutil.rmtree(build_dir) + print_success("Cleaned build directory") + except Exception as e: + print_error(f"Failed to clean build directory: {e}") + if not args.force: + return False + + build_dir.mkdir(exist_ok=True) + os.chdir(build_dir) + + # 步骤3: 配置项目 + print_step(3, 4, "Configuring project") + + cmake_cmd = [ + "cmake", + "-G", "Visual Studio 17 2022", + "-A", "x64", + "-T", "fortran=ifx", + f"-DCMAKE_BUILD_TYPE={args.build_type}", + ".." + ] + + if not run_command(cmake_cmd, check=not args.force): + if not args.force: + return False + + # 步骤4: 构建项目 + print_step(4, 4, "Building project") + + build_cmd = [ + "cmake", + "--build", ".", + "--config", args.build_type, + f"-j{args.jobs}" if args.jobs > 1 else "" + ] + + print(f"build_cmd={build_cmd}") + build_cmd = [c for c in build_cmd if c] # 移除空字符串 + + if not run_command(build_cmd, check=not args.force): + if not args.force: + return False + + build_time = time.time() - start_time + print_success(f"Build completed in {build_time:.1f} seconds") + + # 运行测试 + if args.run_tests: + print_header("Running Tests") + run_tests(args.build_type) + + print_header("Build Summary") + print_color(f"Build directory: {build_dir}", Colors.GREEN) + print_color(f"Build type: {args.build_type}", Colors.GREEN) + print_color(f"Total time: {build_time:.1f}s", Colors.GREEN) + + return True + +def run_tests(build_type): + """运行测试""" + tests = [ + ("test_simple", "Simple functionality test"), + ("test_factory", "Factory pattern test"), + ] + + for test_name, description in tests: + # 修复:在这里直接访问当前循环的 test_name + _run_single_test(test_name, description, build_type) + +def _run_single_test(test_name, description, build_type): + """运行单个测试(修复作用域问题)""" + # 可能的测试可执行文件路径 + possible_paths = [ + Path(f"./{build_type}/{test_name}.exe"), + Path(f"./tests/{build_type}/{test_name}.exe"), + Path(f"./tests/{test_name}.exe"), + Path(f"{test_name}.exe"), + Path(f"./{test_name}.exe"), + ] + + test_exe = None + + # 尝试所有可能的路径 + for path in possible_paths: + if path.exists(): + test_exe = path + break + + if test_exe: + print_color(f"\n[TEST] {description}...", Colors.MAGENTA) + print_color("-" * 50, Colors.DARK_GRAY) + + try: + result = subprocess.run( + [str(test_exe)], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + + if result.stdout: + print(result.stdout) + + if result.returncode == 0: + print_success(f"{test_name} passed") + else: + print_error(f"{test_name} failed (exit code: {result.returncode})") + if result.stderr: + print_color(result.stderr, Colors.YELLOW) + + except Exception as e: + print_error(f"{test_name} failed: {e}") + else: + print_warning(f"{test_name}.exe not found. Searched paths:") + for path in possible_paths: + print_color(f" - {path}", Colors.YELLOW) + +def main(): + """主函数""" + parser = argparse.ArgumentParser(description="Fortran CFD Project Builder") + + parser.add_argument( + "--build-type", + choices=["Debug", "Release", "RelWithDebInfo", "MinSizeRel"], + default="Debug", + help="Build type (default: Debug)" + ) + + parser.add_argument( + "--clean", + action="store_true", + help="Clean build directory before building" + ) + + parser.add_argument( + "--no-tests", + action="store_true", + help="Skip running tests" + ) + + parser.add_argument( + "-j", "--jobs", + type=int, + default=0, + help="Number of parallel jobs (0 = use all cores)" + ) + + parser.add_argument( + "--force", + action="store_true", + help="Continue on errors" + ) + + parser.add_argument( + "--verbose", + action="store_true", + help="Verbose output" + ) + + args = parser.parse_args() + + if args.jobs == 0: + import multiprocessing + args.jobs = multiprocessing.cpu_count() + + args.run_tests = not args.no_tests + + try: + success = build_project(args) + if not success and not args.force: + sys.exit(1) + except KeyboardInterrupt: + print_color("\nBuild interrupted by user", Colors.YELLOW) + sys.exit(1) + except Exception as e: + print_error(f"Unexpected error: {e}") + if args.verbose: + import traceback + traceback.print_exc() + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01k/scripts/requirements.txt b/example/1d-linear-convection/weno3/fortran/registry/01k/scripts/requirements.txt new file mode 100644 index 000000000..d21a12b45 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01k/scripts/requirements.txt @@ -0,0 +1,2 @@ +# 构建脚本的Python依赖(可选) +# 目前没有特殊依赖,保持空文件或添加未来可能需要的包 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01k/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01k/src/CMakeLists.txt new file mode 100644 index 000000000..ee38952bb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01k/src/CMakeLists.txt @@ -0,0 +1,14 @@ +# ==================== src\CMakeLists.txt ==================== + +# src/CMakeLists.txt +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(core) +add_subdirectory(infrastructure) +add_subdirectory(numerics) + +message(STATUS "源代码目录配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01k/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01k/src/core/CMakeLists.txt new file mode 100644 index 000000000..bcfd024d8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01k/src/core/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/core/CMakeLists.txt +message(STATUS "配置核心模块...") + +add_library(core STATIC + registry.f90 + factory_interfaces.f90 +) + +target_include_directories(core PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 安装规则 +install(TARGETS core + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) +install(FILES + ${CMAKE_Fortran_MODULE_DIRECTORY}/registry_module.mod + ${CMAKE_Fortran_MODULE_DIRECTORY}/factory_interfaces.mod + DESTINATION include/fortran_cfd/core +) + +message(STATUS "核心模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01k/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/01k/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..a960167c3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01k/src/core/factory_interfaces.f90 @@ -0,0 +1,17 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, factory_procedure + + ! Factory procedure interface + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01k/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/01k/src/core/registry.f90 new file mode 100644 index 000000000..bf9028c3a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01k/src/core/registry.f90 @@ -0,0 +1,318 @@ +! src/core/registry.f90 +module registry_module + use, intrinsic :: iso_fortran_env, only: real64 + use factory_interfaces, only: factory_procedure + implicit none + + private + + ! Public interface + public :: real64, component_info, component_registry + public :: register_component_simple, initialize_registry, cleanup_registry + public :: has_component, get_available_components + public :: registry_is_initialized, registry_get_size ! + + ! Type definitions (simplified, no factory for now) + type :: component_info + character(len=32) :: category = "" + character(len=32) :: name = "" + integer :: order = 0 + contains + procedure :: print => ci_print + end type component_info + + type :: component_registry_type + private + type(component_info), allocatable :: components(:) + integer :: count = 0 + integer :: capacity = 100 + logical :: verbose = .true. + logical :: initialized = .false. + contains + procedure :: register => cr_register + procedure :: get => cr_get + procedure :: list_all => cr_list_all + procedure :: clear => cr_clear + procedure :: size => cr_size + procedure :: is_initialized => cr_is_initialized ! + end type component_registry_type + + ! Global registry instance + type(component_registry_type), save :: component_registry + +contains + + ! ==================== PUBLIC API ==================== + + ! Initialize registry + subroutine initialize_registry(initial_capacity, verbose) + integer, optional, intent(in) :: initial_capacity + logical, optional, intent(in) :: verbose + + if (component_registry%initialized) then + if (component_registry%verbose) then + print *, "[INFO] Registry already initialized" + end if + return + end if + + if (present(initial_capacity)) then + component_registry%capacity = max(10, initial_capacity) + end if + + if (present(verbose)) then + component_registry%verbose = verbose + end if + + ! Allocate array + allocate(component_registry%components(component_registry%capacity)) + + component_registry%initialized = .true. + component_registry%count = 0 + + if (component_registry%verbose) then + print *, "[INIT] Registry initialized, capacity:", component_registry%capacity + end if + end subroutine initialize_registry + + ! Cleanup registry + subroutine cleanup_registry + call component_registry%clear() + if (component_registry%verbose) then + print *, "[CLEANUP] Registry cleaned up" + end if + end subroutine cleanup_registry + + ! Simple registration (no factory) + subroutine register_component_simple(category, name) + character(len=*), intent(in) :: category, name + + type(component_info) :: info + + info%category = to_lower(trim(adjustl(category))) + info%name = to_lower(trim(adjustl(name))) + info%order = 0 + + call component_registry%register(info) + end subroutine register_component_simple + + ! Check if component exists + function has_component(category, name) result(found) + character(len=*), intent(in) :: category, name + logical :: found + + type(component_info) :: info + character(len=32) :: cat_lower, name_lower + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + info = component_registry%get(cat_lower, name_lower) + found = (len_trim(info%category) > 0) + end function has_component + + ! Get available components in a category + subroutine get_available_components(category, names, orders) + character(len=*), intent(in) :: category + character(len=:), allocatable, intent(out), optional :: names(:) + integer, allocatable, intent(out), optional :: orders(:) + + character(len=32) :: cat_lower + integer :: i, count, idx + type(component_info) :: info + + cat_lower = to_lower(trim(adjustl(category))) + + ! Count components in this category + count = 0 + do i = 1, component_registry%count + if (component_registry%components(i)%category == cat_lower) then + count = count + 1 + end if + end do + + ! Allocate arrays if requested + if (present(names)) then + allocate(character(len=32) :: names(count)) + end if + + if (present(orders)) then + allocate(orders(count)) + end if + + ! Fill arrays + idx = 1 + do i = 1, component_registry%count + if (component_registry%components(i)%category == cat_lower) then + info = component_registry%components(i) + if (present(names)) then + names(idx) = info%name + end if + if (present(orders)) then + orders(idx) = info%order + end if + idx = idx + 1 + end if + end do + end subroutine get_available_components + + ! Public function to check if registry is initialized + function registry_is_initialized() result(is_initialized) + logical :: is_initialized + is_initialized = component_registry%is_initialized() + end function registry_is_initialized + + ! Public function to get registry size + function registry_get_size() result(size_val) + integer :: size_val + size_val = component_registry%size() + end function registry_get_size + + ! ==================== COMPONENT INFO METHODS ==================== + + subroutine ci_print(this) + class(component_info), intent(in) :: this + + if (this%order > 0) then + print *, " [", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ")]" + else + print *, " [", trim(this%category), ".", trim(this%name), "]" + end if + end subroutine ci_print + + ! ==================== REGISTRY INTERNAL METHODS ==================== + + subroutine cr_register(this, info) + class(component_registry_type), intent(inout) :: this + type(component_info), intent(in) :: info + + type(component_info), allocatable :: temp(:) + integer :: i + + if (.not. this%initialized) then + error stop "[ERROR] Registry not initialized, call initialize_registry first" + end if + + ! Check if already exists + do i = 1, this%count + if (this%components(i)%category == info%category .and. & + this%components(i)%name == info%name) then + if (this%verbose) then + print *, "[WARN] Overwriting: ", & + trim(info%category), ".", trim(info%name) + end if + this%components(i) = info + return + end if + end do + + ! Expand array if needed + if (this%count >= this%capacity) then + this%capacity = this%capacity * 2 + allocate(temp(this%capacity)) + temp(1:this%count) = this%components(1:this%count) + call move_alloc(temp, this%components) + + if (this%verbose) then + print *, "[INFO] Registry expanded to capacity:", this%capacity + end if + end if + + ! Add component + this%count = this%count + 1 + this%components(this%count) = info + + if (this%verbose) then + print *, "[OK] Registered: ", trim(info%category), ".", trim(info%name) + end if + end subroutine cr_register + + function cr_get(this, category, name) result(info) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category, name + type(component_info) :: info + + integer :: i + + ! Initialize return value as empty + info%category = "" + info%name = "" + info%order = 0 + + if (.not. this%initialized) then + return + end if + + do i = 1, this%count + if (this%components(i)%category == category .and. & + this%components(i)%name == name) then + info = this%components(i) + return + end if + end do + end function cr_get + + subroutine cr_list_all(this) + class(component_registry_type), intent(in) :: this + integer :: i + + if (.not. this%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + print *, "=== Registry Contents (", this%count, " components) ===" + + if (this%count == 0) then + print *, " (empty)" + return + end if + + ! Show components grouped by category + do i = 1, this%count + call this%components(i)%print() + end do + + print *, "===========================================" + end subroutine cr_list_all + + subroutine cr_clear(this) + class(component_registry_type), intent(inout) :: this + + if (allocated(this%components)) then + deallocate(this%components) + end if + + this%count = 0 + this%capacity = 100 + this%initialized = .false. + end subroutine cr_clear + + integer function cr_size(this) + class(component_registry_type), intent(in) :: this + cr_size = this%count + end function cr_size + + logical function cr_is_initialized(this) + class(component_registry_type), intent(in) :: this + cr_is_initialized = this%initialized + end function cr_is_initialized + + ! ==================== UTILITY FUNCTIONS ==================== + + function to_lower(str) result(lower_str) + character(len=*), intent(in) :: str + character(len=len(str)) :: lower_str + integer :: i + + do i = 1, len(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + lower_str(i:i) = char(ichar(str(i:i)) + 32) + else + lower_str(i:i) = str(i:i) + end if + end do + end function to_lower + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01k/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01k/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..46964ce79 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01k/src/infrastructure/CMakeLists.txt @@ -0,0 +1,20 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "配置基础设施模块...") + +add_library(infrastructure STATIC + config.f90 + mesh.f90 +) + +target_link_libraries(infrastructure PRIVATE core) + +target_include_directories(infrastructure PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS infrastructure + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "基础设施模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01k/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/01k/src/infrastructure/config.f90 new file mode 100644 index 000000000..d3b1e0dfb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01k/src/infrastructure/config.f90 @@ -0,0 +1,90 @@ +! src/infrastructure/config.f90 +module config_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, cfd_config, config_print, config_with_reconstruction + + ! CFD configuration type + type :: cfd_config + character(len=20) :: ic_type = "step" + character(len=20) :: recon_scheme = "eno" + character(len=20) :: flux_type = "rusanov" + integer :: rk_order = 1 + real(real64) :: wave_speed = 1.0_real64 + real(real64) :: final_time = 0.625_real64 + real(real64) :: dt = 0.025_real64 + character(len=20) :: boundary_type = "periodic" + real(real64) :: left_boundary_value = 1.0_real64 + real(real64) :: right_boundary_value = 2.0_real64 + integer :: spatial_order = 2 + logical :: verbose = .true. + end type cfd_config + +contains + + subroutine config_print(this) + type(cfd_config), intent(in) :: this + + print *, "=== CFD Configuration ===" + print *, "Initial condition: ", trim(this%ic_type) + print *, "Reconstruction: ", trim(this%recon_scheme), " (order:", this%spatial_order, ")" + print *, "Flux type: ", trim(this%flux_type) + print *, "Time integration: RK", this%rk_order + print *, "Wave speed: ", this%wave_speed + print *, "Final time: ", this%final_time + print *, "Time step: ", this%dt + print *, "Boundary: ", trim(this%boundary_type) + if (trim(this%boundary_type) == 'dirichlet') then + print *, " Dirichlet values: [", this%left_boundary_value, ", ", & + this%right_boundary_value, "]" + end if + print *, "===============================" + end subroutine config_print + + subroutine config_with_reconstruction(this, scheme, order) + type(cfd_config), intent(inout) :: this + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + character(len=20) :: scheme_lower + + ! Convert to lowercase + scheme_lower = scheme + call to_lower_inplace(scheme_lower) + this%recon_scheme = trim(adjustl(scheme_lower)) + + ! Set order + if (present(order)) then + this%spatial_order = order + else + ! Smart defaults + if (index(this%recon_scheme, 'weno') > 0) then + this%spatial_order = 5 + else if (trim(this%recon_scheme) == 'eno') then + this%spatial_order = 3 + else + print *, "[ERROR] Unsupported reconstruction scheme: ", trim(this%recon_scheme) + return + end if + end if + + if (this%verbose) then + print *, "[CONFIG] Reconstruction: ", trim(this%recon_scheme), & + " Order: ", this%spatial_order + end if + end subroutine config_with_reconstruction + + subroutine to_lower_inplace(str) + character(len=*), intent(inout) :: str + integer :: i + + do i = 1, len_trim(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + str(i:i) = char(ichar(str(i:i)) + 32) + end if + end do + end subroutine to_lower_inplace + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01k/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/01k/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..896969bd0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01k/src/infrastructure/mesh.f90 @@ -0,0 +1,74 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, mesh_type, mesh_init, mesh_print_info + + ! mesh + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer :: ncells = 40 + integer :: nnodes + integer :: nx + real(wp), allocatable :: x(:) ! + real(wp), allocatable :: xcc(:) ! + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer, optional, intent(in) :: ncells + + integer :: i + + ! Set + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! computation + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! node + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! cell + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== ===" + print *, ": [", this%xmin, ", ", this%xmax, "]" + print *, ": ", this%ncells + print *, ": ", this%nnodes + print *, " dx: ", this%dx + print *, " L: ", this%L + print *, "==========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..66874a7ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/CMakeLists.txt @@ -0,0 +1,7 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..3fde7746b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,28 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +# Start with just the base module +add_library(flux STATIC + base.f90 + rusanov.f90 +) + +#target_link_libraries(flux PRIVATE core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 明确设置不使用 /Qm64 选项 +if(CMAKE_Fortran_COMPILER_ID MATCHES "IntelLLVM|Intel") + set_target_properties(flux PROPERTIES + Fortran_FLAGS "${CMAKE_Fortran_FLAGS} /Qm64" # 明确设置,避免继承问题 + ) +endif() + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/flux/base.f90 new file mode 100644 index 000000000..7080a7ae4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/flux/base.f90 @@ -0,0 +1,30 @@ +!src/numerics/flux/base.f90 +module flux_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, flux_calculator_base + + ! Base flux calculator type + type :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure :: info => flux_info + procedure :: print_basic_info => flux_print_basic ! 添加辅助方法 + end type flux_calculator_base + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "Flux calculator information:" + print *, " Name: ", trim(this%name) + end subroutine flux_info + + subroutine flux_print_basic(this) + class(flux_calculator_base), intent(in) :: this + print *, " Name: ", trim(this%name) + end subroutine flux_print_basic + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/flux/rusanov.f90 b/example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/flux/rusanov.f90 new file mode 100644 index 000000000..7140f710b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/flux/rusanov.f90 @@ -0,0 +1,41 @@ +! src/numerics/flux/rusanov.f90 +module rusanov_flux_module + use, intrinsic :: iso_fortran_env, only: real64 + use flux_base_module, only: flux_calculator_base + implicit none + + private + public :: real64, rusanov_flux + + type, extends(flux_calculator_base) :: rusanov_flux + real(real64) :: wave_speed_default = 1.0_real64 + contains + procedure :: info => rusanov_info + end type rusanov_flux + + ! 构造函数接口 + interface rusanov_flux + module procedure create_rusanov_flux + end interface + +contains + + ! 构造函数 + type(rusanov_flux) function create_rusanov_flux() result(this) + this%name = "Rusanov" + this%wave_speed_default = 1.0_real64 + end function create_rusanov_flux + + subroutine rusanov_info(this) + class(rusanov_flux), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Flux calculator information:" + call this%print_basic_info() + + ! 添加Rusanov特有信息 + print *, " Type: Rusanov flux" + print *, " Default wave speed: ", this%wave_speed_default + end subroutine rusanov_info + +end module rusanov_flux_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..5e4b938d5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,22 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "Configuring reconstructor module...") + +# Start with just the base module +add_library(reconstructor STATIC + base.f90 + eno.f90 + weno3.f90 +) + +target_link_libraries(reconstructor PRIVATE core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Reconstructor module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..53798d021 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/reconstructor/base.f90 @@ -0,0 +1,33 @@ +!src/numerics/reconstructor/base.f90 +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, reconstructor_base + + ! Base reconstructor type + type :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + contains + procedure :: info => reconstructor_info + procedure :: print_basic_info => reconstructor_print_basic ! 添加一个辅助方法 + end type reconstructor_base + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + print *, "Reconstructor information:" + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_info + + subroutine reconstructor_print_basic(this) + class(reconstructor_base), intent(in) :: this + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_print_basic + +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/reconstructor/eno.f90 b/example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/reconstructor/eno.f90 new file mode 100644 index 000000000..a468b82ef --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/reconstructor/eno.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/eno.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, eno_reconstructor + + type, extends(reconstructor_base) :: eno_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => eno_info + end type eno_reconstructor + + ! 构造函数接口 + interface eno_reconstructor + module procedure create_eno_reconstructor + end interface + +contains + + ! 构造函数 + type(eno_reconstructor) function create_eno_reconstructor() result(this) + this%name = "ENO" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_eno_reconstructor + + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加ENO特有信息 + print *, " Type: ENO reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/reconstructor/weno3.f90 b/example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/reconstructor/weno3.f90 new file mode 100644 index 000000000..5e9542915 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01k/src/numerics/reconstructor/weno3.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/weno3.f90 +module weno3_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno3_reconstructor + + type, extends(reconstructor_base) :: weno3_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno3_info + end type weno3_reconstructor + + ! 构造函数接口 + interface weno3_reconstructor + module procedure create_weno3_reconstructor + end interface + +contains + + ! 构造函数 + type(weno3_reconstructor) function create_weno3_reconstructor() result(this) + this%name = "WENO3" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_weno3_reconstructor + + subroutine weno3_info(this) + class(weno3_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加WENO3特有信息 + print *, " Type: WENO-3 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno3_info + +end module weno3_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01k/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01k/tests/CMakeLists.txt new file mode 100644 index 000000000..0cd64b0c6 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01k/tests/CMakeLists.txt @@ -0,0 +1,43 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +add_executable(test_minimal_simple test_minimal_simple.f90) + +message(STATUS "CMAKE_Fortran_MODULE_DIRECTORY=${CMAKE_Fortran_MODULE_DIRECTORY}") + +#target_include_directories( test_minimal_simple +# PRIVATE +# ${CMAKE_Fortran_MODULE_DIRECTORY} +#) + +target_link_libraries( test_minimal_simple + PRIVATE + infrastructure +) + +add_executable(test_simple_link test_simple_link.f90) +target_link_libraries(test_simple_link + PRIVATE + reconstructor + flux +) + + +#add_executable(test_factory_simple test_factory_simple.f90) + +#target_link_libraries( test_factory_simple +# PRIVATE +# core +# infrastructure +# reconstructor +# flux +#) + +add_executable(test_factory_simple test_factory_simple.f90) +target_link_libraries(test_factory_simple + PRIVATE + core + infrastructure + reconstructor + flux +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01k/tests/test_factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/01k/tests/test_factory.f90 new file mode 100644 index 000000000..c62d5f8ab --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01k/tests/test_factory.f90 @@ -0,0 +1,154 @@ +! tests/test_factory.f90 +program test_factory + use registry_module + use config_module + use mesh_module + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + class(*), allocatable :: instance + class(reconstructor_base), allocatable :: recon + class(flux_calculator_base), allocatable :: flux_calc + real(wp), allocatable :: q(:), qL(:), qR(:), flux(:) + integer :: i, n + + print *, "=== Factory Pattern Test ===" + print *, "" + + ! Initialize systems + call initialize_registry(verbose=.true.) + + ! Register components with factories + print *, "1. Registering components with factories..." + call register_component_with_factory("reconstructor", "eno", create_eno, 3) + call register_component_with_factory("reconstructor", "weno3", create_weno3, 3) + call register_component_with_factory("flux", "rusanov", create_rusanov) + + call component_registry%list_all() + print *, "" + + ! Test creating ENO reconstructor + print *, "2. Creating ENO reconstructor..." + call create_component("reconstructor", "eno", instance) + + if (allocated(instance)) then + select type (inst => instance) + type is (eno_reconstructor) + allocate(recon, source=inst) + print *, "ENO reconstructor created successfully" + call recon%info() + print *, "" + + ! Test reconstruction + n = 10 + allocate(q(0:n+1), qL(n), qR(n)) + + ! Initialize test data (sine wave) + do i = 0, n+1 + q(i) = sin(2.0_wp * 3.141592653589793_wp * real(i-1, wp) / real(n, wp)) + end do + + print *, "Testing ENO reconstruction..." + call recon%reconstruct(q, qL, qR) + + print *, "q (internal):" + do i = 1, n + write(*, '(I3, F10.6)') i, q(i) + end do + + print *, "qL (left interface values):" + do i = 1, n + write(*, '(I3, F10.6)') i, qL(i) + end do + + deallocate(q, qL, qR) + class default + print *, "[ERROR] Wrong type for ENO reconstructor" + end select + else + print *, "[ERROR] Failed to create ENO reconstructor" + end if + print *, "" + + ! Test creating WENO3 reconstructor + print *, "3. Creating WENO3 reconstructor..." + if (allocated(instance)) deallocate(instance) + call create_component("reconstructor", "weno3", instance) + + if (allocated(instance)) then + select type (inst => instance) + type is (weno3_reconstructor) + if (allocated(recon)) deallocate(recon) + allocate(recon, source=inst) + print *, "WENO3 reconstructor created successfully" + call recon%info() + class default + print *, "[ERROR] Wrong type for WENO3 reconstructor" + end select + else + print *, "[ERROR] Failed to create WENO3 reconstructor" + end if + print *, "" + + ! Test creating Rusanov flux calculator + print *, "4. Creating Rusanov flux calculator..." + if (allocated(instance)) deallocate(instance) + call create_component("flux", "rusanov", instance) + + if (allocated(instance)) then + select type (inst => instance) + type is (rusanov_flux) + allocate(flux_calc, source=inst) + print *, "Rusanov flux calculator created successfully" + call flux_calc%info() + print *, "" + + ! Test flux computation + n = 5 + allocate(qL(n), qR(n), flux(n)) + + ! Initialize test data + do i = 1, n + qL(i) = 1.0_wp + 0.1_wp * real(i-1, wp) + qR(i) = 1.0_wp + 0.1_wp * real(i, wp) + end do + + print *, "Testing Rusanov flux computation..." + call flux_calc%compute(qL, qR, flux, 1.0_wp) + + print *, "qL:" + do i = 1, n + write(*, '(I3, F10.6)') i, qL(i) + end do + + print *, "qR:" + do i = 1, n + write(*, '(I3, F10.6)') i, qR(i) + end do + + print *, "Flux:" + do i = 1, n + write(*, '(I3, F10.6)') i, flux(i) + end do + + deallocate(qL, qR, flux) + class default + print *, "[ERROR] Wrong type for Rusanov flux" + end select + else + print *, "[ERROR] Failed to create Rusanov flux calculator" + end if + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Factory pattern test completed successfully ===" + +end program test_factory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01k/tests/test_factory_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/01k/tests/test_factory_simple.f90 new file mode 100644 index 000000000..d4139bb1a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01k/tests/test_factory_simple.f90 @@ -0,0 +1,86 @@ +!tests/test_factory_simple.f90 +program test_factory_simple + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module, only: initialize_registry, cleanup_registry, & + register_component_simple, has_component + use config_module, only: cfd_config, config_print + use mesh_module, only: mesh_type + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(eno_reconstructor) :: eno + type(weno3_reconstructor) :: weno3 + type(rusanov_flux) :: rusanov + + print *, "=== Factory Pattern Simple Test ===" + print *, "" + + ! Test 1: Basic systems + print *, "1. Testing basic systems..." + print *, "-----------------------------" + call config_print(config) + + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 2: Creating reconstructors + print *, "2. Testing reconstructors..." + print *, "------------------------------" + + ! 创建并测试ENO重构器 + print *, "Creating ENO reconstructor..." + eno = eno_reconstructor() ! 使用构造函数 + call eno%info() ! 必须调用info方法 + + print *, "" + print *, "Creating WENO3 reconstructor..." + weno3 = weno3_reconstructor() ! 使用构造函数 + call weno3%info() ! 必须调用info方法 + print *, "" + + ! Test 3: Creating flux calculator + print *, "3. Testing flux calculator..." + print *, "-------------------------------" + + print *, "Creating Rusanov flux calculator..." + rusanov = rusanov_flux() ! 使用构造函数 + call rusanov%info() ! 必须调用info方法 + print *, "" + + ! Test 4: Registry integration + print *, "4. Testing registry..." + print *, "----------------------" + + call initialize_registry(verbose=.true.) + + ! Register components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("flux", "rusanov") + + ! Check registration + if (has_component("reconstructor", "eno")) then + print *, "[OK] ENO reconstructor registered successfully" + end if + + if (has_component("reconstructor", "weno3")) then + print *, "[OK] WENO3 reconstructor registered successfully" + end if + + if (has_component("flux", "rusanov")) then + print *, "[OK] Rusanov flux registered successfully" + end if + + print *, "" + call cleanup_registry() + + print *, "=== Factory pattern simple test completed successfully ===" + +end program test_factory_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01k/tests/test_minimal.f90 b/example/1d-linear-convection/weno3/fortran/registry/01k/tests/test_minimal.f90 new file mode 100644 index 000000000..807d0bc2c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01k/tests/test_minimal.f90 @@ -0,0 +1,108 @@ +! tests/test_minimal.f90 +program test_minimal + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i ! Declare loop variable here + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", component_registry%size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components + print *, "5. Testing available components" + print *, "--------------------------------" + + ! Use a separate block to avoid allocation issues + call test_available_components() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Minimal test completed successfully ===" + +contains + + ! Internal subroutine for testing available components + subroutine test_available_components() + character(len=:), allocatable :: names(:) + integer, allocatable :: orders(:) + integer :: j + + call get_available_components("reconstructor", names, orders) + + if (allocated(names)) then + print *, "Reconstructors:" + do j = 1, size(names) + print *, " - ", trim(names(j)) + if (allocated(orders)) then + print *, " Order: ", orders(j) + end if + end do + else + print *, "No reconstructors found" + end if + end subroutine test_available_components + +end program test_minimal \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01k/tests/test_minimal_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/01k/tests/test_minimal_simple.f90 new file mode 100644 index 000000000..6213e8f89 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01k/tests/test_minimal_simple.f90 @@ -0,0 +1,84 @@ +! tests/test_minimal_simple.f90 +program test_minimal_simple + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Simple Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components (simple version) + print *, "5. Testing registry functionality" + print *, "---------------------------------" + print *, "Registry initialized: ", registry_is_initialized() + print *, "Number of components: ", registry_get_size() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Simple test completed successfully ===" + +end program test_minimal_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01k/tests/test_simple_link.f90 b/example/1d-linear-convection/weno3/fortran/registry/01k/tests/test_simple_link.f90 new file mode 100644 index 000000000..807d0bc2c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01k/tests/test_simple_link.f90 @@ -0,0 +1,108 @@ +! tests/test_minimal.f90 +program test_minimal + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i ! Declare loop variable here + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", component_registry%size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components + print *, "5. Testing available components" + print *, "--------------------------------" + + ! Use a separate block to avoid allocation issues + call test_available_components() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Minimal test completed successfully ===" + +contains + + ! Internal subroutine for testing available components + subroutine test_available_components() + character(len=:), allocatable :: names(:) + integer, allocatable :: orders(:) + integer :: j + + call get_available_components("reconstructor", names, orders) + + if (allocated(names)) then + print *, "Reconstructors:" + do j = 1, size(names) + print *, " - ", trim(names(j)) + if (allocated(orders)) then + print *, " Order: ", orders(j) + end if + end do + else + print *, "No reconstructors found" + end if + end subroutine test_available_components + +end program test_minimal \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01l/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01l/CMakeLists.txt new file mode 100644 index 000000000..ef66d5848 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01l/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01l/README.md b/example/1d-linear-convection/weno3/fortran/registry/01l/README.md new file mode 100644 index 000000000..c4889757b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01l/README.md @@ -0,0 +1,221 @@ +# Fortran CFD Project + +基于工厂模式和注册系统的CFD求解器框架。 + +## 项目结构 + +``` +fortran_cfd/ +├── CMakeLists.txt # 根CMake配置 +├── scripts/ # 构建脚本 +│ ├── build.py # Python构建脚本(推荐) +│ ├── build.bat # Batch包装脚本 +│ ├── build.ps1 # PowerShell包装脚本 +│ └── requirements.txt # Python依赖 +├── src/ # 源代码 +│ ├── CMakeLists.txt +│ ├── core/ # 核心模块(注册系统、工厂接口) +│ ├── infrastructure/ # 基础设施(配置、网格) +│ └── numerics/ # 数值方法 +├── tests/ # 测试 +│ ├── CMakeLists.txt +│ ├── test_minimal_simple.f90 # 简单测试 +│ └── test_factory.f90 # 工厂模式测试 +└── README.md # 项目说明(本文件) +``` + +## 快速开始 + +### 使用Python脚本(推荐) + +```bash +# 进入脚本目录 +cd scripts + +# 基本构建 +python build.py + +# 清理并构建 +python build.py --clean + +# 发布构建 +python build.py --build-type Release --clean + +# 并行构建(使用所有核心) +python build.py -j + +# 不运行测试 +python build.py --no-tests + +# 查看帮助 +python build.py --help +``` + +### 使用批处理脚本 + +```bash +cd scripts +.\build.bat +``` + +### 使用PowerShell脚本 + +```powershell +cd scripts +.\build.ps1 +``` + +## 手动构建 + +```bash +# 设置Intel环境 +cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && powershell' + +# 配置项目 +mkdir build +cd build +cmake -G "Visual Studio 17 2022" -A x64 -T fortran=ifx .. + +# 构建项目 +cmake --build . --config Debug + +# 运行测试 +Debug\test_simple.exe +Debug\test_factory.exe +``` + +## 功能特性 + +✅ 组件注册系统 +✅ 工厂模式 +✅ ENO/WENO重构器 +✅ Rusanov通量计算器 +✅ 可扩展架构 +✅ 自动化测试 + +## 依赖 + +- Intel oneAPI(包含ifx编译器) +- CMake 3.12+ +- Python 3.6+(仅用于构建脚本) + +## 源代码文件 + +### 核心模块 +- `src/core/registry.f90` - 组件注册系统 +- `src/core/factory_interfaces.f90` - 工厂接口 + +### 基础设施 +- `src/infrastructure/config.f90` - 配置管理 +- `src/infrastructure/mesh.f90` - 网格生成 + +### 数值方法 +- `src/numerics/reconstructor/base.f90` - 重构器基类 +- `src/numerics/reconstructor/eno.f90` - ENO重构器 +- `src/numerics/reconstructor/weno3.f90` - WENO-3重构器 +- `src/numerics/flux/base.f90` - 通量计算器基类 +- `src/numerics/flux/rusanov.f90` - Rusanov通量 + +### 测试 +- `tests/test_minimal_simple.f90` - 简单功能测试 +- `tests/test_factory.f90` - 工厂模式测试 + +## 构建脚本 + +### Python脚本 (build.py) +主构建脚本,支持: +- 自动设置Intel oneAPI环境 +- 参数化构建选项 +- 并行编译 +- 自动化测试 +- 彩色输出 + +### Batch脚本 (build.bat) +Windows批处理包装器,调用Python脚本。 + +### PowerShell脚本 (build.ps1) +PowerShell包装器,调用Python脚本。 + +## 示例输出 + +成功构建后,会看到类似输出: + +``` +============================================================ + Fortran CFD Project Builder +============================================================ + +[1/4] Setting up Intel Fortran compiler... +✓ Intel oneAPI environment configured + +[2/4] Preparing build directory... +✓ Cleaned build directory + +[3/4] Configuring project... + $ cmake -G Visual Studio 17 2022 -A x64 -T fortran=ifx -DCMAKE_BUILD_TYPE=Debug .. +✓ CMake configuration successful + +[4/4] Building project... + $ cmake --build . --config Debug +✓ Build completed in 12.3 seconds + +============================================================ + Running Tests +============================================================ + +[TEST] Simple functionality test... +✓ Simple test passed + +[TEST] Factory pattern test... +✓ Factory test passed + +============================================================ + Build Summary +============================================================ +✓ Build directory: D:\path\to\build +✓ Build type: Debug +✓ Total time: 15.2s +``` + +## 开发指南 + +### 添加新组件 + +1. 在相应模块中创建Fortran源文件 +2. 实现工厂函数 +3. 使用`register_component_with_factory`注册组件 +4. 添加测试 + +### 扩展架构 + +项目采用模块化设计: +1. **注册系统** - 管理所有可用的组件 +2. **工厂模式** - 动态创建组件实例 +3. **抽象接口** - 确保组件兼容性 +4. **配置驱动** - 通过配置文件控制行为 + +## 故障排除 + +### 常见问题 + +1. **Intel环境未找到** + - 检查Intel oneAPI安装路径 + - 手动运行`setvars.bat` + +2. **CMake配置失败** + - 确保使用正确的生成器:`Visual Studio 17 2022` + - 检查Fortran编译器是否可用 + +3. **构建失败** + - 检查依赖项是否完整 + - 查看详细的错误信息 + +### 调试建议 + +- 使用`--verbose`参数获取详细输出 +- 检查`build/CMakeCache.txt`了解配置详情 +- 查看编译器错误日志 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01l/scripts/build.bat b/example/1d-linear-convection/weno3/fortran/registry/01l/scripts/build.bat new file mode 100644 index 000000000..6fd6dc031 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01l/scripts/build.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Project Builder +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python build script with full Intel environment support... +echo. + +python build.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Build failed + pause + exit /b 1 +) + +echo. +echo [INFO] Build completed successfully! +echo. +echo [INFO] To run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01l/scripts/build.py b/example/1d-linear-convection/weno3/fortran/registry/01l/scripts/build.py new file mode 100644 index 000000000..3bf6d5375 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01l/scripts/build.py @@ -0,0 +1,629 @@ +#!/usr/bin/env python3 +""" +Fortran CFD Project Builder - 完整Python解决方案 +在Python内部处理Intel oneAPI环境配置 +""" + +import os +import sys +import subprocess +import shutil +import argparse +import time +import platform +import tempfile +from pathlib import Path + +class IntelEnvironment: + """Intel oneAPI环境管理器""" + + def __init__(self): + self.setvars_path = None + self.env_vars = {} + + def find_setvars(self): + """查找setvars.bat文件""" + possible_paths = [ + r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat", + r"C:\Program Files\Intel\oneAPI\setvars.bat", + r"C:\Program Files (x86)\Intel\oneAPI\compiler\latest\env\vars.bat", + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\setvars.bat"), + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\compiler\latest\env\vars.bat"), + ] + + for path in possible_paths: + if os.path.exists(path): + self.setvars_path = path + return True + + return False + + def setup_environment(self): + """设置Intel环境""" + if not self.find_setvars(): + return False + + try: + # 创建临时的批处理文件来捕获环境变量 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + f.write(f'@echo off\n') + f.write(f'call "{self.setvars_path}" >nul 2>&1\n') + f.write(f'set\n') # 输出所有环境变量 + temp_bat = f.name + + # 运行批处理文件并捕获输出 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + shell=True + ) + + # 解析环境变量 + for line in result.stdout.split('\n'): + line = line.strip() + if '=' in line: + key, value = line.split('=', 1) + self.env_vars[key.strip()] = value.strip() + + # 清理临时文件 + os.unlink(temp_bat) + + # 更新当前进程的环境变量 + os.environ.update(self.env_vars) + + return True + + except Exception as e: + print(f"设置Intel环境失败: {e}") + return False + + def get_compiler_info(self): + """获取编译器信息""" + info = {} + + # 检查ifx编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + env={**os.environ, **self.env_vars} if self.env_vars else os.environ + ) + + if result.returncode == 0: + for line in result.stdout.split('\n'): + if 'Version' in line or '版本' in line: + info['ifx_version'] = line.strip() + break + except: + pass + + # 检查环境变量 + info['ifx_root'] = self.env_vars.get('IFX_ROOT', '') + info['compiler_root'] = self.env_vars.get('ONEAPI_ROOT', '') + + return info + +class BuildSystem: + """构建系统主类""" + + def __init__(self): + self.project_root = Path(__file__).parent.parent + self.build_dir = self.project_root / "build" + self.intel_env = IntelEnvironment() + + # 设置控制台编码 + if sys.platform == "win32": + try: + import ctypes + # 设置控制台输出为UTF-8 + ctypes.windll.kernel32.SetConsoleOutputCP(65001) + except: + pass + + def print_header(self, text): + """打印标题""" + print(f"\n{'='*70}") + print(f" {text}") + print(f"{'='*70}\n") + + def print_step(self, step, total, message): + """打印步骤""" + print(f"[{step}/{total}] {message}...") + + def print_success(self, message): + """打印成功""" + print(f"\033[92m✓ {message}\033[0m") + + def print_error(self, message): + """打印错误""" + print(f"\033[91m✗ {message}\033[0m") + + def print_warning(self, message): + """打印警告""" + print(f"\033[93m! {message}\033[0m") + + def print_info(self, message): + """打印信息""" + print(f"\033[94mℹ {message}\033[0m") + + def check_prerequisites(self): + """检查前提条件""" + self.print_step(1, 6, "检查前提条件") + + # 检查Python版本 + python_version = sys.version.split()[0] + self.print_info(f"Python版本: {python_version}") + + # 检查平台 + self.print_info(f"平台: {platform.system()} {platform.release()}") + self.print_info(f"处理器核心数: {os.cpu_count()}") + + # 检查CMake + try: + result = subprocess.run( + ["cmake", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + version_line = result.stdout.split('\n')[0] + self.print_success(f"CMake: {version_line}") + else: + self.print_error("CMake未找到") + return False + except FileNotFoundError: + self.print_error("CMake未安装") + return False + + return True + + def setup_intel_environment(self, args): + """设置Intel环境""" + self.print_step(2, 6, "配置Intel oneAPI环境") + + if not self.intel_env.find_setvars(): + self.print_warning("未找到Intel oneAPI setvars.bat") + self.print_info("将尝试使用系统环境中的编译器") + + # 检查是否能直接访问编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + self.print_success("Intel编译器在系统PATH中找到") + return True + else: + self.print_warning("Intel编译器未在PATH中找到") + except: + self.print_warning("无法访问Intel编译器") + + return True # 继续,让CMake自己找编译器 + + # 设置环境 + if self.intel_env.setup_environment(): + compiler_info = self.intel_env.get_compiler_info() + + if compiler_info.get('ifx_version'): + self.print_success(f"Intel Fortran编译器: {compiler_info['ifx_version']}") + elif compiler_info.get('ifx_root'): + self.print_success(f"Intel编译器路径: {compiler_info['ifx_root']}") + else: + self.print_success("Intel oneAPI环境配置完成") + + return True + else: + self.print_warning("Intel环境配置失败,将继续使用系统环境") + return True + + def clean_build_directory(self, args): + """清理构建目录""" + if args.clean and self.build_dir.exists(): + self.print_info("清理构建目录...") + try: + shutil.rmtree(self.build_dir) + self.print_success("构建目录已清理") + except Exception as e: + self.print_error(f"清理失败: {e}") + if not args.force: + return False + return True + + def run_command(self, cmd, cwd=None, check=True, env=None): + """运行命令""" + if isinstance(cmd, list): + cmd_str = ' '.join(str(c) for c in cmd if c) + else: + cmd_str = str(cmd) + + print(f" \033[96m$\033[0m {cmd_str}") + + try: + # 合并环境变量 + exec_env = os.environ.copy() + if env: + exec_env.update(env) + if self.intel_env.env_vars: + exec_env.update(self.intel_env.env_vars) + + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=False, + env=exec_env + ) + + # 处理输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or '完成' in line or '生成' in line: + print(f" \033[92m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + if check and result.returncode != 0: + self.print_error(f"命令执行失败,退出码: {result.returncode}") + return False + + return True + + except Exception as e: + self.print_error(f"命令执行异常: {e}") + return False + + def configure_cmake(self, args): + """配置CMake""" + self.print_step(3, 6, "配置CMake项目") + + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + f"-DCMAKE_BUILD_TYPE={args.build_type}", + ] + + if args.compiler == "ifx": + cmake_cmd.extend(["-T", "fortran=ifx"]) + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + success = self.run_command(cmake_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("CMake配置完成") + else: + self.print_error("CMake配置失败") + + return success + + def build_project(self, args): + """构建项目""" + self.print_step(4, 6, "构建项目") + + build_cmd = [ + "cmake", + "--build", ".", + "--config", args.build_type, + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + success = self.run_command(build_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("项目构建完成") + else: + self.print_error("构建失败") + + return success + + def run_tests_with_environment(self, test_exe): + """运行单个测试,确保有Intel环境""" + try: + # 创建临时的批处理文件来运行测试 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'"{test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'"{test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + return result + + except Exception as e: + print(f"运行测试失败: {e}") + return None + + def run_tests(self, args): + """运行测试""" + self.print_step(5, 6, "运行测试") + + # 查找测试可执行文件 + test_dir = self.build_dir / "bin" / args.build_type + if not test_dir.exists(): + test_dir = self.build_dir / "bin" + if not test_dir.exists(): + test_dir = self.build_dir + + test_files = list(test_dir.glob("test_*.exe")) + + if not test_files: + self.print_warning("未找到测试程序") + return True + + all_passed = True + + for test_exe in sorted(test_files): + test_name = test_exe.stem + self.print_info(f"运行测试: {test_name}") + print(f" {'-'*50}") + + # 运行测试 + result = self.run_tests_with_environment(str(test_exe)) + + if result is None: + self.print_error(f" {test_name} 运行失败") + all_passed = False + continue + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + print(f" {line}") + + if result.returncode == 0: + self.print_success(f" {test_name} 通过") + else: + self.print_error(f" {test_name} 失败 (退出码: {result.returncode})") + all_passed = False + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + print() # 空行 + + return all_passed + + def create_test_runner(self, args): + """创建独立的测试运行器""" + self.print_step(6, 6, "创建测试运行器") + + runner_path = self.build_dir / "run_tests.bat" + + content = f'''@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Test Runner +echo ======================================== +echo. + +REM Setup Intel oneAPI environment +set "SETVARS_PATH={self.intel_env.setvars_path or ''}" +if exist "%SETVARS_PATH%" ( + call "%SETVARS_PATH%" >nul + echo [INFO] Intel environment configured +) else ( + echo [WARNING] Intel environment not found + echo [WARNING] Tests may fail without runtime libraries +) + +echo. + +REM Run all test executables +set "TEST_COUNT=0" +set "PASS_COUNT=0" + +for %%f in ("bin\\{args.build_type}\\test_*.exe") do ( + set /a TEST_COUNT+=1 + echo [TEST %%f] + echo {'-'*50} + + %%f + if errorlevel 1 ( + echo [FAILED] %%f + ) else ( + echo [PASSED] %%f + set /a PASS_COUNT+=1 + ) + echo. +) + +echo ======================================== +echo Tests: %PASS_COUNT%/%TEST_COUNT% passed +if %PASS_COUNT% equ %TEST_COUNT% ( + echo [SUCCESS] All tests passed! +) else ( + echo [FAILURE] Some tests failed +) +echo ======================================== + +pause +''' + + with open(runner_path, 'w', encoding='utf-8') as f: + f.write(content) + + self.print_success(f"测试运行器已创建: {runner_path}") + self.print_info(f"使用方法: cd build && run_tests.bat") + + return runner_path + + def generate_report(self, args, build_time, tests_passed): + """生成构建报告""" + self.print_header("构建完成") + + print(f"项目: {self.project_root.name}") + print(f"构建类型: {args.build_type}") + print(f"编译器: {args.compiler}") + print(f"并行作业: {args.jobs}") + print(f"总耗时: {build_time:.1f}秒") + print(f"测试结果: {'全部通过' if tests_passed else '有失败'}") + + # 显示生成的可执行文件 + bin_dir = self.build_dir / "bin" / args.build_type + if bin_dir.exists(): + print(f"\n生成的可执行文件:") + for exe in sorted(bin_dir.glob("*.exe")): + size_mb = exe.stat().st_size / (1024 * 1024) + print(f" • {exe.name} ({size_mb:.2f} MB)") + + # 显示测试运行器信息 + runner_path = self.build_dir / "run_tests.bat" + if runner_path.exists(): + print(f"\n独立测试运行器:") + print(f" • {runner_path.name}") + print(f" 在Intel oneAPI环境中运行所有测试") + + def run(self): + """运行构建系统""" + parser = argparse.ArgumentParser( + description="Fortran CFD项目构建工具", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认构建 + %(prog)s --clean # 清理后构建 + %(prog)s --build-type Release # Release构建 + %(prog)s --no-tests # 只构建,不运行测试 + %(prog)s -j8 --verbose # 8线程并行构建,详细输出 + """ + ) + + parser.add_argument("--build-type", choices=["Debug", "Release"], + default="Debug", help="构建类型") + parser.add_argument("--compiler", choices=["ifx", "ifort"], + default="ifx", help="Fortran编译器") + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-tests", action="store_true", + help="跳过测试") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + + args = parser.parse_args() + + # 开始构建 + start_time = time.time() + + self.print_header("Fortran CFD 项目构建系统") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 检查前提条件 + if not self.check_prerequisites(): + if not args.force: + return 1 + + # 2. 设置Intel环境 + if not self.setup_intel_environment(args): + if not args.force: + return 1 + + # 3. 清理目录 + if not self.clean_build_directory(args): + if not args.force: + return 1 + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 4. 配置CMake + if not self.configure_cmake(args): + if not args.force: + return 1 + + # 5. 构建项目 + if not self.build_project(args): + if not args.force: + return 1 + + # 6. 运行测试和创建测试运行器 + tests_passed = True + if not args.no_tests: + tests_passed = self.run_tests(args) + + # 创建测试运行器 + self.create_test_runner(args) # 传递 args 参数 + + # 7. 生成报告 + build_time = time.time() - start_time + self.generate_report(args, build_time, tests_passed) + + return 0 if tests_passed else 1 + + except KeyboardInterrupt: + self.print_error("\n构建被用户中断") + return 1 + except Exception as e: + self.print_error(f"构建过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + builder = BuildSystem() + return builder.run() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01l/scripts/requirements.txt b/example/1d-linear-convection/weno3/fortran/registry/01l/scripts/requirements.txt new file mode 100644 index 000000000..d21a12b45 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01l/scripts/requirements.txt @@ -0,0 +1,2 @@ +# 构建脚本的Python依赖(可选) +# 目前没有特殊依赖,保持空文件或添加未来可能需要的包 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01l/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01l/src/CMakeLists.txt new file mode 100644 index 000000000..ee38952bb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01l/src/CMakeLists.txt @@ -0,0 +1,14 @@ +# ==================== src\CMakeLists.txt ==================== + +# src/CMakeLists.txt +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(core) +add_subdirectory(infrastructure) +add_subdirectory(numerics) + +message(STATUS "源代码目录配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01l/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01l/src/core/CMakeLists.txt new file mode 100644 index 000000000..bcfd024d8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01l/src/core/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/core/CMakeLists.txt +message(STATUS "配置核心模块...") + +add_library(core STATIC + registry.f90 + factory_interfaces.f90 +) + +target_include_directories(core PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 安装规则 +install(TARGETS core + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) +install(FILES + ${CMAKE_Fortran_MODULE_DIRECTORY}/registry_module.mod + ${CMAKE_Fortran_MODULE_DIRECTORY}/factory_interfaces.mod + DESTINATION include/fortran_cfd/core +) + +message(STATUS "核心模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01l/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/01l/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..a960167c3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01l/src/core/factory_interfaces.f90 @@ -0,0 +1,17 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, factory_procedure + + ! Factory procedure interface + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01l/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/01l/src/core/registry.f90 new file mode 100644 index 000000000..bf9028c3a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01l/src/core/registry.f90 @@ -0,0 +1,318 @@ +! src/core/registry.f90 +module registry_module + use, intrinsic :: iso_fortran_env, only: real64 + use factory_interfaces, only: factory_procedure + implicit none + + private + + ! Public interface + public :: real64, component_info, component_registry + public :: register_component_simple, initialize_registry, cleanup_registry + public :: has_component, get_available_components + public :: registry_is_initialized, registry_get_size ! + + ! Type definitions (simplified, no factory for now) + type :: component_info + character(len=32) :: category = "" + character(len=32) :: name = "" + integer :: order = 0 + contains + procedure :: print => ci_print + end type component_info + + type :: component_registry_type + private + type(component_info), allocatable :: components(:) + integer :: count = 0 + integer :: capacity = 100 + logical :: verbose = .true. + logical :: initialized = .false. + contains + procedure :: register => cr_register + procedure :: get => cr_get + procedure :: list_all => cr_list_all + procedure :: clear => cr_clear + procedure :: size => cr_size + procedure :: is_initialized => cr_is_initialized ! + end type component_registry_type + + ! Global registry instance + type(component_registry_type), save :: component_registry + +contains + + ! ==================== PUBLIC API ==================== + + ! Initialize registry + subroutine initialize_registry(initial_capacity, verbose) + integer, optional, intent(in) :: initial_capacity + logical, optional, intent(in) :: verbose + + if (component_registry%initialized) then + if (component_registry%verbose) then + print *, "[INFO] Registry already initialized" + end if + return + end if + + if (present(initial_capacity)) then + component_registry%capacity = max(10, initial_capacity) + end if + + if (present(verbose)) then + component_registry%verbose = verbose + end if + + ! Allocate array + allocate(component_registry%components(component_registry%capacity)) + + component_registry%initialized = .true. + component_registry%count = 0 + + if (component_registry%verbose) then + print *, "[INIT] Registry initialized, capacity:", component_registry%capacity + end if + end subroutine initialize_registry + + ! Cleanup registry + subroutine cleanup_registry + call component_registry%clear() + if (component_registry%verbose) then + print *, "[CLEANUP] Registry cleaned up" + end if + end subroutine cleanup_registry + + ! Simple registration (no factory) + subroutine register_component_simple(category, name) + character(len=*), intent(in) :: category, name + + type(component_info) :: info + + info%category = to_lower(trim(adjustl(category))) + info%name = to_lower(trim(adjustl(name))) + info%order = 0 + + call component_registry%register(info) + end subroutine register_component_simple + + ! Check if component exists + function has_component(category, name) result(found) + character(len=*), intent(in) :: category, name + logical :: found + + type(component_info) :: info + character(len=32) :: cat_lower, name_lower + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + info = component_registry%get(cat_lower, name_lower) + found = (len_trim(info%category) > 0) + end function has_component + + ! Get available components in a category + subroutine get_available_components(category, names, orders) + character(len=*), intent(in) :: category + character(len=:), allocatable, intent(out), optional :: names(:) + integer, allocatable, intent(out), optional :: orders(:) + + character(len=32) :: cat_lower + integer :: i, count, idx + type(component_info) :: info + + cat_lower = to_lower(trim(adjustl(category))) + + ! Count components in this category + count = 0 + do i = 1, component_registry%count + if (component_registry%components(i)%category == cat_lower) then + count = count + 1 + end if + end do + + ! Allocate arrays if requested + if (present(names)) then + allocate(character(len=32) :: names(count)) + end if + + if (present(orders)) then + allocate(orders(count)) + end if + + ! Fill arrays + idx = 1 + do i = 1, component_registry%count + if (component_registry%components(i)%category == cat_lower) then + info = component_registry%components(i) + if (present(names)) then + names(idx) = info%name + end if + if (present(orders)) then + orders(idx) = info%order + end if + idx = idx + 1 + end if + end do + end subroutine get_available_components + + ! Public function to check if registry is initialized + function registry_is_initialized() result(is_initialized) + logical :: is_initialized + is_initialized = component_registry%is_initialized() + end function registry_is_initialized + + ! Public function to get registry size + function registry_get_size() result(size_val) + integer :: size_val + size_val = component_registry%size() + end function registry_get_size + + ! ==================== COMPONENT INFO METHODS ==================== + + subroutine ci_print(this) + class(component_info), intent(in) :: this + + if (this%order > 0) then + print *, " [", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ")]" + else + print *, " [", trim(this%category), ".", trim(this%name), "]" + end if + end subroutine ci_print + + ! ==================== REGISTRY INTERNAL METHODS ==================== + + subroutine cr_register(this, info) + class(component_registry_type), intent(inout) :: this + type(component_info), intent(in) :: info + + type(component_info), allocatable :: temp(:) + integer :: i + + if (.not. this%initialized) then + error stop "[ERROR] Registry not initialized, call initialize_registry first" + end if + + ! Check if already exists + do i = 1, this%count + if (this%components(i)%category == info%category .and. & + this%components(i)%name == info%name) then + if (this%verbose) then + print *, "[WARN] Overwriting: ", & + trim(info%category), ".", trim(info%name) + end if + this%components(i) = info + return + end if + end do + + ! Expand array if needed + if (this%count >= this%capacity) then + this%capacity = this%capacity * 2 + allocate(temp(this%capacity)) + temp(1:this%count) = this%components(1:this%count) + call move_alloc(temp, this%components) + + if (this%verbose) then + print *, "[INFO] Registry expanded to capacity:", this%capacity + end if + end if + + ! Add component + this%count = this%count + 1 + this%components(this%count) = info + + if (this%verbose) then + print *, "[OK] Registered: ", trim(info%category), ".", trim(info%name) + end if + end subroutine cr_register + + function cr_get(this, category, name) result(info) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category, name + type(component_info) :: info + + integer :: i + + ! Initialize return value as empty + info%category = "" + info%name = "" + info%order = 0 + + if (.not. this%initialized) then + return + end if + + do i = 1, this%count + if (this%components(i)%category == category .and. & + this%components(i)%name == name) then + info = this%components(i) + return + end if + end do + end function cr_get + + subroutine cr_list_all(this) + class(component_registry_type), intent(in) :: this + integer :: i + + if (.not. this%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + print *, "=== Registry Contents (", this%count, " components) ===" + + if (this%count == 0) then + print *, " (empty)" + return + end if + + ! Show components grouped by category + do i = 1, this%count + call this%components(i)%print() + end do + + print *, "===========================================" + end subroutine cr_list_all + + subroutine cr_clear(this) + class(component_registry_type), intent(inout) :: this + + if (allocated(this%components)) then + deallocate(this%components) + end if + + this%count = 0 + this%capacity = 100 + this%initialized = .false. + end subroutine cr_clear + + integer function cr_size(this) + class(component_registry_type), intent(in) :: this + cr_size = this%count + end function cr_size + + logical function cr_is_initialized(this) + class(component_registry_type), intent(in) :: this + cr_is_initialized = this%initialized + end function cr_is_initialized + + ! ==================== UTILITY FUNCTIONS ==================== + + function to_lower(str) result(lower_str) + character(len=*), intent(in) :: str + character(len=len(str)) :: lower_str + integer :: i + + do i = 1, len(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + lower_str(i:i) = char(ichar(str(i:i)) + 32) + else + lower_str(i:i) = str(i:i) + end if + end do + end function to_lower + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01l/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01l/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..46964ce79 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01l/src/infrastructure/CMakeLists.txt @@ -0,0 +1,20 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "配置基础设施模块...") + +add_library(infrastructure STATIC + config.f90 + mesh.f90 +) + +target_link_libraries(infrastructure PRIVATE core) + +target_include_directories(infrastructure PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS infrastructure + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "基础设施模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01l/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/01l/src/infrastructure/config.f90 new file mode 100644 index 000000000..d3b1e0dfb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01l/src/infrastructure/config.f90 @@ -0,0 +1,90 @@ +! src/infrastructure/config.f90 +module config_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, cfd_config, config_print, config_with_reconstruction + + ! CFD configuration type + type :: cfd_config + character(len=20) :: ic_type = "step" + character(len=20) :: recon_scheme = "eno" + character(len=20) :: flux_type = "rusanov" + integer :: rk_order = 1 + real(real64) :: wave_speed = 1.0_real64 + real(real64) :: final_time = 0.625_real64 + real(real64) :: dt = 0.025_real64 + character(len=20) :: boundary_type = "periodic" + real(real64) :: left_boundary_value = 1.0_real64 + real(real64) :: right_boundary_value = 2.0_real64 + integer :: spatial_order = 2 + logical :: verbose = .true. + end type cfd_config + +contains + + subroutine config_print(this) + type(cfd_config), intent(in) :: this + + print *, "=== CFD Configuration ===" + print *, "Initial condition: ", trim(this%ic_type) + print *, "Reconstruction: ", trim(this%recon_scheme), " (order:", this%spatial_order, ")" + print *, "Flux type: ", trim(this%flux_type) + print *, "Time integration: RK", this%rk_order + print *, "Wave speed: ", this%wave_speed + print *, "Final time: ", this%final_time + print *, "Time step: ", this%dt + print *, "Boundary: ", trim(this%boundary_type) + if (trim(this%boundary_type) == 'dirichlet') then + print *, " Dirichlet values: [", this%left_boundary_value, ", ", & + this%right_boundary_value, "]" + end if + print *, "===============================" + end subroutine config_print + + subroutine config_with_reconstruction(this, scheme, order) + type(cfd_config), intent(inout) :: this + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + character(len=20) :: scheme_lower + + ! Convert to lowercase + scheme_lower = scheme + call to_lower_inplace(scheme_lower) + this%recon_scheme = trim(adjustl(scheme_lower)) + + ! Set order + if (present(order)) then + this%spatial_order = order + else + ! Smart defaults + if (index(this%recon_scheme, 'weno') > 0) then + this%spatial_order = 5 + else if (trim(this%recon_scheme) == 'eno') then + this%spatial_order = 3 + else + print *, "[ERROR] Unsupported reconstruction scheme: ", trim(this%recon_scheme) + return + end if + end if + + if (this%verbose) then + print *, "[CONFIG] Reconstruction: ", trim(this%recon_scheme), & + " Order: ", this%spatial_order + end if + end subroutine config_with_reconstruction + + subroutine to_lower_inplace(str) + character(len=*), intent(inout) :: str + integer :: i + + do i = 1, len_trim(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + str(i:i) = char(ichar(str(i:i)) + 32) + end if + end do + end subroutine to_lower_inplace + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01l/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/01l/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..896969bd0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01l/src/infrastructure/mesh.f90 @@ -0,0 +1,74 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, mesh_type, mesh_init, mesh_print_info + + ! mesh + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer :: ncells = 40 + integer :: nnodes + integer :: nx + real(wp), allocatable :: x(:) ! + real(wp), allocatable :: xcc(:) ! + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer, optional, intent(in) :: ncells + + integer :: i + + ! Set + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! computation + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! node + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! cell + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== ===" + print *, ": [", this%xmin, ", ", this%xmax, "]" + print *, ": ", this%ncells + print *, ": ", this%nnodes + print *, " dx: ", this%dx + print *, " L: ", this%L + print *, "==========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..66874a7ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/CMakeLists.txt @@ -0,0 +1,7 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..3fde7746b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,28 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +# Start with just the base module +add_library(flux STATIC + base.f90 + rusanov.f90 +) + +#target_link_libraries(flux PRIVATE core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 明确设置不使用 /Qm64 选项 +if(CMAKE_Fortran_COMPILER_ID MATCHES "IntelLLVM|Intel") + set_target_properties(flux PROPERTIES + Fortran_FLAGS "${CMAKE_Fortran_FLAGS} /Qm64" # 明确设置,避免继承问题 + ) +endif() + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/flux/base.f90 new file mode 100644 index 000000000..7080a7ae4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/flux/base.f90 @@ -0,0 +1,30 @@ +!src/numerics/flux/base.f90 +module flux_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, flux_calculator_base + + ! Base flux calculator type + type :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure :: info => flux_info + procedure :: print_basic_info => flux_print_basic ! 添加辅助方法 + end type flux_calculator_base + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "Flux calculator information:" + print *, " Name: ", trim(this%name) + end subroutine flux_info + + subroutine flux_print_basic(this) + class(flux_calculator_base), intent(in) :: this + print *, " Name: ", trim(this%name) + end subroutine flux_print_basic + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/flux/rusanov.f90 b/example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/flux/rusanov.f90 new file mode 100644 index 000000000..7140f710b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/flux/rusanov.f90 @@ -0,0 +1,41 @@ +! src/numerics/flux/rusanov.f90 +module rusanov_flux_module + use, intrinsic :: iso_fortran_env, only: real64 + use flux_base_module, only: flux_calculator_base + implicit none + + private + public :: real64, rusanov_flux + + type, extends(flux_calculator_base) :: rusanov_flux + real(real64) :: wave_speed_default = 1.0_real64 + contains + procedure :: info => rusanov_info + end type rusanov_flux + + ! 构造函数接口 + interface rusanov_flux + module procedure create_rusanov_flux + end interface + +contains + + ! 构造函数 + type(rusanov_flux) function create_rusanov_flux() result(this) + this%name = "Rusanov" + this%wave_speed_default = 1.0_real64 + end function create_rusanov_flux + + subroutine rusanov_info(this) + class(rusanov_flux), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Flux calculator information:" + call this%print_basic_info() + + ! 添加Rusanov特有信息 + print *, " Type: Rusanov flux" + print *, " Default wave speed: ", this%wave_speed_default + end subroutine rusanov_info + +end module rusanov_flux_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..5e4b938d5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,22 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "Configuring reconstructor module...") + +# Start with just the base module +add_library(reconstructor STATIC + base.f90 + eno.f90 + weno3.f90 +) + +target_link_libraries(reconstructor PRIVATE core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Reconstructor module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..53798d021 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/reconstructor/base.f90 @@ -0,0 +1,33 @@ +!src/numerics/reconstructor/base.f90 +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, reconstructor_base + + ! Base reconstructor type + type :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + contains + procedure :: info => reconstructor_info + procedure :: print_basic_info => reconstructor_print_basic ! 添加一个辅助方法 + end type reconstructor_base + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + print *, "Reconstructor information:" + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_info + + subroutine reconstructor_print_basic(this) + class(reconstructor_base), intent(in) :: this + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_print_basic + +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/reconstructor/eno.f90 b/example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/reconstructor/eno.f90 new file mode 100644 index 000000000..a468b82ef --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/reconstructor/eno.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/eno.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, eno_reconstructor + + type, extends(reconstructor_base) :: eno_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => eno_info + end type eno_reconstructor + + ! 构造函数接口 + interface eno_reconstructor + module procedure create_eno_reconstructor + end interface + +contains + + ! 构造函数 + type(eno_reconstructor) function create_eno_reconstructor() result(this) + this%name = "ENO" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_eno_reconstructor + + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加ENO特有信息 + print *, " Type: ENO reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/reconstructor/weno3.f90 b/example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/reconstructor/weno3.f90 new file mode 100644 index 000000000..5e9542915 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01l/src/numerics/reconstructor/weno3.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/weno3.f90 +module weno3_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno3_reconstructor + + type, extends(reconstructor_base) :: weno3_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno3_info + end type weno3_reconstructor + + ! 构造函数接口 + interface weno3_reconstructor + module procedure create_weno3_reconstructor + end interface + +contains + + ! 构造函数 + type(weno3_reconstructor) function create_weno3_reconstructor() result(this) + this%name = "WENO3" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_weno3_reconstructor + + subroutine weno3_info(this) + class(weno3_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加WENO3特有信息 + print *, " Type: WENO-3 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno3_info + +end module weno3_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01l/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01l/tests/CMakeLists.txt new file mode 100644 index 000000000..0cd64b0c6 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01l/tests/CMakeLists.txt @@ -0,0 +1,43 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +add_executable(test_minimal_simple test_minimal_simple.f90) + +message(STATUS "CMAKE_Fortran_MODULE_DIRECTORY=${CMAKE_Fortran_MODULE_DIRECTORY}") + +#target_include_directories( test_minimal_simple +# PRIVATE +# ${CMAKE_Fortran_MODULE_DIRECTORY} +#) + +target_link_libraries( test_minimal_simple + PRIVATE + infrastructure +) + +add_executable(test_simple_link test_simple_link.f90) +target_link_libraries(test_simple_link + PRIVATE + reconstructor + flux +) + + +#add_executable(test_factory_simple test_factory_simple.f90) + +#target_link_libraries( test_factory_simple +# PRIVATE +# core +# infrastructure +# reconstructor +# flux +#) + +add_executable(test_factory_simple test_factory_simple.f90) +target_link_libraries(test_factory_simple + PRIVATE + core + infrastructure + reconstructor + flux +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01l/tests/test_factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/01l/tests/test_factory.f90 new file mode 100644 index 000000000..c62d5f8ab --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01l/tests/test_factory.f90 @@ -0,0 +1,154 @@ +! tests/test_factory.f90 +program test_factory + use registry_module + use config_module + use mesh_module + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + class(*), allocatable :: instance + class(reconstructor_base), allocatable :: recon + class(flux_calculator_base), allocatable :: flux_calc + real(wp), allocatable :: q(:), qL(:), qR(:), flux(:) + integer :: i, n + + print *, "=== Factory Pattern Test ===" + print *, "" + + ! Initialize systems + call initialize_registry(verbose=.true.) + + ! Register components with factories + print *, "1. Registering components with factories..." + call register_component_with_factory("reconstructor", "eno", create_eno, 3) + call register_component_with_factory("reconstructor", "weno3", create_weno3, 3) + call register_component_with_factory("flux", "rusanov", create_rusanov) + + call component_registry%list_all() + print *, "" + + ! Test creating ENO reconstructor + print *, "2. Creating ENO reconstructor..." + call create_component("reconstructor", "eno", instance) + + if (allocated(instance)) then + select type (inst => instance) + type is (eno_reconstructor) + allocate(recon, source=inst) + print *, "ENO reconstructor created successfully" + call recon%info() + print *, "" + + ! Test reconstruction + n = 10 + allocate(q(0:n+1), qL(n), qR(n)) + + ! Initialize test data (sine wave) + do i = 0, n+1 + q(i) = sin(2.0_wp * 3.141592653589793_wp * real(i-1, wp) / real(n, wp)) + end do + + print *, "Testing ENO reconstruction..." + call recon%reconstruct(q, qL, qR) + + print *, "q (internal):" + do i = 1, n + write(*, '(I3, F10.6)') i, q(i) + end do + + print *, "qL (left interface values):" + do i = 1, n + write(*, '(I3, F10.6)') i, qL(i) + end do + + deallocate(q, qL, qR) + class default + print *, "[ERROR] Wrong type for ENO reconstructor" + end select + else + print *, "[ERROR] Failed to create ENO reconstructor" + end if + print *, "" + + ! Test creating WENO3 reconstructor + print *, "3. Creating WENO3 reconstructor..." + if (allocated(instance)) deallocate(instance) + call create_component("reconstructor", "weno3", instance) + + if (allocated(instance)) then + select type (inst => instance) + type is (weno3_reconstructor) + if (allocated(recon)) deallocate(recon) + allocate(recon, source=inst) + print *, "WENO3 reconstructor created successfully" + call recon%info() + class default + print *, "[ERROR] Wrong type for WENO3 reconstructor" + end select + else + print *, "[ERROR] Failed to create WENO3 reconstructor" + end if + print *, "" + + ! Test creating Rusanov flux calculator + print *, "4. Creating Rusanov flux calculator..." + if (allocated(instance)) deallocate(instance) + call create_component("flux", "rusanov", instance) + + if (allocated(instance)) then + select type (inst => instance) + type is (rusanov_flux) + allocate(flux_calc, source=inst) + print *, "Rusanov flux calculator created successfully" + call flux_calc%info() + print *, "" + + ! Test flux computation + n = 5 + allocate(qL(n), qR(n), flux(n)) + + ! Initialize test data + do i = 1, n + qL(i) = 1.0_wp + 0.1_wp * real(i-1, wp) + qR(i) = 1.0_wp + 0.1_wp * real(i, wp) + end do + + print *, "Testing Rusanov flux computation..." + call flux_calc%compute(qL, qR, flux, 1.0_wp) + + print *, "qL:" + do i = 1, n + write(*, '(I3, F10.6)') i, qL(i) + end do + + print *, "qR:" + do i = 1, n + write(*, '(I3, F10.6)') i, qR(i) + end do + + print *, "Flux:" + do i = 1, n + write(*, '(I3, F10.6)') i, flux(i) + end do + + deallocate(qL, qR, flux) + class default + print *, "[ERROR] Wrong type for Rusanov flux" + end select + else + print *, "[ERROR] Failed to create Rusanov flux calculator" + end if + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Factory pattern test completed successfully ===" + +end program test_factory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01l/tests/test_factory_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/01l/tests/test_factory_simple.f90 new file mode 100644 index 000000000..d4139bb1a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01l/tests/test_factory_simple.f90 @@ -0,0 +1,86 @@ +!tests/test_factory_simple.f90 +program test_factory_simple + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module, only: initialize_registry, cleanup_registry, & + register_component_simple, has_component + use config_module, only: cfd_config, config_print + use mesh_module, only: mesh_type + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(eno_reconstructor) :: eno + type(weno3_reconstructor) :: weno3 + type(rusanov_flux) :: rusanov + + print *, "=== Factory Pattern Simple Test ===" + print *, "" + + ! Test 1: Basic systems + print *, "1. Testing basic systems..." + print *, "-----------------------------" + call config_print(config) + + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 2: Creating reconstructors + print *, "2. Testing reconstructors..." + print *, "------------------------------" + + ! 创建并测试ENO重构器 + print *, "Creating ENO reconstructor..." + eno = eno_reconstructor() ! 使用构造函数 + call eno%info() ! 必须调用info方法 + + print *, "" + print *, "Creating WENO3 reconstructor..." + weno3 = weno3_reconstructor() ! 使用构造函数 + call weno3%info() ! 必须调用info方法 + print *, "" + + ! Test 3: Creating flux calculator + print *, "3. Testing flux calculator..." + print *, "-------------------------------" + + print *, "Creating Rusanov flux calculator..." + rusanov = rusanov_flux() ! 使用构造函数 + call rusanov%info() ! 必须调用info方法 + print *, "" + + ! Test 4: Registry integration + print *, "4. Testing registry..." + print *, "----------------------" + + call initialize_registry(verbose=.true.) + + ! Register components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("flux", "rusanov") + + ! Check registration + if (has_component("reconstructor", "eno")) then + print *, "[OK] ENO reconstructor registered successfully" + end if + + if (has_component("reconstructor", "weno3")) then + print *, "[OK] WENO3 reconstructor registered successfully" + end if + + if (has_component("flux", "rusanov")) then + print *, "[OK] Rusanov flux registered successfully" + end if + + print *, "" + call cleanup_registry() + + print *, "=== Factory pattern simple test completed successfully ===" + +end program test_factory_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01l/tests/test_minimal.f90 b/example/1d-linear-convection/weno3/fortran/registry/01l/tests/test_minimal.f90 new file mode 100644 index 000000000..807d0bc2c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01l/tests/test_minimal.f90 @@ -0,0 +1,108 @@ +! tests/test_minimal.f90 +program test_minimal + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i ! Declare loop variable here + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", component_registry%size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components + print *, "5. Testing available components" + print *, "--------------------------------" + + ! Use a separate block to avoid allocation issues + call test_available_components() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Minimal test completed successfully ===" + +contains + + ! Internal subroutine for testing available components + subroutine test_available_components() + character(len=:), allocatable :: names(:) + integer, allocatable :: orders(:) + integer :: j + + call get_available_components("reconstructor", names, orders) + + if (allocated(names)) then + print *, "Reconstructors:" + do j = 1, size(names) + print *, " - ", trim(names(j)) + if (allocated(orders)) then + print *, " Order: ", orders(j) + end if + end do + else + print *, "No reconstructors found" + end if + end subroutine test_available_components + +end program test_minimal \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01l/tests/test_minimal_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/01l/tests/test_minimal_simple.f90 new file mode 100644 index 000000000..6213e8f89 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01l/tests/test_minimal_simple.f90 @@ -0,0 +1,84 @@ +! tests/test_minimal_simple.f90 +program test_minimal_simple + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Simple Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components (simple version) + print *, "5. Testing registry functionality" + print *, "---------------------------------" + print *, "Registry initialized: ", registry_is_initialized() + print *, "Number of components: ", registry_get_size() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Simple test completed successfully ===" + +end program test_minimal_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01l/tests/test_simple_link.f90 b/example/1d-linear-convection/weno3/fortran/registry/01l/tests/test_simple_link.f90 new file mode 100644 index 000000000..807d0bc2c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01l/tests/test_simple_link.f90 @@ -0,0 +1,108 @@ +! tests/test_minimal.f90 +program test_minimal + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i ! Declare loop variable here + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", component_registry%size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components + print *, "5. Testing available components" + print *, "--------------------------------" + + ! Use a separate block to avoid allocation issues + call test_available_components() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Minimal test completed successfully ===" + +contains + + ! Internal subroutine for testing available components + subroutine test_available_components() + character(len=:), allocatable :: names(:) + integer, allocatable :: orders(:) + integer :: j + + call get_available_components("reconstructor", names, orders) + + if (allocated(names)) then + print *, "Reconstructors:" + do j = 1, size(names) + print *, " - ", trim(names(j)) + if (allocated(orders)) then + print *, " Order: ", orders(j) + end if + end do + else + print *, "No reconstructors found" + end if + end subroutine test_available_components + +end program test_minimal \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01m/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01m/CMakeLists.txt new file mode 100644 index 000000000..ef66d5848 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01m/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01m/README.md b/example/1d-linear-convection/weno3/fortran/registry/01m/README.md new file mode 100644 index 000000000..c4889757b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01m/README.md @@ -0,0 +1,221 @@ +# Fortran CFD Project + +基于工厂模式和注册系统的CFD求解器框架。 + +## 项目结构 + +``` +fortran_cfd/ +├── CMakeLists.txt # 根CMake配置 +├── scripts/ # 构建脚本 +│ ├── build.py # Python构建脚本(推荐) +│ ├── build.bat # Batch包装脚本 +│ ├── build.ps1 # PowerShell包装脚本 +│ └── requirements.txt # Python依赖 +├── src/ # 源代码 +│ ├── CMakeLists.txt +│ ├── core/ # 核心模块(注册系统、工厂接口) +│ ├── infrastructure/ # 基础设施(配置、网格) +│ └── numerics/ # 数值方法 +├── tests/ # 测试 +│ ├── CMakeLists.txt +│ ├── test_minimal_simple.f90 # 简单测试 +│ └── test_factory.f90 # 工厂模式测试 +└── README.md # 项目说明(本文件) +``` + +## 快速开始 + +### 使用Python脚本(推荐) + +```bash +# 进入脚本目录 +cd scripts + +# 基本构建 +python build.py + +# 清理并构建 +python build.py --clean + +# 发布构建 +python build.py --build-type Release --clean + +# 并行构建(使用所有核心) +python build.py -j + +# 不运行测试 +python build.py --no-tests + +# 查看帮助 +python build.py --help +``` + +### 使用批处理脚本 + +```bash +cd scripts +.\build.bat +``` + +### 使用PowerShell脚本 + +```powershell +cd scripts +.\build.ps1 +``` + +## 手动构建 + +```bash +# 设置Intel环境 +cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && powershell' + +# 配置项目 +mkdir build +cd build +cmake -G "Visual Studio 17 2022" -A x64 -T fortran=ifx .. + +# 构建项目 +cmake --build . --config Debug + +# 运行测试 +Debug\test_simple.exe +Debug\test_factory.exe +``` + +## 功能特性 + +✅ 组件注册系统 +✅ 工厂模式 +✅ ENO/WENO重构器 +✅ Rusanov通量计算器 +✅ 可扩展架构 +✅ 自动化测试 + +## 依赖 + +- Intel oneAPI(包含ifx编译器) +- CMake 3.12+ +- Python 3.6+(仅用于构建脚本) + +## 源代码文件 + +### 核心模块 +- `src/core/registry.f90` - 组件注册系统 +- `src/core/factory_interfaces.f90` - 工厂接口 + +### 基础设施 +- `src/infrastructure/config.f90` - 配置管理 +- `src/infrastructure/mesh.f90` - 网格生成 + +### 数值方法 +- `src/numerics/reconstructor/base.f90` - 重构器基类 +- `src/numerics/reconstructor/eno.f90` - ENO重构器 +- `src/numerics/reconstructor/weno3.f90` - WENO-3重构器 +- `src/numerics/flux/base.f90` - 通量计算器基类 +- `src/numerics/flux/rusanov.f90` - Rusanov通量 + +### 测试 +- `tests/test_minimal_simple.f90` - 简单功能测试 +- `tests/test_factory.f90` - 工厂模式测试 + +## 构建脚本 + +### Python脚本 (build.py) +主构建脚本,支持: +- 自动设置Intel oneAPI环境 +- 参数化构建选项 +- 并行编译 +- 自动化测试 +- 彩色输出 + +### Batch脚本 (build.bat) +Windows批处理包装器,调用Python脚本。 + +### PowerShell脚本 (build.ps1) +PowerShell包装器,调用Python脚本。 + +## 示例输出 + +成功构建后,会看到类似输出: + +``` +============================================================ + Fortran CFD Project Builder +============================================================ + +[1/4] Setting up Intel Fortran compiler... +✓ Intel oneAPI environment configured + +[2/4] Preparing build directory... +✓ Cleaned build directory + +[3/4] Configuring project... + $ cmake -G Visual Studio 17 2022 -A x64 -T fortran=ifx -DCMAKE_BUILD_TYPE=Debug .. +✓ CMake configuration successful + +[4/4] Building project... + $ cmake --build . --config Debug +✓ Build completed in 12.3 seconds + +============================================================ + Running Tests +============================================================ + +[TEST] Simple functionality test... +✓ Simple test passed + +[TEST] Factory pattern test... +✓ Factory test passed + +============================================================ + Build Summary +============================================================ +✓ Build directory: D:\path\to\build +✓ Build type: Debug +✓ Total time: 15.2s +``` + +## 开发指南 + +### 添加新组件 + +1. 在相应模块中创建Fortran源文件 +2. 实现工厂函数 +3. 使用`register_component_with_factory`注册组件 +4. 添加测试 + +### 扩展架构 + +项目采用模块化设计: +1. **注册系统** - 管理所有可用的组件 +2. **工厂模式** - 动态创建组件实例 +3. **抽象接口** - 确保组件兼容性 +4. **配置驱动** - 通过配置文件控制行为 + +## 故障排除 + +### 常见问题 + +1. **Intel环境未找到** + - 检查Intel oneAPI安装路径 + - 手动运行`setvars.bat` + +2. **CMake配置失败** + - 确保使用正确的生成器:`Visual Studio 17 2022` + - 检查Fortran编译器是否可用 + +3. **构建失败** + - 检查依赖项是否完整 + - 查看详细的错误信息 + +### 调试建议 + +- 使用`--verbose`参数获取详细输出 +- 检查`build/CMakeCache.txt`了解配置详情 +- 查看编译器错误日志 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01m/scripts/build.bat b/example/1d-linear-convection/weno3/fortran/registry/01m/scripts/build.bat new file mode 100644 index 000000000..6fd6dc031 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01m/scripts/build.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Project Builder +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python build script with full Intel environment support... +echo. + +python build.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Build failed + pause + exit /b 1 +) + +echo. +echo [INFO] Build completed successfully! +echo. +echo [INFO] To run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01m/scripts/build.py b/example/1d-linear-convection/weno3/fortran/registry/01m/scripts/build.py new file mode 100644 index 000000000..3bf6d5375 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01m/scripts/build.py @@ -0,0 +1,629 @@ +#!/usr/bin/env python3 +""" +Fortran CFD Project Builder - 完整Python解决方案 +在Python内部处理Intel oneAPI环境配置 +""" + +import os +import sys +import subprocess +import shutil +import argparse +import time +import platform +import tempfile +from pathlib import Path + +class IntelEnvironment: + """Intel oneAPI环境管理器""" + + def __init__(self): + self.setvars_path = None + self.env_vars = {} + + def find_setvars(self): + """查找setvars.bat文件""" + possible_paths = [ + r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat", + r"C:\Program Files\Intel\oneAPI\setvars.bat", + r"C:\Program Files (x86)\Intel\oneAPI\compiler\latest\env\vars.bat", + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\setvars.bat"), + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\compiler\latest\env\vars.bat"), + ] + + for path in possible_paths: + if os.path.exists(path): + self.setvars_path = path + return True + + return False + + def setup_environment(self): + """设置Intel环境""" + if not self.find_setvars(): + return False + + try: + # 创建临时的批处理文件来捕获环境变量 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + f.write(f'@echo off\n') + f.write(f'call "{self.setvars_path}" >nul 2>&1\n') + f.write(f'set\n') # 输出所有环境变量 + temp_bat = f.name + + # 运行批处理文件并捕获输出 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + shell=True + ) + + # 解析环境变量 + for line in result.stdout.split('\n'): + line = line.strip() + if '=' in line: + key, value = line.split('=', 1) + self.env_vars[key.strip()] = value.strip() + + # 清理临时文件 + os.unlink(temp_bat) + + # 更新当前进程的环境变量 + os.environ.update(self.env_vars) + + return True + + except Exception as e: + print(f"设置Intel环境失败: {e}") + return False + + def get_compiler_info(self): + """获取编译器信息""" + info = {} + + # 检查ifx编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + env={**os.environ, **self.env_vars} if self.env_vars else os.environ + ) + + if result.returncode == 0: + for line in result.stdout.split('\n'): + if 'Version' in line or '版本' in line: + info['ifx_version'] = line.strip() + break + except: + pass + + # 检查环境变量 + info['ifx_root'] = self.env_vars.get('IFX_ROOT', '') + info['compiler_root'] = self.env_vars.get('ONEAPI_ROOT', '') + + return info + +class BuildSystem: + """构建系统主类""" + + def __init__(self): + self.project_root = Path(__file__).parent.parent + self.build_dir = self.project_root / "build" + self.intel_env = IntelEnvironment() + + # 设置控制台编码 + if sys.platform == "win32": + try: + import ctypes + # 设置控制台输出为UTF-8 + ctypes.windll.kernel32.SetConsoleOutputCP(65001) + except: + pass + + def print_header(self, text): + """打印标题""" + print(f"\n{'='*70}") + print(f" {text}") + print(f"{'='*70}\n") + + def print_step(self, step, total, message): + """打印步骤""" + print(f"[{step}/{total}] {message}...") + + def print_success(self, message): + """打印成功""" + print(f"\033[92m✓ {message}\033[0m") + + def print_error(self, message): + """打印错误""" + print(f"\033[91m✗ {message}\033[0m") + + def print_warning(self, message): + """打印警告""" + print(f"\033[93m! {message}\033[0m") + + def print_info(self, message): + """打印信息""" + print(f"\033[94mℹ {message}\033[0m") + + def check_prerequisites(self): + """检查前提条件""" + self.print_step(1, 6, "检查前提条件") + + # 检查Python版本 + python_version = sys.version.split()[0] + self.print_info(f"Python版本: {python_version}") + + # 检查平台 + self.print_info(f"平台: {platform.system()} {platform.release()}") + self.print_info(f"处理器核心数: {os.cpu_count()}") + + # 检查CMake + try: + result = subprocess.run( + ["cmake", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + version_line = result.stdout.split('\n')[0] + self.print_success(f"CMake: {version_line}") + else: + self.print_error("CMake未找到") + return False + except FileNotFoundError: + self.print_error("CMake未安装") + return False + + return True + + def setup_intel_environment(self, args): + """设置Intel环境""" + self.print_step(2, 6, "配置Intel oneAPI环境") + + if not self.intel_env.find_setvars(): + self.print_warning("未找到Intel oneAPI setvars.bat") + self.print_info("将尝试使用系统环境中的编译器") + + # 检查是否能直接访问编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + self.print_success("Intel编译器在系统PATH中找到") + return True + else: + self.print_warning("Intel编译器未在PATH中找到") + except: + self.print_warning("无法访问Intel编译器") + + return True # 继续,让CMake自己找编译器 + + # 设置环境 + if self.intel_env.setup_environment(): + compiler_info = self.intel_env.get_compiler_info() + + if compiler_info.get('ifx_version'): + self.print_success(f"Intel Fortran编译器: {compiler_info['ifx_version']}") + elif compiler_info.get('ifx_root'): + self.print_success(f"Intel编译器路径: {compiler_info['ifx_root']}") + else: + self.print_success("Intel oneAPI环境配置完成") + + return True + else: + self.print_warning("Intel环境配置失败,将继续使用系统环境") + return True + + def clean_build_directory(self, args): + """清理构建目录""" + if args.clean and self.build_dir.exists(): + self.print_info("清理构建目录...") + try: + shutil.rmtree(self.build_dir) + self.print_success("构建目录已清理") + except Exception as e: + self.print_error(f"清理失败: {e}") + if not args.force: + return False + return True + + def run_command(self, cmd, cwd=None, check=True, env=None): + """运行命令""" + if isinstance(cmd, list): + cmd_str = ' '.join(str(c) for c in cmd if c) + else: + cmd_str = str(cmd) + + print(f" \033[96m$\033[0m {cmd_str}") + + try: + # 合并环境变量 + exec_env = os.environ.copy() + if env: + exec_env.update(env) + if self.intel_env.env_vars: + exec_env.update(self.intel_env.env_vars) + + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=False, + env=exec_env + ) + + # 处理输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or '完成' in line or '生成' in line: + print(f" \033[92m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + if check and result.returncode != 0: + self.print_error(f"命令执行失败,退出码: {result.returncode}") + return False + + return True + + except Exception as e: + self.print_error(f"命令执行异常: {e}") + return False + + def configure_cmake(self, args): + """配置CMake""" + self.print_step(3, 6, "配置CMake项目") + + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + f"-DCMAKE_BUILD_TYPE={args.build_type}", + ] + + if args.compiler == "ifx": + cmake_cmd.extend(["-T", "fortran=ifx"]) + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + success = self.run_command(cmake_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("CMake配置完成") + else: + self.print_error("CMake配置失败") + + return success + + def build_project(self, args): + """构建项目""" + self.print_step(4, 6, "构建项目") + + build_cmd = [ + "cmake", + "--build", ".", + "--config", args.build_type, + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + success = self.run_command(build_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("项目构建完成") + else: + self.print_error("构建失败") + + return success + + def run_tests_with_environment(self, test_exe): + """运行单个测试,确保有Intel环境""" + try: + # 创建临时的批处理文件来运行测试 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'"{test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'"{test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + return result + + except Exception as e: + print(f"运行测试失败: {e}") + return None + + def run_tests(self, args): + """运行测试""" + self.print_step(5, 6, "运行测试") + + # 查找测试可执行文件 + test_dir = self.build_dir / "bin" / args.build_type + if not test_dir.exists(): + test_dir = self.build_dir / "bin" + if not test_dir.exists(): + test_dir = self.build_dir + + test_files = list(test_dir.glob("test_*.exe")) + + if not test_files: + self.print_warning("未找到测试程序") + return True + + all_passed = True + + for test_exe in sorted(test_files): + test_name = test_exe.stem + self.print_info(f"运行测试: {test_name}") + print(f" {'-'*50}") + + # 运行测试 + result = self.run_tests_with_environment(str(test_exe)) + + if result is None: + self.print_error(f" {test_name} 运行失败") + all_passed = False + continue + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + print(f" {line}") + + if result.returncode == 0: + self.print_success(f" {test_name} 通过") + else: + self.print_error(f" {test_name} 失败 (退出码: {result.returncode})") + all_passed = False + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + print() # 空行 + + return all_passed + + def create_test_runner(self, args): + """创建独立的测试运行器""" + self.print_step(6, 6, "创建测试运行器") + + runner_path = self.build_dir / "run_tests.bat" + + content = f'''@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Test Runner +echo ======================================== +echo. + +REM Setup Intel oneAPI environment +set "SETVARS_PATH={self.intel_env.setvars_path or ''}" +if exist "%SETVARS_PATH%" ( + call "%SETVARS_PATH%" >nul + echo [INFO] Intel environment configured +) else ( + echo [WARNING] Intel environment not found + echo [WARNING] Tests may fail without runtime libraries +) + +echo. + +REM Run all test executables +set "TEST_COUNT=0" +set "PASS_COUNT=0" + +for %%f in ("bin\\{args.build_type}\\test_*.exe") do ( + set /a TEST_COUNT+=1 + echo [TEST %%f] + echo {'-'*50} + + %%f + if errorlevel 1 ( + echo [FAILED] %%f + ) else ( + echo [PASSED] %%f + set /a PASS_COUNT+=1 + ) + echo. +) + +echo ======================================== +echo Tests: %PASS_COUNT%/%TEST_COUNT% passed +if %PASS_COUNT% equ %TEST_COUNT% ( + echo [SUCCESS] All tests passed! +) else ( + echo [FAILURE] Some tests failed +) +echo ======================================== + +pause +''' + + with open(runner_path, 'w', encoding='utf-8') as f: + f.write(content) + + self.print_success(f"测试运行器已创建: {runner_path}") + self.print_info(f"使用方法: cd build && run_tests.bat") + + return runner_path + + def generate_report(self, args, build_time, tests_passed): + """生成构建报告""" + self.print_header("构建完成") + + print(f"项目: {self.project_root.name}") + print(f"构建类型: {args.build_type}") + print(f"编译器: {args.compiler}") + print(f"并行作业: {args.jobs}") + print(f"总耗时: {build_time:.1f}秒") + print(f"测试结果: {'全部通过' if tests_passed else '有失败'}") + + # 显示生成的可执行文件 + bin_dir = self.build_dir / "bin" / args.build_type + if bin_dir.exists(): + print(f"\n生成的可执行文件:") + for exe in sorted(bin_dir.glob("*.exe")): + size_mb = exe.stat().st_size / (1024 * 1024) + print(f" • {exe.name} ({size_mb:.2f} MB)") + + # 显示测试运行器信息 + runner_path = self.build_dir / "run_tests.bat" + if runner_path.exists(): + print(f"\n独立测试运行器:") + print(f" • {runner_path.name}") + print(f" 在Intel oneAPI环境中运行所有测试") + + def run(self): + """运行构建系统""" + parser = argparse.ArgumentParser( + description="Fortran CFD项目构建工具", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认构建 + %(prog)s --clean # 清理后构建 + %(prog)s --build-type Release # Release构建 + %(prog)s --no-tests # 只构建,不运行测试 + %(prog)s -j8 --verbose # 8线程并行构建,详细输出 + """ + ) + + parser.add_argument("--build-type", choices=["Debug", "Release"], + default="Debug", help="构建类型") + parser.add_argument("--compiler", choices=["ifx", "ifort"], + default="ifx", help="Fortran编译器") + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-tests", action="store_true", + help="跳过测试") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + + args = parser.parse_args() + + # 开始构建 + start_time = time.time() + + self.print_header("Fortran CFD 项目构建系统") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 检查前提条件 + if not self.check_prerequisites(): + if not args.force: + return 1 + + # 2. 设置Intel环境 + if not self.setup_intel_environment(args): + if not args.force: + return 1 + + # 3. 清理目录 + if not self.clean_build_directory(args): + if not args.force: + return 1 + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 4. 配置CMake + if not self.configure_cmake(args): + if not args.force: + return 1 + + # 5. 构建项目 + if not self.build_project(args): + if not args.force: + return 1 + + # 6. 运行测试和创建测试运行器 + tests_passed = True + if not args.no_tests: + tests_passed = self.run_tests(args) + + # 创建测试运行器 + self.create_test_runner(args) # 传递 args 参数 + + # 7. 生成报告 + build_time = time.time() - start_time + self.generate_report(args, build_time, tests_passed) + + return 0 if tests_passed else 1 + + except KeyboardInterrupt: + self.print_error("\n构建被用户中断") + return 1 + except Exception as e: + self.print_error(f"构建过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + builder = BuildSystem() + return builder.run() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01m/scripts/requirements.txt b/example/1d-linear-convection/weno3/fortran/registry/01m/scripts/requirements.txt new file mode 100644 index 000000000..d21a12b45 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01m/scripts/requirements.txt @@ -0,0 +1,2 @@ +# 构建脚本的Python依赖(可选) +# 目前没有特殊依赖,保持空文件或添加未来可能需要的包 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01m/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01m/src/CMakeLists.txt new file mode 100644 index 000000000..ee38952bb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01m/src/CMakeLists.txt @@ -0,0 +1,14 @@ +# ==================== src\CMakeLists.txt ==================== + +# src/CMakeLists.txt +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(core) +add_subdirectory(infrastructure) +add_subdirectory(numerics) + +message(STATUS "源代码目录配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01m/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01m/src/core/CMakeLists.txt new file mode 100644 index 000000000..bcfd024d8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01m/src/core/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/core/CMakeLists.txt +message(STATUS "配置核心模块...") + +add_library(core STATIC + registry.f90 + factory_interfaces.f90 +) + +target_include_directories(core PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 安装规则 +install(TARGETS core + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) +install(FILES + ${CMAKE_Fortran_MODULE_DIRECTORY}/registry_module.mod + ${CMAKE_Fortran_MODULE_DIRECTORY}/factory_interfaces.mod + DESTINATION include/fortran_cfd/core +) + +message(STATUS "核心模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01m/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/01m/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..a960167c3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01m/src/core/factory_interfaces.f90 @@ -0,0 +1,17 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, factory_procedure + + ! Factory procedure interface + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01m/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/01m/src/core/registry.f90 new file mode 100644 index 000000000..bf9028c3a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01m/src/core/registry.f90 @@ -0,0 +1,318 @@ +! src/core/registry.f90 +module registry_module + use, intrinsic :: iso_fortran_env, only: real64 + use factory_interfaces, only: factory_procedure + implicit none + + private + + ! Public interface + public :: real64, component_info, component_registry + public :: register_component_simple, initialize_registry, cleanup_registry + public :: has_component, get_available_components + public :: registry_is_initialized, registry_get_size ! + + ! Type definitions (simplified, no factory for now) + type :: component_info + character(len=32) :: category = "" + character(len=32) :: name = "" + integer :: order = 0 + contains + procedure :: print => ci_print + end type component_info + + type :: component_registry_type + private + type(component_info), allocatable :: components(:) + integer :: count = 0 + integer :: capacity = 100 + logical :: verbose = .true. + logical :: initialized = .false. + contains + procedure :: register => cr_register + procedure :: get => cr_get + procedure :: list_all => cr_list_all + procedure :: clear => cr_clear + procedure :: size => cr_size + procedure :: is_initialized => cr_is_initialized ! + end type component_registry_type + + ! Global registry instance + type(component_registry_type), save :: component_registry + +contains + + ! ==================== PUBLIC API ==================== + + ! Initialize registry + subroutine initialize_registry(initial_capacity, verbose) + integer, optional, intent(in) :: initial_capacity + logical, optional, intent(in) :: verbose + + if (component_registry%initialized) then + if (component_registry%verbose) then + print *, "[INFO] Registry already initialized" + end if + return + end if + + if (present(initial_capacity)) then + component_registry%capacity = max(10, initial_capacity) + end if + + if (present(verbose)) then + component_registry%verbose = verbose + end if + + ! Allocate array + allocate(component_registry%components(component_registry%capacity)) + + component_registry%initialized = .true. + component_registry%count = 0 + + if (component_registry%verbose) then + print *, "[INIT] Registry initialized, capacity:", component_registry%capacity + end if + end subroutine initialize_registry + + ! Cleanup registry + subroutine cleanup_registry + call component_registry%clear() + if (component_registry%verbose) then + print *, "[CLEANUP] Registry cleaned up" + end if + end subroutine cleanup_registry + + ! Simple registration (no factory) + subroutine register_component_simple(category, name) + character(len=*), intent(in) :: category, name + + type(component_info) :: info + + info%category = to_lower(trim(adjustl(category))) + info%name = to_lower(trim(adjustl(name))) + info%order = 0 + + call component_registry%register(info) + end subroutine register_component_simple + + ! Check if component exists + function has_component(category, name) result(found) + character(len=*), intent(in) :: category, name + logical :: found + + type(component_info) :: info + character(len=32) :: cat_lower, name_lower + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + info = component_registry%get(cat_lower, name_lower) + found = (len_trim(info%category) > 0) + end function has_component + + ! Get available components in a category + subroutine get_available_components(category, names, orders) + character(len=*), intent(in) :: category + character(len=:), allocatable, intent(out), optional :: names(:) + integer, allocatable, intent(out), optional :: orders(:) + + character(len=32) :: cat_lower + integer :: i, count, idx + type(component_info) :: info + + cat_lower = to_lower(trim(adjustl(category))) + + ! Count components in this category + count = 0 + do i = 1, component_registry%count + if (component_registry%components(i)%category == cat_lower) then + count = count + 1 + end if + end do + + ! Allocate arrays if requested + if (present(names)) then + allocate(character(len=32) :: names(count)) + end if + + if (present(orders)) then + allocate(orders(count)) + end if + + ! Fill arrays + idx = 1 + do i = 1, component_registry%count + if (component_registry%components(i)%category == cat_lower) then + info = component_registry%components(i) + if (present(names)) then + names(idx) = info%name + end if + if (present(orders)) then + orders(idx) = info%order + end if + idx = idx + 1 + end if + end do + end subroutine get_available_components + + ! Public function to check if registry is initialized + function registry_is_initialized() result(is_initialized) + logical :: is_initialized + is_initialized = component_registry%is_initialized() + end function registry_is_initialized + + ! Public function to get registry size + function registry_get_size() result(size_val) + integer :: size_val + size_val = component_registry%size() + end function registry_get_size + + ! ==================== COMPONENT INFO METHODS ==================== + + subroutine ci_print(this) + class(component_info), intent(in) :: this + + if (this%order > 0) then + print *, " [", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ")]" + else + print *, " [", trim(this%category), ".", trim(this%name), "]" + end if + end subroutine ci_print + + ! ==================== REGISTRY INTERNAL METHODS ==================== + + subroutine cr_register(this, info) + class(component_registry_type), intent(inout) :: this + type(component_info), intent(in) :: info + + type(component_info), allocatable :: temp(:) + integer :: i + + if (.not. this%initialized) then + error stop "[ERROR] Registry not initialized, call initialize_registry first" + end if + + ! Check if already exists + do i = 1, this%count + if (this%components(i)%category == info%category .and. & + this%components(i)%name == info%name) then + if (this%verbose) then + print *, "[WARN] Overwriting: ", & + trim(info%category), ".", trim(info%name) + end if + this%components(i) = info + return + end if + end do + + ! Expand array if needed + if (this%count >= this%capacity) then + this%capacity = this%capacity * 2 + allocate(temp(this%capacity)) + temp(1:this%count) = this%components(1:this%count) + call move_alloc(temp, this%components) + + if (this%verbose) then + print *, "[INFO] Registry expanded to capacity:", this%capacity + end if + end if + + ! Add component + this%count = this%count + 1 + this%components(this%count) = info + + if (this%verbose) then + print *, "[OK] Registered: ", trim(info%category), ".", trim(info%name) + end if + end subroutine cr_register + + function cr_get(this, category, name) result(info) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category, name + type(component_info) :: info + + integer :: i + + ! Initialize return value as empty + info%category = "" + info%name = "" + info%order = 0 + + if (.not. this%initialized) then + return + end if + + do i = 1, this%count + if (this%components(i)%category == category .and. & + this%components(i)%name == name) then + info = this%components(i) + return + end if + end do + end function cr_get + + subroutine cr_list_all(this) + class(component_registry_type), intent(in) :: this + integer :: i + + if (.not. this%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + print *, "=== Registry Contents (", this%count, " components) ===" + + if (this%count == 0) then + print *, " (empty)" + return + end if + + ! Show components grouped by category + do i = 1, this%count + call this%components(i)%print() + end do + + print *, "===========================================" + end subroutine cr_list_all + + subroutine cr_clear(this) + class(component_registry_type), intent(inout) :: this + + if (allocated(this%components)) then + deallocate(this%components) + end if + + this%count = 0 + this%capacity = 100 + this%initialized = .false. + end subroutine cr_clear + + integer function cr_size(this) + class(component_registry_type), intent(in) :: this + cr_size = this%count + end function cr_size + + logical function cr_is_initialized(this) + class(component_registry_type), intent(in) :: this + cr_is_initialized = this%initialized + end function cr_is_initialized + + ! ==================== UTILITY FUNCTIONS ==================== + + function to_lower(str) result(lower_str) + character(len=*), intent(in) :: str + character(len=len(str)) :: lower_str + integer :: i + + do i = 1, len(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + lower_str(i:i) = char(ichar(str(i:i)) + 32) + else + lower_str(i:i) = str(i:i) + end if + end do + end function to_lower + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01m/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01m/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..46964ce79 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01m/src/infrastructure/CMakeLists.txt @@ -0,0 +1,20 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "配置基础设施模块...") + +add_library(infrastructure STATIC + config.f90 + mesh.f90 +) + +target_link_libraries(infrastructure PRIVATE core) + +target_include_directories(infrastructure PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS infrastructure + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "基础设施模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01m/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/01m/src/infrastructure/config.f90 new file mode 100644 index 000000000..d3b1e0dfb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01m/src/infrastructure/config.f90 @@ -0,0 +1,90 @@ +! src/infrastructure/config.f90 +module config_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, cfd_config, config_print, config_with_reconstruction + + ! CFD configuration type + type :: cfd_config + character(len=20) :: ic_type = "step" + character(len=20) :: recon_scheme = "eno" + character(len=20) :: flux_type = "rusanov" + integer :: rk_order = 1 + real(real64) :: wave_speed = 1.0_real64 + real(real64) :: final_time = 0.625_real64 + real(real64) :: dt = 0.025_real64 + character(len=20) :: boundary_type = "periodic" + real(real64) :: left_boundary_value = 1.0_real64 + real(real64) :: right_boundary_value = 2.0_real64 + integer :: spatial_order = 2 + logical :: verbose = .true. + end type cfd_config + +contains + + subroutine config_print(this) + type(cfd_config), intent(in) :: this + + print *, "=== CFD Configuration ===" + print *, "Initial condition: ", trim(this%ic_type) + print *, "Reconstruction: ", trim(this%recon_scheme), " (order:", this%spatial_order, ")" + print *, "Flux type: ", trim(this%flux_type) + print *, "Time integration: RK", this%rk_order + print *, "Wave speed: ", this%wave_speed + print *, "Final time: ", this%final_time + print *, "Time step: ", this%dt + print *, "Boundary: ", trim(this%boundary_type) + if (trim(this%boundary_type) == 'dirichlet') then + print *, " Dirichlet values: [", this%left_boundary_value, ", ", & + this%right_boundary_value, "]" + end if + print *, "===============================" + end subroutine config_print + + subroutine config_with_reconstruction(this, scheme, order) + type(cfd_config), intent(inout) :: this + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + character(len=20) :: scheme_lower + + ! Convert to lowercase + scheme_lower = scheme + call to_lower_inplace(scheme_lower) + this%recon_scheme = trim(adjustl(scheme_lower)) + + ! Set order + if (present(order)) then + this%spatial_order = order + else + ! Smart defaults + if (index(this%recon_scheme, 'weno') > 0) then + this%spatial_order = 5 + else if (trim(this%recon_scheme) == 'eno') then + this%spatial_order = 3 + else + print *, "[ERROR] Unsupported reconstruction scheme: ", trim(this%recon_scheme) + return + end if + end if + + if (this%verbose) then + print *, "[CONFIG] Reconstruction: ", trim(this%recon_scheme), & + " Order: ", this%spatial_order + end if + end subroutine config_with_reconstruction + + subroutine to_lower_inplace(str) + character(len=*), intent(inout) :: str + integer :: i + + do i = 1, len_trim(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + str(i:i) = char(ichar(str(i:i)) + 32) + end if + end do + end subroutine to_lower_inplace + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01m/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/01m/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..896969bd0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01m/src/infrastructure/mesh.f90 @@ -0,0 +1,74 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, mesh_type, mesh_init, mesh_print_info + + ! mesh + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer :: ncells = 40 + integer :: nnodes + integer :: nx + real(wp), allocatable :: x(:) ! + real(wp), allocatable :: xcc(:) ! + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer, optional, intent(in) :: ncells + + integer :: i + + ! Set + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! computation + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! node + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! cell + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== ===" + print *, ": [", this%xmin, ", ", this%xmax, "]" + print *, ": ", this%ncells + print *, ": ", this%nnodes + print *, " dx: ", this%dx + print *, " L: ", this%L + print *, "==========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..66874a7ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/CMakeLists.txt @@ -0,0 +1,7 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..3fde7746b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,28 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +# Start with just the base module +add_library(flux STATIC + base.f90 + rusanov.f90 +) + +#target_link_libraries(flux PRIVATE core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 明确设置不使用 /Qm64 选项 +if(CMAKE_Fortran_COMPILER_ID MATCHES "IntelLLVM|Intel") + set_target_properties(flux PROPERTIES + Fortran_FLAGS "${CMAKE_Fortran_FLAGS} /Qm64" # 明确设置,避免继承问题 + ) +endif() + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/flux/base.f90 new file mode 100644 index 000000000..7080a7ae4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/flux/base.f90 @@ -0,0 +1,30 @@ +!src/numerics/flux/base.f90 +module flux_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, flux_calculator_base + + ! Base flux calculator type + type :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure :: info => flux_info + procedure :: print_basic_info => flux_print_basic ! 添加辅助方法 + end type flux_calculator_base + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "Flux calculator information:" + print *, " Name: ", trim(this%name) + end subroutine flux_info + + subroutine flux_print_basic(this) + class(flux_calculator_base), intent(in) :: this + print *, " Name: ", trim(this%name) + end subroutine flux_print_basic + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/flux/rusanov.f90 b/example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/flux/rusanov.f90 new file mode 100644 index 000000000..7140f710b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/flux/rusanov.f90 @@ -0,0 +1,41 @@ +! src/numerics/flux/rusanov.f90 +module rusanov_flux_module + use, intrinsic :: iso_fortran_env, only: real64 + use flux_base_module, only: flux_calculator_base + implicit none + + private + public :: real64, rusanov_flux + + type, extends(flux_calculator_base) :: rusanov_flux + real(real64) :: wave_speed_default = 1.0_real64 + contains + procedure :: info => rusanov_info + end type rusanov_flux + + ! 构造函数接口 + interface rusanov_flux + module procedure create_rusanov_flux + end interface + +contains + + ! 构造函数 + type(rusanov_flux) function create_rusanov_flux() result(this) + this%name = "Rusanov" + this%wave_speed_default = 1.0_real64 + end function create_rusanov_flux + + subroutine rusanov_info(this) + class(rusanov_flux), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Flux calculator information:" + call this%print_basic_info() + + ! 添加Rusanov特有信息 + print *, " Type: Rusanov flux" + print *, " Default wave speed: ", this%wave_speed_default + end subroutine rusanov_info + +end module rusanov_flux_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..5e4b938d5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,22 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "Configuring reconstructor module...") + +# Start with just the base module +add_library(reconstructor STATIC + base.f90 + eno.f90 + weno3.f90 +) + +target_link_libraries(reconstructor PRIVATE core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Reconstructor module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..53798d021 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/reconstructor/base.f90 @@ -0,0 +1,33 @@ +!src/numerics/reconstructor/base.f90 +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, reconstructor_base + + ! Base reconstructor type + type :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + contains + procedure :: info => reconstructor_info + procedure :: print_basic_info => reconstructor_print_basic ! 添加一个辅助方法 + end type reconstructor_base + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + print *, "Reconstructor information:" + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_info + + subroutine reconstructor_print_basic(this) + class(reconstructor_base), intent(in) :: this + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_print_basic + +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/reconstructor/eno.f90 b/example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/reconstructor/eno.f90 new file mode 100644 index 000000000..a468b82ef --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/reconstructor/eno.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/eno.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, eno_reconstructor + + type, extends(reconstructor_base) :: eno_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => eno_info + end type eno_reconstructor + + ! 构造函数接口 + interface eno_reconstructor + module procedure create_eno_reconstructor + end interface + +contains + + ! 构造函数 + type(eno_reconstructor) function create_eno_reconstructor() result(this) + this%name = "ENO" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_eno_reconstructor + + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加ENO特有信息 + print *, " Type: ENO reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/reconstructor/weno3.f90 b/example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/reconstructor/weno3.f90 new file mode 100644 index 000000000..5e9542915 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01m/src/numerics/reconstructor/weno3.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/weno3.f90 +module weno3_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno3_reconstructor + + type, extends(reconstructor_base) :: weno3_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno3_info + end type weno3_reconstructor + + ! 构造函数接口 + interface weno3_reconstructor + module procedure create_weno3_reconstructor + end interface + +contains + + ! 构造函数 + type(weno3_reconstructor) function create_weno3_reconstructor() result(this) + this%name = "WENO3" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_weno3_reconstructor + + subroutine weno3_info(this) + class(weno3_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加WENO3特有信息 + print *, " Type: WENO-3 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno3_info + +end module weno3_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01m/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/01m/tests/CMakeLists.txt new file mode 100644 index 000000000..0cd64b0c6 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01m/tests/CMakeLists.txt @@ -0,0 +1,43 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +add_executable(test_minimal_simple test_minimal_simple.f90) + +message(STATUS "CMAKE_Fortran_MODULE_DIRECTORY=${CMAKE_Fortran_MODULE_DIRECTORY}") + +#target_include_directories( test_minimal_simple +# PRIVATE +# ${CMAKE_Fortran_MODULE_DIRECTORY} +#) + +target_link_libraries( test_minimal_simple + PRIVATE + infrastructure +) + +add_executable(test_simple_link test_simple_link.f90) +target_link_libraries(test_simple_link + PRIVATE + reconstructor + flux +) + + +#add_executable(test_factory_simple test_factory_simple.f90) + +#target_link_libraries( test_factory_simple +# PRIVATE +# core +# infrastructure +# reconstructor +# flux +#) + +add_executable(test_factory_simple test_factory_simple.f90) +target_link_libraries(test_factory_simple + PRIVATE + core + infrastructure + reconstructor + flux +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01m/tests/test_factory_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/01m/tests/test_factory_simple.f90 new file mode 100644 index 000000000..d4139bb1a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01m/tests/test_factory_simple.f90 @@ -0,0 +1,86 @@ +!tests/test_factory_simple.f90 +program test_factory_simple + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module, only: initialize_registry, cleanup_registry, & + register_component_simple, has_component + use config_module, only: cfd_config, config_print + use mesh_module, only: mesh_type + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(eno_reconstructor) :: eno + type(weno3_reconstructor) :: weno3 + type(rusanov_flux) :: rusanov + + print *, "=== Factory Pattern Simple Test ===" + print *, "" + + ! Test 1: Basic systems + print *, "1. Testing basic systems..." + print *, "-----------------------------" + call config_print(config) + + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 2: Creating reconstructors + print *, "2. Testing reconstructors..." + print *, "------------------------------" + + ! 创建并测试ENO重构器 + print *, "Creating ENO reconstructor..." + eno = eno_reconstructor() ! 使用构造函数 + call eno%info() ! 必须调用info方法 + + print *, "" + print *, "Creating WENO3 reconstructor..." + weno3 = weno3_reconstructor() ! 使用构造函数 + call weno3%info() ! 必须调用info方法 + print *, "" + + ! Test 3: Creating flux calculator + print *, "3. Testing flux calculator..." + print *, "-------------------------------" + + print *, "Creating Rusanov flux calculator..." + rusanov = rusanov_flux() ! 使用构造函数 + call rusanov%info() ! 必须调用info方法 + print *, "" + + ! Test 4: Registry integration + print *, "4. Testing registry..." + print *, "----------------------" + + call initialize_registry(verbose=.true.) + + ! Register components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("flux", "rusanov") + + ! Check registration + if (has_component("reconstructor", "eno")) then + print *, "[OK] ENO reconstructor registered successfully" + end if + + if (has_component("reconstructor", "weno3")) then + print *, "[OK] WENO3 reconstructor registered successfully" + end if + + if (has_component("flux", "rusanov")) then + print *, "[OK] Rusanov flux registered successfully" + end if + + print *, "" + call cleanup_registry() + + print *, "=== Factory pattern simple test completed successfully ===" + +end program test_factory_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01m/tests/test_minimal_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/01m/tests/test_minimal_simple.f90 new file mode 100644 index 000000000..6213e8f89 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01m/tests/test_minimal_simple.f90 @@ -0,0 +1,84 @@ +! tests/test_minimal_simple.f90 +program test_minimal_simple + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Simple Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components (simple version) + print *, "5. Testing registry functionality" + print *, "---------------------------------" + print *, "Registry initialized: ", registry_is_initialized() + print *, "Number of components: ", registry_get_size() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Simple test completed successfully ===" + +end program test_minimal_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/01m/tests/test_simple_link.f90 b/example/1d-linear-convection/weno3/fortran/registry/01m/tests/test_simple_link.f90 new file mode 100644 index 000000000..807d0bc2c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/01m/tests/test_simple_link.f90 @@ -0,0 +1,108 @@ +! tests/test_minimal.f90 +program test_minimal + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i ! Declare loop variable here + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", component_registry%size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components + print *, "5. Testing available components" + print *, "--------------------------------" + + ! Use a separate block to avoid allocation issues + call test_available_components() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Minimal test completed successfully ===" + +contains + + ! Internal subroutine for testing available components + subroutine test_available_components() + character(len=:), allocatable :: names(:) + integer, allocatable :: orders(:) + integer :: j + + call get_available_components("reconstructor", names, orders) + + if (allocated(names)) then + print *, "Reconstructors:" + do j = 1, size(names) + print *, " - ", trim(names(j)) + if (allocated(orders)) then + print *, " Order: ", orders(j) + end if + end do + else + print *, "No reconstructors found" + end if + end subroutine test_available_components + +end program test_minimal \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02/CMakeLists.txt new file mode 100644 index 000000000..ef66d5848 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02/README.md b/example/1d-linear-convection/weno3/fortran/registry/02/README.md new file mode 100644 index 000000000..c4889757b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02/README.md @@ -0,0 +1,221 @@ +# Fortran CFD Project + +基于工厂模式和注册系统的CFD求解器框架。 + +## 项目结构 + +``` +fortran_cfd/ +├── CMakeLists.txt # 根CMake配置 +├── scripts/ # 构建脚本 +│ ├── build.py # Python构建脚本(推荐) +│ ├── build.bat # Batch包装脚本 +│ ├── build.ps1 # PowerShell包装脚本 +│ └── requirements.txt # Python依赖 +├── src/ # 源代码 +│ ├── CMakeLists.txt +│ ├── core/ # 核心模块(注册系统、工厂接口) +│ ├── infrastructure/ # 基础设施(配置、网格) +│ └── numerics/ # 数值方法 +├── tests/ # 测试 +│ ├── CMakeLists.txt +│ ├── test_minimal_simple.f90 # 简单测试 +│ └── test_factory.f90 # 工厂模式测试 +└── README.md # 项目说明(本文件) +``` + +## 快速开始 + +### 使用Python脚本(推荐) + +```bash +# 进入脚本目录 +cd scripts + +# 基本构建 +python build.py + +# 清理并构建 +python build.py --clean + +# 发布构建 +python build.py --build-type Release --clean + +# 并行构建(使用所有核心) +python build.py -j + +# 不运行测试 +python build.py --no-tests + +# 查看帮助 +python build.py --help +``` + +### 使用批处理脚本 + +```bash +cd scripts +.\build.bat +``` + +### 使用PowerShell脚本 + +```powershell +cd scripts +.\build.ps1 +``` + +## 手动构建 + +```bash +# 设置Intel环境 +cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && powershell' + +# 配置项目 +mkdir build +cd build +cmake -G "Visual Studio 17 2022" -A x64 -T fortran=ifx .. + +# 构建项目 +cmake --build . --config Debug + +# 运行测试 +Debug\test_simple.exe +Debug\test_factory.exe +``` + +## 功能特性 + +✅ 组件注册系统 +✅ 工厂模式 +✅ ENO/WENO重构器 +✅ Rusanov通量计算器 +✅ 可扩展架构 +✅ 自动化测试 + +## 依赖 + +- Intel oneAPI(包含ifx编译器) +- CMake 3.12+ +- Python 3.6+(仅用于构建脚本) + +## 源代码文件 + +### 核心模块 +- `src/core/registry.f90` - 组件注册系统 +- `src/core/factory_interfaces.f90` - 工厂接口 + +### 基础设施 +- `src/infrastructure/config.f90` - 配置管理 +- `src/infrastructure/mesh.f90` - 网格生成 + +### 数值方法 +- `src/numerics/reconstructor/base.f90` - 重构器基类 +- `src/numerics/reconstructor/eno.f90` - ENO重构器 +- `src/numerics/reconstructor/weno3.f90` - WENO-3重构器 +- `src/numerics/flux/base.f90` - 通量计算器基类 +- `src/numerics/flux/rusanov.f90` - Rusanov通量 + +### 测试 +- `tests/test_minimal_simple.f90` - 简单功能测试 +- `tests/test_factory.f90` - 工厂模式测试 + +## 构建脚本 + +### Python脚本 (build.py) +主构建脚本,支持: +- 自动设置Intel oneAPI环境 +- 参数化构建选项 +- 并行编译 +- 自动化测试 +- 彩色输出 + +### Batch脚本 (build.bat) +Windows批处理包装器,调用Python脚本。 + +### PowerShell脚本 (build.ps1) +PowerShell包装器,调用Python脚本。 + +## 示例输出 + +成功构建后,会看到类似输出: + +``` +============================================================ + Fortran CFD Project Builder +============================================================ + +[1/4] Setting up Intel Fortran compiler... +✓ Intel oneAPI environment configured + +[2/4] Preparing build directory... +✓ Cleaned build directory + +[3/4] Configuring project... + $ cmake -G Visual Studio 17 2022 -A x64 -T fortran=ifx -DCMAKE_BUILD_TYPE=Debug .. +✓ CMake configuration successful + +[4/4] Building project... + $ cmake --build . --config Debug +✓ Build completed in 12.3 seconds + +============================================================ + Running Tests +============================================================ + +[TEST] Simple functionality test... +✓ Simple test passed + +[TEST] Factory pattern test... +✓ Factory test passed + +============================================================ + Build Summary +============================================================ +✓ Build directory: D:\path\to\build +✓ Build type: Debug +✓ Total time: 15.2s +``` + +## 开发指南 + +### 添加新组件 + +1. 在相应模块中创建Fortran源文件 +2. 实现工厂函数 +3. 使用`register_component_with_factory`注册组件 +4. 添加测试 + +### 扩展架构 + +项目采用模块化设计: +1. **注册系统** - 管理所有可用的组件 +2. **工厂模式** - 动态创建组件实例 +3. **抽象接口** - 确保组件兼容性 +4. **配置驱动** - 通过配置文件控制行为 + +## 故障排除 + +### 常见问题 + +1. **Intel环境未找到** + - 检查Intel oneAPI安装路径 + - 手动运行`setvars.bat` + +2. **CMake配置失败** + - 确保使用正确的生成器:`Visual Studio 17 2022` + - 检查Fortran编译器是否可用 + +3. **构建失败** + - 检查依赖项是否完整 + - 查看详细的错误信息 + +### 调试建议 + +- 使用`--verbose`参数获取详细输出 +- 检查`build/CMakeCache.txt`了解配置详情 +- 查看编译器错误日志 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02/scripts/build.bat b/example/1d-linear-convection/weno3/fortran/registry/02/scripts/build.bat new file mode 100644 index 000000000..6fd6dc031 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02/scripts/build.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Project Builder +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python build script with full Intel environment support... +echo. + +python build.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Build failed + pause + exit /b 1 +) + +echo. +echo [INFO] Build completed successfully! +echo. +echo [INFO] To run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02/scripts/build.py b/example/1d-linear-convection/weno3/fortran/registry/02/scripts/build.py new file mode 100644 index 000000000..3bf6d5375 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02/scripts/build.py @@ -0,0 +1,629 @@ +#!/usr/bin/env python3 +""" +Fortran CFD Project Builder - 完整Python解决方案 +在Python内部处理Intel oneAPI环境配置 +""" + +import os +import sys +import subprocess +import shutil +import argparse +import time +import platform +import tempfile +from pathlib import Path + +class IntelEnvironment: + """Intel oneAPI环境管理器""" + + def __init__(self): + self.setvars_path = None + self.env_vars = {} + + def find_setvars(self): + """查找setvars.bat文件""" + possible_paths = [ + r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat", + r"C:\Program Files\Intel\oneAPI\setvars.bat", + r"C:\Program Files (x86)\Intel\oneAPI\compiler\latest\env\vars.bat", + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\setvars.bat"), + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\compiler\latest\env\vars.bat"), + ] + + for path in possible_paths: + if os.path.exists(path): + self.setvars_path = path + return True + + return False + + def setup_environment(self): + """设置Intel环境""" + if not self.find_setvars(): + return False + + try: + # 创建临时的批处理文件来捕获环境变量 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + f.write(f'@echo off\n') + f.write(f'call "{self.setvars_path}" >nul 2>&1\n') + f.write(f'set\n') # 输出所有环境变量 + temp_bat = f.name + + # 运行批处理文件并捕获输出 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + shell=True + ) + + # 解析环境变量 + for line in result.stdout.split('\n'): + line = line.strip() + if '=' in line: + key, value = line.split('=', 1) + self.env_vars[key.strip()] = value.strip() + + # 清理临时文件 + os.unlink(temp_bat) + + # 更新当前进程的环境变量 + os.environ.update(self.env_vars) + + return True + + except Exception as e: + print(f"设置Intel环境失败: {e}") + return False + + def get_compiler_info(self): + """获取编译器信息""" + info = {} + + # 检查ifx编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + env={**os.environ, **self.env_vars} if self.env_vars else os.environ + ) + + if result.returncode == 0: + for line in result.stdout.split('\n'): + if 'Version' in line or '版本' in line: + info['ifx_version'] = line.strip() + break + except: + pass + + # 检查环境变量 + info['ifx_root'] = self.env_vars.get('IFX_ROOT', '') + info['compiler_root'] = self.env_vars.get('ONEAPI_ROOT', '') + + return info + +class BuildSystem: + """构建系统主类""" + + def __init__(self): + self.project_root = Path(__file__).parent.parent + self.build_dir = self.project_root / "build" + self.intel_env = IntelEnvironment() + + # 设置控制台编码 + if sys.platform == "win32": + try: + import ctypes + # 设置控制台输出为UTF-8 + ctypes.windll.kernel32.SetConsoleOutputCP(65001) + except: + pass + + def print_header(self, text): + """打印标题""" + print(f"\n{'='*70}") + print(f" {text}") + print(f"{'='*70}\n") + + def print_step(self, step, total, message): + """打印步骤""" + print(f"[{step}/{total}] {message}...") + + def print_success(self, message): + """打印成功""" + print(f"\033[92m✓ {message}\033[0m") + + def print_error(self, message): + """打印错误""" + print(f"\033[91m✗ {message}\033[0m") + + def print_warning(self, message): + """打印警告""" + print(f"\033[93m! {message}\033[0m") + + def print_info(self, message): + """打印信息""" + print(f"\033[94mℹ {message}\033[0m") + + def check_prerequisites(self): + """检查前提条件""" + self.print_step(1, 6, "检查前提条件") + + # 检查Python版本 + python_version = sys.version.split()[0] + self.print_info(f"Python版本: {python_version}") + + # 检查平台 + self.print_info(f"平台: {platform.system()} {platform.release()}") + self.print_info(f"处理器核心数: {os.cpu_count()}") + + # 检查CMake + try: + result = subprocess.run( + ["cmake", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + version_line = result.stdout.split('\n')[0] + self.print_success(f"CMake: {version_line}") + else: + self.print_error("CMake未找到") + return False + except FileNotFoundError: + self.print_error("CMake未安装") + return False + + return True + + def setup_intel_environment(self, args): + """设置Intel环境""" + self.print_step(2, 6, "配置Intel oneAPI环境") + + if not self.intel_env.find_setvars(): + self.print_warning("未找到Intel oneAPI setvars.bat") + self.print_info("将尝试使用系统环境中的编译器") + + # 检查是否能直接访问编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + self.print_success("Intel编译器在系统PATH中找到") + return True + else: + self.print_warning("Intel编译器未在PATH中找到") + except: + self.print_warning("无法访问Intel编译器") + + return True # 继续,让CMake自己找编译器 + + # 设置环境 + if self.intel_env.setup_environment(): + compiler_info = self.intel_env.get_compiler_info() + + if compiler_info.get('ifx_version'): + self.print_success(f"Intel Fortran编译器: {compiler_info['ifx_version']}") + elif compiler_info.get('ifx_root'): + self.print_success(f"Intel编译器路径: {compiler_info['ifx_root']}") + else: + self.print_success("Intel oneAPI环境配置完成") + + return True + else: + self.print_warning("Intel环境配置失败,将继续使用系统环境") + return True + + def clean_build_directory(self, args): + """清理构建目录""" + if args.clean and self.build_dir.exists(): + self.print_info("清理构建目录...") + try: + shutil.rmtree(self.build_dir) + self.print_success("构建目录已清理") + except Exception as e: + self.print_error(f"清理失败: {e}") + if not args.force: + return False + return True + + def run_command(self, cmd, cwd=None, check=True, env=None): + """运行命令""" + if isinstance(cmd, list): + cmd_str = ' '.join(str(c) for c in cmd if c) + else: + cmd_str = str(cmd) + + print(f" \033[96m$\033[0m {cmd_str}") + + try: + # 合并环境变量 + exec_env = os.environ.copy() + if env: + exec_env.update(env) + if self.intel_env.env_vars: + exec_env.update(self.intel_env.env_vars) + + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=False, + env=exec_env + ) + + # 处理输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or '完成' in line or '生成' in line: + print(f" \033[92m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + if check and result.returncode != 0: + self.print_error(f"命令执行失败,退出码: {result.returncode}") + return False + + return True + + except Exception as e: + self.print_error(f"命令执行异常: {e}") + return False + + def configure_cmake(self, args): + """配置CMake""" + self.print_step(3, 6, "配置CMake项目") + + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + f"-DCMAKE_BUILD_TYPE={args.build_type}", + ] + + if args.compiler == "ifx": + cmake_cmd.extend(["-T", "fortran=ifx"]) + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + success = self.run_command(cmake_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("CMake配置完成") + else: + self.print_error("CMake配置失败") + + return success + + def build_project(self, args): + """构建项目""" + self.print_step(4, 6, "构建项目") + + build_cmd = [ + "cmake", + "--build", ".", + "--config", args.build_type, + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + success = self.run_command(build_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("项目构建完成") + else: + self.print_error("构建失败") + + return success + + def run_tests_with_environment(self, test_exe): + """运行单个测试,确保有Intel环境""" + try: + # 创建临时的批处理文件来运行测试 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'"{test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'"{test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + return result + + except Exception as e: + print(f"运行测试失败: {e}") + return None + + def run_tests(self, args): + """运行测试""" + self.print_step(5, 6, "运行测试") + + # 查找测试可执行文件 + test_dir = self.build_dir / "bin" / args.build_type + if not test_dir.exists(): + test_dir = self.build_dir / "bin" + if not test_dir.exists(): + test_dir = self.build_dir + + test_files = list(test_dir.glob("test_*.exe")) + + if not test_files: + self.print_warning("未找到测试程序") + return True + + all_passed = True + + for test_exe in sorted(test_files): + test_name = test_exe.stem + self.print_info(f"运行测试: {test_name}") + print(f" {'-'*50}") + + # 运行测试 + result = self.run_tests_with_environment(str(test_exe)) + + if result is None: + self.print_error(f" {test_name} 运行失败") + all_passed = False + continue + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + print(f" {line}") + + if result.returncode == 0: + self.print_success(f" {test_name} 通过") + else: + self.print_error(f" {test_name} 失败 (退出码: {result.returncode})") + all_passed = False + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + print() # 空行 + + return all_passed + + def create_test_runner(self, args): + """创建独立的测试运行器""" + self.print_step(6, 6, "创建测试运行器") + + runner_path = self.build_dir / "run_tests.bat" + + content = f'''@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Test Runner +echo ======================================== +echo. + +REM Setup Intel oneAPI environment +set "SETVARS_PATH={self.intel_env.setvars_path or ''}" +if exist "%SETVARS_PATH%" ( + call "%SETVARS_PATH%" >nul + echo [INFO] Intel environment configured +) else ( + echo [WARNING] Intel environment not found + echo [WARNING] Tests may fail without runtime libraries +) + +echo. + +REM Run all test executables +set "TEST_COUNT=0" +set "PASS_COUNT=0" + +for %%f in ("bin\\{args.build_type}\\test_*.exe") do ( + set /a TEST_COUNT+=1 + echo [TEST %%f] + echo {'-'*50} + + %%f + if errorlevel 1 ( + echo [FAILED] %%f + ) else ( + echo [PASSED] %%f + set /a PASS_COUNT+=1 + ) + echo. +) + +echo ======================================== +echo Tests: %PASS_COUNT%/%TEST_COUNT% passed +if %PASS_COUNT% equ %TEST_COUNT% ( + echo [SUCCESS] All tests passed! +) else ( + echo [FAILURE] Some tests failed +) +echo ======================================== + +pause +''' + + with open(runner_path, 'w', encoding='utf-8') as f: + f.write(content) + + self.print_success(f"测试运行器已创建: {runner_path}") + self.print_info(f"使用方法: cd build && run_tests.bat") + + return runner_path + + def generate_report(self, args, build_time, tests_passed): + """生成构建报告""" + self.print_header("构建完成") + + print(f"项目: {self.project_root.name}") + print(f"构建类型: {args.build_type}") + print(f"编译器: {args.compiler}") + print(f"并行作业: {args.jobs}") + print(f"总耗时: {build_time:.1f}秒") + print(f"测试结果: {'全部通过' if tests_passed else '有失败'}") + + # 显示生成的可执行文件 + bin_dir = self.build_dir / "bin" / args.build_type + if bin_dir.exists(): + print(f"\n生成的可执行文件:") + for exe in sorted(bin_dir.glob("*.exe")): + size_mb = exe.stat().st_size / (1024 * 1024) + print(f" • {exe.name} ({size_mb:.2f} MB)") + + # 显示测试运行器信息 + runner_path = self.build_dir / "run_tests.bat" + if runner_path.exists(): + print(f"\n独立测试运行器:") + print(f" • {runner_path.name}") + print(f" 在Intel oneAPI环境中运行所有测试") + + def run(self): + """运行构建系统""" + parser = argparse.ArgumentParser( + description="Fortran CFD项目构建工具", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认构建 + %(prog)s --clean # 清理后构建 + %(prog)s --build-type Release # Release构建 + %(prog)s --no-tests # 只构建,不运行测试 + %(prog)s -j8 --verbose # 8线程并行构建,详细输出 + """ + ) + + parser.add_argument("--build-type", choices=["Debug", "Release"], + default="Debug", help="构建类型") + parser.add_argument("--compiler", choices=["ifx", "ifort"], + default="ifx", help="Fortran编译器") + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-tests", action="store_true", + help="跳过测试") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + + args = parser.parse_args() + + # 开始构建 + start_time = time.time() + + self.print_header("Fortran CFD 项目构建系统") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 检查前提条件 + if not self.check_prerequisites(): + if not args.force: + return 1 + + # 2. 设置Intel环境 + if not self.setup_intel_environment(args): + if not args.force: + return 1 + + # 3. 清理目录 + if not self.clean_build_directory(args): + if not args.force: + return 1 + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 4. 配置CMake + if not self.configure_cmake(args): + if not args.force: + return 1 + + # 5. 构建项目 + if not self.build_project(args): + if not args.force: + return 1 + + # 6. 运行测试和创建测试运行器 + tests_passed = True + if not args.no_tests: + tests_passed = self.run_tests(args) + + # 创建测试运行器 + self.create_test_runner(args) # 传递 args 参数 + + # 7. 生成报告 + build_time = time.time() - start_time + self.generate_report(args, build_time, tests_passed) + + return 0 if tests_passed else 1 + + except KeyboardInterrupt: + self.print_error("\n构建被用户中断") + return 1 + except Exception as e: + self.print_error(f"构建过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + builder = BuildSystem() + return builder.run() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02/scripts/requirements.txt b/example/1d-linear-convection/weno3/fortran/registry/02/scripts/requirements.txt new file mode 100644 index 000000000..d21a12b45 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02/scripts/requirements.txt @@ -0,0 +1,2 @@ +# 构建脚本的Python依赖(可选) +# 目前没有特殊依赖,保持空文件或添加未来可能需要的包 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02/src/CMakeLists.txt new file mode 100644 index 000000000..ee38952bb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02/src/CMakeLists.txt @@ -0,0 +1,14 @@ +# ==================== src\CMakeLists.txt ==================== + +# src/CMakeLists.txt +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(core) +add_subdirectory(infrastructure) +add_subdirectory(numerics) + +message(STATUS "源代码目录配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02/src/core/CMakeLists.txt new file mode 100644 index 000000000..bcfd024d8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02/src/core/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/core/CMakeLists.txt +message(STATUS "配置核心模块...") + +add_library(core STATIC + registry.f90 + factory_interfaces.f90 +) + +target_include_directories(core PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 安装规则 +install(TARGETS core + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) +install(FILES + ${CMAKE_Fortran_MODULE_DIRECTORY}/registry_module.mod + ${CMAKE_Fortran_MODULE_DIRECTORY}/factory_interfaces.mod + DESTINATION include/fortran_cfd/core +) + +message(STATUS "核心模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/02/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..a960167c3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02/src/core/factory_interfaces.f90 @@ -0,0 +1,17 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, factory_procedure + + ! Factory procedure interface + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/02/src/core/registry.f90 new file mode 100644 index 000000000..ada7ca026 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02/src/core/registry.f90 @@ -0,0 +1,668 @@ +! src/core/registry.f90 +module registry_module + use, intrinsic :: iso_fortran_env, only: real64 + use factory_interfaces, only: factory_procedure + implicit none + + private + + ! ==================== PUBLIC INTERFACE ==================== + ! 原有接口保持不变 + public :: real64, component_info, component_registry + public :: register_component_simple, initialize_registry, cleanup_registry + public :: has_component, get_available_components + public :: registry_is_initialized, registry_get_size + public :: list_all ! ← 恢复这个接口 + + ! 新增工厂相关接口 + public :: factory_ptr_type, component_registry_entry + public :: register_component_with_factory + public :: create_component_from_registry + public :: has_factory_component + public :: list_factory_components + + ! ==================== TYPE DEFINITIONS ==================== + + ! 原有类型定义 + type :: component_info + character(len=32) :: category = "" + character(len=32) :: name = "" + integer :: order = 0 + contains + procedure :: print => ci_print + end type component_info + + ! 新增:工厂指针类型 + type :: factory_ptr_type + procedure(factory_procedure), pointer, nopass :: ptr => null() + end type factory_ptr_type + + ! 新增:带工厂的注册条目 + type :: component_registry_entry + character(len=32) :: category = "" + character(len=32) :: name = "" + integer :: order = 0 + type(factory_ptr_type) :: factory + logical :: has_factory = .false. + contains + procedure :: print_with_factory => entry_print + procedure :: can_create => entry_has_factory + end type component_registry_entry + + ! 原有注册表类型(扩展以支持工厂) + type :: component_registry_type + private + ! 简单注册组件(向后兼容) + type(component_info), allocatable :: simple_components(:) + integer :: simple_count = 0 + + ! 带工厂的组件(新增) + type(component_registry_entry), allocatable :: factory_components(:) + integer :: factory_count = 0 + + integer :: capacity = 100 + logical :: verbose = .true. + logical :: initialized = .false. + contains + ! 简单注册方法 + procedure :: register => cr_register ! ← 保持原名 + procedure :: get => cr_get ! ← 保持原名 + procedure :: list_all => cr_list_all ! ← 保持原名(重要!) + procedure :: list_simple => cr_list_simple ! ← 新增别名 + + ! 工厂注册方法 + procedure :: register_with_factory => cr_register_with_factory + procedure :: get_with_factory => cr_get_with_factory + procedure :: list_with_factory => cr_list_with_factory + procedure :: create_from_factory => cr_create_from_factory + + ! 通用方法 + procedure :: clear => cr_clear + procedure :: size => cr_size + procedure :: total_size => cr_total_size + procedure :: is_initialized => cr_is_initialized + end type component_registry_type + + ! Global registry instance + type(component_registry_type), save :: component_registry + +contains + + ! ==================== PUBLIC API ==================== + + ! Initialize registry (保持不变) + subroutine initialize_registry(initial_capacity, verbose) + integer, optional, intent(in) :: initial_capacity + logical, optional, intent(in) :: verbose + + if (component_registry%initialized) then + if (component_registry%verbose) then + print *, "[INFO] Registry already initialized" + end if + return + end if + + if (present(initial_capacity)) then + component_registry%capacity = max(10, initial_capacity) + end if + + if (present(verbose)) then + component_registry%verbose = verbose + end if + + ! Allocate arrays for both types + allocate(component_registry%simple_components(component_registry%capacity)) + allocate(component_registry%factory_components(component_registry%capacity)) + + component_registry%initialized = .true. + component_registry%simple_count = 0 + component_registry%factory_count = 0 + + if (component_registry%verbose) then + print *, "[INIT] Registry initialized, capacity:", component_registry%capacity + print *, " Supports both simple and factory-based registration" + end if + end subroutine initialize_registry + + ! Cleanup registry (保持不变) + subroutine cleanup_registry + call component_registry%clear() + if (component_registry%verbose) then + print *, "[CLEANUP] Registry cleaned up" + end if + end subroutine cleanup_registry + + ! ==================== SIMPLE REGISTRATION (向后兼容) ==================== + + ! Simple registration (no factory) - 保持不变 + subroutine register_component_simple(category, name) + character(len=*), intent(in) :: category, name + + type(component_info) :: info + + info%category = to_lower(trim(adjustl(category))) + info%name = to_lower(trim(adjustl(name))) + info%order = 0 + + call component_registry%register(info) + end subroutine register_component_simple + + ! Check if component exists (简单注册) + function has_component(category, name) result(found) + character(len=*), intent(in) :: category, name + logical :: found + + type(component_info) :: info + character(len=32) :: cat_lower, name_lower + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + info = component_registry%get(cat_lower, name_lower) + found = (len_trim(info%category) > 0) + end function has_component + + ! Get available components in a category (简单注册) + subroutine get_available_components(category, names, orders) + character(len=*), intent(in) :: category + character(len=:), allocatable, intent(out), optional :: names(:) + integer, allocatable, intent(out), optional :: orders(:) + + character(len=32) :: cat_lower + integer :: i, count, idx + type(component_info) :: info + + cat_lower = to_lower(trim(adjustl(category))) + + ! Count components in this category + count = 0 + do i = 1, component_registry%simple_count + if (component_registry%simple_components(i)%category == cat_lower) then + count = count + 1 + end if + end do + + ! Allocate arrays if requested + if (present(names)) then + allocate(character(len=32) :: names(count)) + end if + + if (present(orders)) then + allocate(orders(count)) + end if + + ! Fill arrays + idx = 1 + do i = 1, component_registry%simple_count + if (component_registry%simple_components(i)%category == cat_lower) then + info = component_registry%simple_components(i) + if (present(names)) then + names(idx) = info%name + end if + if (present(orders)) then + orders(idx) = info%order + end if + idx = idx + 1 + end if + end do + end subroutine get_available_components + + ! ==================== FACTORY REGISTRATION (新增) ==================== + + ! Register component with factory function + subroutine register_component_with_factory(category, name, factory_func, order) + character(len=*), intent(in) :: category, name + procedure(factory_procedure) :: factory_func + integer, optional, intent(in) :: order + + type(component_registry_entry) :: entry + character(len=32) :: cat_lower, name_lower + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + entry%category = cat_lower + entry%name = name_lower + + if (present(order)) then + entry%order = order + else + entry%order = 0 + end if + + entry%factory%ptr => factory_func + entry%has_factory = .true. + + call component_registry%register_with_factory(entry) + end subroutine register_component_with_factory + + ! Create component from registry using factory + function create_component_from_registry(category, name) result(instance) + character(len=*), intent(in) :: category, name + class(*), allocatable :: instance + + character(len=32) :: cat_lower, name_lower + integer :: status + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + call component_registry%create_from_factory(cat_lower, name_lower, instance, status) + + if (status /= 0) then + if (component_registry%verbose) then + print *, "[ERROR] Failed to create component: ", trim(category), ".", trim(name) + end if + end if + end function create_component_from_registry + + ! Check if component has factory + function has_factory_component(category, name) result(found) + character(len=*), intent(in) :: category, name + logical :: found + + character(len=32) :: cat_lower, name_lower + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + found = .false. + + if (.not. component_registry%initialized) return + + ! 简化的查找逻辑(实际应调用内部方法) + found = component_registry%factory_count > 0 ! 占位实现 + end function has_factory_component + + ! List all factory components + subroutine list_factory_components(category) + character(len=*), optional, intent(in) :: category + + if (present(category)) then + call component_registry%list_with_factory(category) + else + call component_registry%list_with_factory("") + end if + end subroutine list_factory_components + + ! Public function to list all simple components (向后兼容) + subroutine list_all() + call component_registry%list_all() + end subroutine list_all + + ! ==================== PUBLIC UTILITY FUNCTIONS ==================== + + ! Public function to check if registry is initialized + function registry_is_initialized() result(is_initialized) + logical :: is_initialized + is_initialized = component_registry%is_initialized() + end function registry_is_initialized + + ! Public function to get total registry size + function registry_get_size() result(size_val) + integer :: size_val + size_val = component_registry%total_size() + end function registry_get_size + + ! ==================== COMPONENT INFO METHODS ==================== + + subroutine ci_print(this) + class(component_info), intent(in) :: this + + if (this%order > 0) then + print *, " [", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ")]" + else + print *, " [", trim(this%category), ".", trim(this%name), "]" + end if + end subroutine ci_print + + subroutine entry_print(this) + class(component_registry_entry), intent(in) :: this + + if (this%has_factory) then + if (this%order > 0) then + print *, " [FACTORY] ", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ")" + else + print *, " [FACTORY] ", trim(this%category), ".", trim(this%name) + end if + else + if (this%order > 0) then + print *, " [SIMPLE] ", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ")" + else + print *, " [SIMPLE] ", trim(this%category), ".", trim(this%name) + end if + end if + end subroutine entry_print + + logical function entry_has_factory(this) + class(component_registry_entry), intent(in) :: this + entry_has_factory = this%has_factory + end function entry_has_factory + + ! ==================== REGISTRY INTERNAL METHODS ==================== + + ! ---------- Simple Registration Methods ---------- + + subroutine cr_register(this, info) + class(component_registry_type), intent(inout) :: this + type(component_info), intent(in) :: info + + type(component_info), allocatable :: temp(:) + integer :: i + + if (.not. this%initialized) then + error stop "[ERROR] Registry not initialized, call initialize_registry first" + end if + + ! Check if already exists + do i = 1, this%simple_count + if (this%simple_components(i)%category == info%category .and. & + this%simple_components(i)%name == info%name) then + if (this%verbose) then + print *, "[WARN] Overwriting simple component: ", & + trim(info%category), ".", trim(info%name) + end if + this%simple_components(i) = info + return + end if + end do + + ! Expand array if needed + if (this%simple_count >= this%capacity) then + this%capacity = this%capacity * 2 + allocate(temp(this%capacity)) + temp(1:this%simple_count) = this%simple_components(1:this%simple_count) + call move_alloc(temp, this%simple_components) + + if (this%verbose) then + print *, "[INFO] Simple registry expanded to capacity:", this%capacity + end if + end if + + ! Add component + this%simple_count = this%simple_count + 1 + this%simple_components(this%simple_count) = info + + if (this%verbose) then + print *, "[OK] Registered simple: ", trim(info%category), ".", trim(info%name) + end if + end subroutine cr_register + + function cr_get(this, category, name) result(info) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category, name + type(component_info) :: info + + integer :: i + + ! Initialize return value as empty + info%category = "" + info%name = "" + info%order = 0 + + if (.not. this%initialized) then + return + end if + + do i = 1, this%simple_count + if (this%simple_components(i)%category == category .and. & + this%simple_components(i)%name == name) then + info = this%simple_components(i) + return + end if + end do + end function cr_get + + subroutine cr_list_all(this) + class(component_registry_type), intent(in) :: this + integer :: i + + if (.not. this%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + if (this%simple_count == 0) then + print *, "[INFO] No simple components registered" + return + end if + + print *, "=== Simple Registry Contents (", this%simple_count, " components) ===" + do i = 1, this%simple_count + call this%simple_components(i)%print() + end do + print *, "==========================================" + end subroutine cr_list_all + + subroutine cr_list_simple(this) + class(component_registry_type), intent(in) :: this + call this%list_all() + end subroutine cr_list_simple + + ! ---------- Factory Registration Methods ---------- + + subroutine cr_register_with_factory(this, entry) + class(component_registry_type), intent(inout) :: this + type(component_registry_entry), intent(in) :: entry + + type(component_registry_entry), allocatable :: temp(:) + integer :: i + + if (.not. this%initialized) then + error stop "[ERROR] Registry not initialized, call initialize_registry first" + end if + + ! Check if already exists + do i = 1, this%factory_count + if (this%factory_components(i)%category == entry%category .and. & + this%factory_components(i)%name == entry%name) then + if (this%verbose) then + print *, "[WARN] Overwriting factory component: ", & + trim(entry%category), ".", trim(entry%name) + end if + this%factory_components(i) = entry + return + end if + end do + + ! Expand array if needed + if (this%factory_count >= this%capacity) then + this%capacity = this%capacity * 2 + allocate(temp(this%capacity)) + temp(1:this%factory_count) = this%factory_components(1:this%factory_count) + call move_alloc(temp, this%factory_components) + + if (this%verbose) then + print *, "[INFO] Factory registry expanded to capacity:", this%capacity + end if + end if + + ! Add component + this%factory_count = this%factory_count + 1 + this%factory_components(this%factory_count) = entry + + if (this%verbose) then + print *, "[FACTORY] Registered with factory: ", & + trim(entry%category), ".", trim(entry%name) + end if + end subroutine cr_register_with_factory + + function cr_get_with_factory(this, category, name) result(entry) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category, name + type(component_registry_entry) :: entry + + integer :: i + + ! Initialize return value as empty + entry%category = "" + entry%name = "" + entry%order = 0 + entry%factory%ptr => null() + entry%has_factory = .false. + + if (.not. this%initialized) then + return + end if + + do i = 1, this%factory_count + if (this%factory_components(i)%category == category .and. & + this%factory_components(i)%name == name) then + entry = this%factory_components(i) + return + end if + end do + end function cr_get_with_factory + + subroutine cr_list_with_factory(this, category) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category + + character(len=32) :: cat_lower + integer :: i, count + + if (.not. this%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + cat_lower = to_lower(trim(adjustl(category))) + + if (this%factory_count == 0) then + print *, "[INFO] No factory components registered" + return + end if + + ! Count components in this category + count = 0 + do i = 1, this%factory_count + if (len_trim(cat_lower) == 0 .or. & + this%factory_components(i)%category == cat_lower) then + count = count + 1 + end if + end do + + if (count == 0) then + if (len_trim(cat_lower) > 0) then + print *, "[INFO] No factory components in category: ", trim(cat_lower) + else + print *, "[INFO] No factory components registered" + end if + return + end if + + print *, "=== Factory Registry Contents (", count, " components) ===" + do i = 1, this%factory_count + if (len_trim(cat_lower) == 0 .or. & + this%factory_components(i)%category == cat_lower) then + call this%factory_components(i)%print_with_factory() + end if + end do + print *, "==========================================" + end subroutine cr_list_with_factory + + subroutine cr_create_from_factory(this, category, name, instance, status) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category, name + class(*), allocatable, intent(out) :: instance + integer, intent(out) :: status + + type(component_registry_entry) :: entry + integer :: i + + status = -1 ! Default: not found + + if (.not. this%initialized) then + if (this%verbose) then + print *, "[ERROR] Registry not initialized" + end if + return + end if + + ! Find the entry + do i = 1, this%factory_count + if (this%factory_components(i)%category == category .and. & + this%factory_components(i)%name == name) then + entry = this%factory_components(i) + + if (entry%has_factory .and. associated(entry%factory%ptr)) then + ! Call the factory function + call entry%factory%ptr(instance) + status = 0 ! Success + + if (this%verbose) then + print *, "[FACTORY] Created component: ", & + trim(category), ".", trim(name) + end if + else + status = -2 ! No factory function + if (this%verbose) then + print *, "[ERROR] No factory function for: ", & + trim(category), ".", trim(name) + end if + end if + return + end if + end do + + ! If we get here, component not found + if (this%verbose) then + print *, "[ERROR] Factory component not found: ", & + trim(category), ".", trim(name) + end if + end subroutine cr_create_from_factory + + ! ---------- Common Methods ---------- + + subroutine cr_clear(this) + class(component_registry_type), intent(inout) :: this + + if (allocated(this%simple_components)) then + deallocate(this%simple_components) + end if + + if (allocated(this%factory_components)) then + deallocate(this%factory_components) + end if + + this%simple_count = 0 + this%factory_count = 0 + this%capacity = 100 + this%initialized = .false. + end subroutine cr_clear + + integer function cr_total_size(this) + class(component_registry_type), intent(in) :: this + cr_total_size = this%simple_count + this%factory_count + end function cr_total_size + + integer function cr_size(this) + class(component_registry_type), intent(in) :: this + cr_size = this%simple_count ! Backward compatibility + end function cr_size + + logical function cr_is_initialized(this) + class(component_registry_type), intent(in) :: this + cr_is_initialized = this%initialized + end function cr_is_initialized + + ! ==================== UTILITY FUNCTIONS ==================== + + function to_lower(str) result(lower_str) + character(len=*), intent(in) :: str + character(len=len(str)) :: lower_str + integer :: i + + do i = 1, len(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + lower_str(i:i) = char(ichar(str(i:i)) + 32) + else + lower_str(i:i) = str(i:i) + end if + end do + end function to_lower + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..46964ce79 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02/src/infrastructure/CMakeLists.txt @@ -0,0 +1,20 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "配置基础设施模块...") + +add_library(infrastructure STATIC + config.f90 + mesh.f90 +) + +target_link_libraries(infrastructure PRIVATE core) + +target_include_directories(infrastructure PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS infrastructure + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "基础设施模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/02/src/infrastructure/config.f90 new file mode 100644 index 000000000..d3b1e0dfb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02/src/infrastructure/config.f90 @@ -0,0 +1,90 @@ +! src/infrastructure/config.f90 +module config_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, cfd_config, config_print, config_with_reconstruction + + ! CFD configuration type + type :: cfd_config + character(len=20) :: ic_type = "step" + character(len=20) :: recon_scheme = "eno" + character(len=20) :: flux_type = "rusanov" + integer :: rk_order = 1 + real(real64) :: wave_speed = 1.0_real64 + real(real64) :: final_time = 0.625_real64 + real(real64) :: dt = 0.025_real64 + character(len=20) :: boundary_type = "periodic" + real(real64) :: left_boundary_value = 1.0_real64 + real(real64) :: right_boundary_value = 2.0_real64 + integer :: spatial_order = 2 + logical :: verbose = .true. + end type cfd_config + +contains + + subroutine config_print(this) + type(cfd_config), intent(in) :: this + + print *, "=== CFD Configuration ===" + print *, "Initial condition: ", trim(this%ic_type) + print *, "Reconstruction: ", trim(this%recon_scheme), " (order:", this%spatial_order, ")" + print *, "Flux type: ", trim(this%flux_type) + print *, "Time integration: RK", this%rk_order + print *, "Wave speed: ", this%wave_speed + print *, "Final time: ", this%final_time + print *, "Time step: ", this%dt + print *, "Boundary: ", trim(this%boundary_type) + if (trim(this%boundary_type) == 'dirichlet') then + print *, " Dirichlet values: [", this%left_boundary_value, ", ", & + this%right_boundary_value, "]" + end if + print *, "===============================" + end subroutine config_print + + subroutine config_with_reconstruction(this, scheme, order) + type(cfd_config), intent(inout) :: this + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + character(len=20) :: scheme_lower + + ! Convert to lowercase + scheme_lower = scheme + call to_lower_inplace(scheme_lower) + this%recon_scheme = trim(adjustl(scheme_lower)) + + ! Set order + if (present(order)) then + this%spatial_order = order + else + ! Smart defaults + if (index(this%recon_scheme, 'weno') > 0) then + this%spatial_order = 5 + else if (trim(this%recon_scheme) == 'eno') then + this%spatial_order = 3 + else + print *, "[ERROR] Unsupported reconstruction scheme: ", trim(this%recon_scheme) + return + end if + end if + + if (this%verbose) then + print *, "[CONFIG] Reconstruction: ", trim(this%recon_scheme), & + " Order: ", this%spatial_order + end if + end subroutine config_with_reconstruction + + subroutine to_lower_inplace(str) + character(len=*), intent(inout) :: str + integer :: i + + do i = 1, len_trim(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + str(i:i) = char(ichar(str(i:i)) + 32) + end if + end do + end subroutine to_lower_inplace + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/02/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..896969bd0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02/src/infrastructure/mesh.f90 @@ -0,0 +1,74 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, mesh_type, mesh_init, mesh_print_info + + ! mesh + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer :: ncells = 40 + integer :: nnodes + integer :: nx + real(wp), allocatable :: x(:) ! + real(wp), allocatable :: xcc(:) ! + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer, optional, intent(in) :: ncells + + integer :: i + + ! Set + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! computation + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! node + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! cell + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== ===" + print *, ": [", this%xmin, ", ", this%xmax, "]" + print *, ": ", this%ncells + print *, ": ", this%nnodes + print *, " dx: ", this%dx + print *, " L: ", this%L + print *, "==========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..66874a7ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/CMakeLists.txt @@ -0,0 +1,7 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..3fde7746b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,28 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +# Start with just the base module +add_library(flux STATIC + base.f90 + rusanov.f90 +) + +#target_link_libraries(flux PRIVATE core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 明确设置不使用 /Qm64 选项 +if(CMAKE_Fortran_COMPILER_ID MATCHES "IntelLLVM|Intel") + set_target_properties(flux PROPERTIES + Fortran_FLAGS "${CMAKE_Fortran_FLAGS} /Qm64" # 明确设置,避免继承问题 + ) +endif() + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/flux/base.f90 new file mode 100644 index 000000000..7080a7ae4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/flux/base.f90 @@ -0,0 +1,30 @@ +!src/numerics/flux/base.f90 +module flux_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, flux_calculator_base + + ! Base flux calculator type + type :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure :: info => flux_info + procedure :: print_basic_info => flux_print_basic ! 添加辅助方法 + end type flux_calculator_base + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "Flux calculator information:" + print *, " Name: ", trim(this%name) + end subroutine flux_info + + subroutine flux_print_basic(this) + class(flux_calculator_base), intent(in) :: this + print *, " Name: ", trim(this%name) + end subroutine flux_print_basic + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/flux/rusanov.f90 b/example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/flux/rusanov.f90 new file mode 100644 index 000000000..7140f710b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/flux/rusanov.f90 @@ -0,0 +1,41 @@ +! src/numerics/flux/rusanov.f90 +module rusanov_flux_module + use, intrinsic :: iso_fortran_env, only: real64 + use flux_base_module, only: flux_calculator_base + implicit none + + private + public :: real64, rusanov_flux + + type, extends(flux_calculator_base) :: rusanov_flux + real(real64) :: wave_speed_default = 1.0_real64 + contains + procedure :: info => rusanov_info + end type rusanov_flux + + ! 构造函数接口 + interface rusanov_flux + module procedure create_rusanov_flux + end interface + +contains + + ! 构造函数 + type(rusanov_flux) function create_rusanov_flux() result(this) + this%name = "Rusanov" + this%wave_speed_default = 1.0_real64 + end function create_rusanov_flux + + subroutine rusanov_info(this) + class(rusanov_flux), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Flux calculator information:" + call this%print_basic_info() + + ! 添加Rusanov特有信息 + print *, " Type: Rusanov flux" + print *, " Default wave speed: ", this%wave_speed_default + end subroutine rusanov_info + +end module rusanov_flux_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..5e4b938d5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,22 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "Configuring reconstructor module...") + +# Start with just the base module +add_library(reconstructor STATIC + base.f90 + eno.f90 + weno3.f90 +) + +target_link_libraries(reconstructor PRIVATE core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Reconstructor module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..53798d021 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/reconstructor/base.f90 @@ -0,0 +1,33 @@ +!src/numerics/reconstructor/base.f90 +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, reconstructor_base + + ! Base reconstructor type + type :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + contains + procedure :: info => reconstructor_info + procedure :: print_basic_info => reconstructor_print_basic ! 添加一个辅助方法 + end type reconstructor_base + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + print *, "Reconstructor information:" + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_info + + subroutine reconstructor_print_basic(this) + class(reconstructor_base), intent(in) :: this + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_print_basic + +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/reconstructor/eno.f90 b/example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/reconstructor/eno.f90 new file mode 100644 index 000000000..a468b82ef --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/reconstructor/eno.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/eno.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, eno_reconstructor + + type, extends(reconstructor_base) :: eno_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => eno_info + end type eno_reconstructor + + ! 构造函数接口 + interface eno_reconstructor + module procedure create_eno_reconstructor + end interface + +contains + + ! 构造函数 + type(eno_reconstructor) function create_eno_reconstructor() result(this) + this%name = "ENO" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_eno_reconstructor + + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加ENO特有信息 + print *, " Type: ENO reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/reconstructor/weno3.f90 b/example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/reconstructor/weno3.f90 new file mode 100644 index 000000000..5e9542915 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02/src/numerics/reconstructor/weno3.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/weno3.f90 +module weno3_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno3_reconstructor + + type, extends(reconstructor_base) :: weno3_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno3_info + end type weno3_reconstructor + + ! 构造函数接口 + interface weno3_reconstructor + module procedure create_weno3_reconstructor + end interface + +contains + + ! 构造函数 + type(weno3_reconstructor) function create_weno3_reconstructor() result(this) + this%name = "WENO3" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_weno3_reconstructor + + subroutine weno3_info(this) + class(weno3_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加WENO3特有信息 + print *, " Type: WENO-3 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno3_info + +end module weno3_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02/tests/CMakeLists.txt new file mode 100644 index 000000000..0cd64b0c6 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02/tests/CMakeLists.txt @@ -0,0 +1,43 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +add_executable(test_minimal_simple test_minimal_simple.f90) + +message(STATUS "CMAKE_Fortran_MODULE_DIRECTORY=${CMAKE_Fortran_MODULE_DIRECTORY}") + +#target_include_directories( test_minimal_simple +# PRIVATE +# ${CMAKE_Fortran_MODULE_DIRECTORY} +#) + +target_link_libraries( test_minimal_simple + PRIVATE + infrastructure +) + +add_executable(test_simple_link test_simple_link.f90) +target_link_libraries(test_simple_link + PRIVATE + reconstructor + flux +) + + +#add_executable(test_factory_simple test_factory_simple.f90) + +#target_link_libraries( test_factory_simple +# PRIVATE +# core +# infrastructure +# reconstructor +# flux +#) + +add_executable(test_factory_simple test_factory_simple.f90) +target_link_libraries(test_factory_simple + PRIVATE + core + infrastructure + reconstructor + flux +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02/tests/test_factory_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/02/tests/test_factory_simple.f90 new file mode 100644 index 000000000..d4139bb1a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02/tests/test_factory_simple.f90 @@ -0,0 +1,86 @@ +!tests/test_factory_simple.f90 +program test_factory_simple + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module, only: initialize_registry, cleanup_registry, & + register_component_simple, has_component + use config_module, only: cfd_config, config_print + use mesh_module, only: mesh_type + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(eno_reconstructor) :: eno + type(weno3_reconstructor) :: weno3 + type(rusanov_flux) :: rusanov + + print *, "=== Factory Pattern Simple Test ===" + print *, "" + + ! Test 1: Basic systems + print *, "1. Testing basic systems..." + print *, "-----------------------------" + call config_print(config) + + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 2: Creating reconstructors + print *, "2. Testing reconstructors..." + print *, "------------------------------" + + ! 创建并测试ENO重构器 + print *, "Creating ENO reconstructor..." + eno = eno_reconstructor() ! 使用构造函数 + call eno%info() ! 必须调用info方法 + + print *, "" + print *, "Creating WENO3 reconstructor..." + weno3 = weno3_reconstructor() ! 使用构造函数 + call weno3%info() ! 必须调用info方法 + print *, "" + + ! Test 3: Creating flux calculator + print *, "3. Testing flux calculator..." + print *, "-------------------------------" + + print *, "Creating Rusanov flux calculator..." + rusanov = rusanov_flux() ! 使用构造函数 + call rusanov%info() ! 必须调用info方法 + print *, "" + + ! Test 4: Registry integration + print *, "4. Testing registry..." + print *, "----------------------" + + call initialize_registry(verbose=.true.) + + ! Register components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("flux", "rusanov") + + ! Check registration + if (has_component("reconstructor", "eno")) then + print *, "[OK] ENO reconstructor registered successfully" + end if + + if (has_component("reconstructor", "weno3")) then + print *, "[OK] WENO3 reconstructor registered successfully" + end if + + if (has_component("flux", "rusanov")) then + print *, "[OK] Rusanov flux registered successfully" + end if + + print *, "" + call cleanup_registry() + + print *, "=== Factory pattern simple test completed successfully ===" + +end program test_factory_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02/tests/test_minimal_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/02/tests/test_minimal_simple.f90 new file mode 100644 index 000000000..6213e8f89 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02/tests/test_minimal_simple.f90 @@ -0,0 +1,84 @@ +! tests/test_minimal_simple.f90 +program test_minimal_simple + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Simple Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components (simple version) + print *, "5. Testing registry functionality" + print *, "---------------------------------" + print *, "Registry initialized: ", registry_is_initialized() + print *, "Number of components: ", registry_get_size() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Simple test completed successfully ===" + +end program test_minimal_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02/tests/test_simple_link.f90 b/example/1d-linear-convection/weno3/fortran/registry/02/tests/test_simple_link.f90 new file mode 100644 index 000000000..807d0bc2c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02/tests/test_simple_link.f90 @@ -0,0 +1,108 @@ +! tests/test_minimal.f90 +program test_minimal + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i ! Declare loop variable here + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_all() + print *, "Registry size: ", component_registry%size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components + print *, "5. Testing available components" + print *, "--------------------------------" + + ! Use a separate block to avoid allocation issues + call test_available_components() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Minimal test completed successfully ===" + +contains + + ! Internal subroutine for testing available components + subroutine test_available_components() + character(len=:), allocatable :: names(:) + integer, allocatable :: orders(:) + integer :: j + + call get_available_components("reconstructor", names, orders) + + if (allocated(names)) then + print *, "Reconstructors:" + do j = 1, size(names) + print *, " - ", trim(names(j)) + if (allocated(orders)) then + print *, " Order: ", orders(j) + end if + end do + else + print *, "No reconstructors found" + end if + end subroutine test_available_components + +end program test_minimal \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02a/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02a/CMakeLists.txt new file mode 100644 index 000000000..ef66d5848 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02a/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02a/README.md b/example/1d-linear-convection/weno3/fortran/registry/02a/README.md new file mode 100644 index 000000000..c4889757b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02a/README.md @@ -0,0 +1,221 @@ +# Fortran CFD Project + +基于工厂模式和注册系统的CFD求解器框架。 + +## 项目结构 + +``` +fortran_cfd/ +├── CMakeLists.txt # 根CMake配置 +├── scripts/ # 构建脚本 +│ ├── build.py # Python构建脚本(推荐) +│ ├── build.bat # Batch包装脚本 +│ ├── build.ps1 # PowerShell包装脚本 +│ └── requirements.txt # Python依赖 +├── src/ # 源代码 +│ ├── CMakeLists.txt +│ ├── core/ # 核心模块(注册系统、工厂接口) +│ ├── infrastructure/ # 基础设施(配置、网格) +│ └── numerics/ # 数值方法 +├── tests/ # 测试 +│ ├── CMakeLists.txt +│ ├── test_minimal_simple.f90 # 简单测试 +│ └── test_factory.f90 # 工厂模式测试 +└── README.md # 项目说明(本文件) +``` + +## 快速开始 + +### 使用Python脚本(推荐) + +```bash +# 进入脚本目录 +cd scripts + +# 基本构建 +python build.py + +# 清理并构建 +python build.py --clean + +# 发布构建 +python build.py --build-type Release --clean + +# 并行构建(使用所有核心) +python build.py -j + +# 不运行测试 +python build.py --no-tests + +# 查看帮助 +python build.py --help +``` + +### 使用批处理脚本 + +```bash +cd scripts +.\build.bat +``` + +### 使用PowerShell脚本 + +```powershell +cd scripts +.\build.ps1 +``` + +## 手动构建 + +```bash +# 设置Intel环境 +cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && powershell' + +# 配置项目 +mkdir build +cd build +cmake -G "Visual Studio 17 2022" -A x64 -T fortran=ifx .. + +# 构建项目 +cmake --build . --config Debug + +# 运行测试 +Debug\test_simple.exe +Debug\test_factory.exe +``` + +## 功能特性 + +✅ 组件注册系统 +✅ 工厂模式 +✅ ENO/WENO重构器 +✅ Rusanov通量计算器 +✅ 可扩展架构 +✅ 自动化测试 + +## 依赖 + +- Intel oneAPI(包含ifx编译器) +- CMake 3.12+ +- Python 3.6+(仅用于构建脚本) + +## 源代码文件 + +### 核心模块 +- `src/core/registry.f90` - 组件注册系统 +- `src/core/factory_interfaces.f90` - 工厂接口 + +### 基础设施 +- `src/infrastructure/config.f90` - 配置管理 +- `src/infrastructure/mesh.f90` - 网格生成 + +### 数值方法 +- `src/numerics/reconstructor/base.f90` - 重构器基类 +- `src/numerics/reconstructor/eno.f90` - ENO重构器 +- `src/numerics/reconstructor/weno3.f90` - WENO-3重构器 +- `src/numerics/flux/base.f90` - 通量计算器基类 +- `src/numerics/flux/rusanov.f90` - Rusanov通量 + +### 测试 +- `tests/test_minimal_simple.f90` - 简单功能测试 +- `tests/test_factory.f90` - 工厂模式测试 + +## 构建脚本 + +### Python脚本 (build.py) +主构建脚本,支持: +- 自动设置Intel oneAPI环境 +- 参数化构建选项 +- 并行编译 +- 自动化测试 +- 彩色输出 + +### Batch脚本 (build.bat) +Windows批处理包装器,调用Python脚本。 + +### PowerShell脚本 (build.ps1) +PowerShell包装器,调用Python脚本。 + +## 示例输出 + +成功构建后,会看到类似输出: + +``` +============================================================ + Fortran CFD Project Builder +============================================================ + +[1/4] Setting up Intel Fortran compiler... +✓ Intel oneAPI environment configured + +[2/4] Preparing build directory... +✓ Cleaned build directory + +[3/4] Configuring project... + $ cmake -G Visual Studio 17 2022 -A x64 -T fortran=ifx -DCMAKE_BUILD_TYPE=Debug .. +✓ CMake configuration successful + +[4/4] Building project... + $ cmake --build . --config Debug +✓ Build completed in 12.3 seconds + +============================================================ + Running Tests +============================================================ + +[TEST] Simple functionality test... +✓ Simple test passed + +[TEST] Factory pattern test... +✓ Factory test passed + +============================================================ + Build Summary +============================================================ +✓ Build directory: D:\path\to\build +✓ Build type: Debug +✓ Total time: 15.2s +``` + +## 开发指南 + +### 添加新组件 + +1. 在相应模块中创建Fortran源文件 +2. 实现工厂函数 +3. 使用`register_component_with_factory`注册组件 +4. 添加测试 + +### 扩展架构 + +项目采用模块化设计: +1. **注册系统** - 管理所有可用的组件 +2. **工厂模式** - 动态创建组件实例 +3. **抽象接口** - 确保组件兼容性 +4. **配置驱动** - 通过配置文件控制行为 + +## 故障排除 + +### 常见问题 + +1. **Intel环境未找到** + - 检查Intel oneAPI安装路径 + - 手动运行`setvars.bat` + +2. **CMake配置失败** + - 确保使用正确的生成器:`Visual Studio 17 2022` + - 检查Fortran编译器是否可用 + +3. **构建失败** + - 检查依赖项是否完整 + - 查看详细的错误信息 + +### 调试建议 + +- 使用`--verbose`参数获取详细输出 +- 检查`build/CMakeCache.txt`了解配置详情 +- 查看编译器错误日志 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02a/scripts/build.bat b/example/1d-linear-convection/weno3/fortran/registry/02a/scripts/build.bat new file mode 100644 index 000000000..6fd6dc031 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02a/scripts/build.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Project Builder +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python build script with full Intel environment support... +echo. + +python build.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Build failed + pause + exit /b 1 +) + +echo. +echo [INFO] Build completed successfully! +echo. +echo [INFO] To run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02a/scripts/build.py b/example/1d-linear-convection/weno3/fortran/registry/02a/scripts/build.py new file mode 100644 index 000000000..3bf6d5375 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02a/scripts/build.py @@ -0,0 +1,629 @@ +#!/usr/bin/env python3 +""" +Fortran CFD Project Builder - 完整Python解决方案 +在Python内部处理Intel oneAPI环境配置 +""" + +import os +import sys +import subprocess +import shutil +import argparse +import time +import platform +import tempfile +from pathlib import Path + +class IntelEnvironment: + """Intel oneAPI环境管理器""" + + def __init__(self): + self.setvars_path = None + self.env_vars = {} + + def find_setvars(self): + """查找setvars.bat文件""" + possible_paths = [ + r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat", + r"C:\Program Files\Intel\oneAPI\setvars.bat", + r"C:\Program Files (x86)\Intel\oneAPI\compiler\latest\env\vars.bat", + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\setvars.bat"), + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\compiler\latest\env\vars.bat"), + ] + + for path in possible_paths: + if os.path.exists(path): + self.setvars_path = path + return True + + return False + + def setup_environment(self): + """设置Intel环境""" + if not self.find_setvars(): + return False + + try: + # 创建临时的批处理文件来捕获环境变量 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + f.write(f'@echo off\n') + f.write(f'call "{self.setvars_path}" >nul 2>&1\n') + f.write(f'set\n') # 输出所有环境变量 + temp_bat = f.name + + # 运行批处理文件并捕获输出 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + shell=True + ) + + # 解析环境变量 + for line in result.stdout.split('\n'): + line = line.strip() + if '=' in line: + key, value = line.split('=', 1) + self.env_vars[key.strip()] = value.strip() + + # 清理临时文件 + os.unlink(temp_bat) + + # 更新当前进程的环境变量 + os.environ.update(self.env_vars) + + return True + + except Exception as e: + print(f"设置Intel环境失败: {e}") + return False + + def get_compiler_info(self): + """获取编译器信息""" + info = {} + + # 检查ifx编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + env={**os.environ, **self.env_vars} if self.env_vars else os.environ + ) + + if result.returncode == 0: + for line in result.stdout.split('\n'): + if 'Version' in line or '版本' in line: + info['ifx_version'] = line.strip() + break + except: + pass + + # 检查环境变量 + info['ifx_root'] = self.env_vars.get('IFX_ROOT', '') + info['compiler_root'] = self.env_vars.get('ONEAPI_ROOT', '') + + return info + +class BuildSystem: + """构建系统主类""" + + def __init__(self): + self.project_root = Path(__file__).parent.parent + self.build_dir = self.project_root / "build" + self.intel_env = IntelEnvironment() + + # 设置控制台编码 + if sys.platform == "win32": + try: + import ctypes + # 设置控制台输出为UTF-8 + ctypes.windll.kernel32.SetConsoleOutputCP(65001) + except: + pass + + def print_header(self, text): + """打印标题""" + print(f"\n{'='*70}") + print(f" {text}") + print(f"{'='*70}\n") + + def print_step(self, step, total, message): + """打印步骤""" + print(f"[{step}/{total}] {message}...") + + def print_success(self, message): + """打印成功""" + print(f"\033[92m✓ {message}\033[0m") + + def print_error(self, message): + """打印错误""" + print(f"\033[91m✗ {message}\033[0m") + + def print_warning(self, message): + """打印警告""" + print(f"\033[93m! {message}\033[0m") + + def print_info(self, message): + """打印信息""" + print(f"\033[94mℹ {message}\033[0m") + + def check_prerequisites(self): + """检查前提条件""" + self.print_step(1, 6, "检查前提条件") + + # 检查Python版本 + python_version = sys.version.split()[0] + self.print_info(f"Python版本: {python_version}") + + # 检查平台 + self.print_info(f"平台: {platform.system()} {platform.release()}") + self.print_info(f"处理器核心数: {os.cpu_count()}") + + # 检查CMake + try: + result = subprocess.run( + ["cmake", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + version_line = result.stdout.split('\n')[0] + self.print_success(f"CMake: {version_line}") + else: + self.print_error("CMake未找到") + return False + except FileNotFoundError: + self.print_error("CMake未安装") + return False + + return True + + def setup_intel_environment(self, args): + """设置Intel环境""" + self.print_step(2, 6, "配置Intel oneAPI环境") + + if not self.intel_env.find_setvars(): + self.print_warning("未找到Intel oneAPI setvars.bat") + self.print_info("将尝试使用系统环境中的编译器") + + # 检查是否能直接访问编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + self.print_success("Intel编译器在系统PATH中找到") + return True + else: + self.print_warning("Intel编译器未在PATH中找到") + except: + self.print_warning("无法访问Intel编译器") + + return True # 继续,让CMake自己找编译器 + + # 设置环境 + if self.intel_env.setup_environment(): + compiler_info = self.intel_env.get_compiler_info() + + if compiler_info.get('ifx_version'): + self.print_success(f"Intel Fortran编译器: {compiler_info['ifx_version']}") + elif compiler_info.get('ifx_root'): + self.print_success(f"Intel编译器路径: {compiler_info['ifx_root']}") + else: + self.print_success("Intel oneAPI环境配置完成") + + return True + else: + self.print_warning("Intel环境配置失败,将继续使用系统环境") + return True + + def clean_build_directory(self, args): + """清理构建目录""" + if args.clean and self.build_dir.exists(): + self.print_info("清理构建目录...") + try: + shutil.rmtree(self.build_dir) + self.print_success("构建目录已清理") + except Exception as e: + self.print_error(f"清理失败: {e}") + if not args.force: + return False + return True + + def run_command(self, cmd, cwd=None, check=True, env=None): + """运行命令""" + if isinstance(cmd, list): + cmd_str = ' '.join(str(c) for c in cmd if c) + else: + cmd_str = str(cmd) + + print(f" \033[96m$\033[0m {cmd_str}") + + try: + # 合并环境变量 + exec_env = os.environ.copy() + if env: + exec_env.update(env) + if self.intel_env.env_vars: + exec_env.update(self.intel_env.env_vars) + + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=False, + env=exec_env + ) + + # 处理输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or '完成' in line or '生成' in line: + print(f" \033[92m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + if check and result.returncode != 0: + self.print_error(f"命令执行失败,退出码: {result.returncode}") + return False + + return True + + except Exception as e: + self.print_error(f"命令执行异常: {e}") + return False + + def configure_cmake(self, args): + """配置CMake""" + self.print_step(3, 6, "配置CMake项目") + + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + f"-DCMAKE_BUILD_TYPE={args.build_type}", + ] + + if args.compiler == "ifx": + cmake_cmd.extend(["-T", "fortran=ifx"]) + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + success = self.run_command(cmake_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("CMake配置完成") + else: + self.print_error("CMake配置失败") + + return success + + def build_project(self, args): + """构建项目""" + self.print_step(4, 6, "构建项目") + + build_cmd = [ + "cmake", + "--build", ".", + "--config", args.build_type, + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + success = self.run_command(build_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("项目构建完成") + else: + self.print_error("构建失败") + + return success + + def run_tests_with_environment(self, test_exe): + """运行单个测试,确保有Intel环境""" + try: + # 创建临时的批处理文件来运行测试 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'"{test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'"{test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + return result + + except Exception as e: + print(f"运行测试失败: {e}") + return None + + def run_tests(self, args): + """运行测试""" + self.print_step(5, 6, "运行测试") + + # 查找测试可执行文件 + test_dir = self.build_dir / "bin" / args.build_type + if not test_dir.exists(): + test_dir = self.build_dir / "bin" + if not test_dir.exists(): + test_dir = self.build_dir + + test_files = list(test_dir.glob("test_*.exe")) + + if not test_files: + self.print_warning("未找到测试程序") + return True + + all_passed = True + + for test_exe in sorted(test_files): + test_name = test_exe.stem + self.print_info(f"运行测试: {test_name}") + print(f" {'-'*50}") + + # 运行测试 + result = self.run_tests_with_environment(str(test_exe)) + + if result is None: + self.print_error(f" {test_name} 运行失败") + all_passed = False + continue + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + print(f" {line}") + + if result.returncode == 0: + self.print_success(f" {test_name} 通过") + else: + self.print_error(f" {test_name} 失败 (退出码: {result.returncode})") + all_passed = False + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + print() # 空行 + + return all_passed + + def create_test_runner(self, args): + """创建独立的测试运行器""" + self.print_step(6, 6, "创建测试运行器") + + runner_path = self.build_dir / "run_tests.bat" + + content = f'''@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Test Runner +echo ======================================== +echo. + +REM Setup Intel oneAPI environment +set "SETVARS_PATH={self.intel_env.setvars_path or ''}" +if exist "%SETVARS_PATH%" ( + call "%SETVARS_PATH%" >nul + echo [INFO] Intel environment configured +) else ( + echo [WARNING] Intel environment not found + echo [WARNING] Tests may fail without runtime libraries +) + +echo. + +REM Run all test executables +set "TEST_COUNT=0" +set "PASS_COUNT=0" + +for %%f in ("bin\\{args.build_type}\\test_*.exe") do ( + set /a TEST_COUNT+=1 + echo [TEST %%f] + echo {'-'*50} + + %%f + if errorlevel 1 ( + echo [FAILED] %%f + ) else ( + echo [PASSED] %%f + set /a PASS_COUNT+=1 + ) + echo. +) + +echo ======================================== +echo Tests: %PASS_COUNT%/%TEST_COUNT% passed +if %PASS_COUNT% equ %TEST_COUNT% ( + echo [SUCCESS] All tests passed! +) else ( + echo [FAILURE] Some tests failed +) +echo ======================================== + +pause +''' + + with open(runner_path, 'w', encoding='utf-8') as f: + f.write(content) + + self.print_success(f"测试运行器已创建: {runner_path}") + self.print_info(f"使用方法: cd build && run_tests.bat") + + return runner_path + + def generate_report(self, args, build_time, tests_passed): + """生成构建报告""" + self.print_header("构建完成") + + print(f"项目: {self.project_root.name}") + print(f"构建类型: {args.build_type}") + print(f"编译器: {args.compiler}") + print(f"并行作业: {args.jobs}") + print(f"总耗时: {build_time:.1f}秒") + print(f"测试结果: {'全部通过' if tests_passed else '有失败'}") + + # 显示生成的可执行文件 + bin_dir = self.build_dir / "bin" / args.build_type + if bin_dir.exists(): + print(f"\n生成的可执行文件:") + for exe in sorted(bin_dir.glob("*.exe")): + size_mb = exe.stat().st_size / (1024 * 1024) + print(f" • {exe.name} ({size_mb:.2f} MB)") + + # 显示测试运行器信息 + runner_path = self.build_dir / "run_tests.bat" + if runner_path.exists(): + print(f"\n独立测试运行器:") + print(f" • {runner_path.name}") + print(f" 在Intel oneAPI环境中运行所有测试") + + def run(self): + """运行构建系统""" + parser = argparse.ArgumentParser( + description="Fortran CFD项目构建工具", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认构建 + %(prog)s --clean # 清理后构建 + %(prog)s --build-type Release # Release构建 + %(prog)s --no-tests # 只构建,不运行测试 + %(prog)s -j8 --verbose # 8线程并行构建,详细输出 + """ + ) + + parser.add_argument("--build-type", choices=["Debug", "Release"], + default="Debug", help="构建类型") + parser.add_argument("--compiler", choices=["ifx", "ifort"], + default="ifx", help="Fortran编译器") + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-tests", action="store_true", + help="跳过测试") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + + args = parser.parse_args() + + # 开始构建 + start_time = time.time() + + self.print_header("Fortran CFD 项目构建系统") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 检查前提条件 + if not self.check_prerequisites(): + if not args.force: + return 1 + + # 2. 设置Intel环境 + if not self.setup_intel_environment(args): + if not args.force: + return 1 + + # 3. 清理目录 + if not self.clean_build_directory(args): + if not args.force: + return 1 + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 4. 配置CMake + if not self.configure_cmake(args): + if not args.force: + return 1 + + # 5. 构建项目 + if not self.build_project(args): + if not args.force: + return 1 + + # 6. 运行测试和创建测试运行器 + tests_passed = True + if not args.no_tests: + tests_passed = self.run_tests(args) + + # 创建测试运行器 + self.create_test_runner(args) # 传递 args 参数 + + # 7. 生成报告 + build_time = time.time() - start_time + self.generate_report(args, build_time, tests_passed) + + return 0 if tests_passed else 1 + + except KeyboardInterrupt: + self.print_error("\n构建被用户中断") + return 1 + except Exception as e: + self.print_error(f"构建过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + builder = BuildSystem() + return builder.run() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02a/scripts/requirements.txt b/example/1d-linear-convection/weno3/fortran/registry/02a/scripts/requirements.txt new file mode 100644 index 000000000..d21a12b45 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02a/scripts/requirements.txt @@ -0,0 +1,2 @@ +# 构建脚本的Python依赖(可选) +# 目前没有特殊依赖,保持空文件或添加未来可能需要的包 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02a/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02a/src/CMakeLists.txt new file mode 100644 index 000000000..ee38952bb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02a/src/CMakeLists.txt @@ -0,0 +1,14 @@ +# ==================== src\CMakeLists.txt ==================== + +# src/CMakeLists.txt +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(core) +add_subdirectory(infrastructure) +add_subdirectory(numerics) + +message(STATUS "源代码目录配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02a/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02a/src/core/CMakeLists.txt new file mode 100644 index 000000000..bcfd024d8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02a/src/core/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/core/CMakeLists.txt +message(STATUS "配置核心模块...") + +add_library(core STATIC + registry.f90 + factory_interfaces.f90 +) + +target_include_directories(core PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 安装规则 +install(TARGETS core + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) +install(FILES + ${CMAKE_Fortran_MODULE_DIRECTORY}/registry_module.mod + ${CMAKE_Fortran_MODULE_DIRECTORY}/factory_interfaces.mod + DESTINATION include/fortran_cfd/core +) + +message(STATUS "核心模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02a/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/02a/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..a960167c3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02a/src/core/factory_interfaces.f90 @@ -0,0 +1,17 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, factory_procedure + + ! Factory procedure interface + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02a/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/02a/src/core/registry.f90 new file mode 100644 index 000000000..d620f5ec9 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02a/src/core/registry.f90 @@ -0,0 +1,656 @@ +! src/core/registry.f90 +module registry_module + use, intrinsic :: iso_fortran_env, only: real64 + use factory_interfaces, only: factory_procedure + implicit none + + private + + ! ==================== PUBLIC INTERFACE ==================== + ! 原有接口保持不变 + public :: real64, component_info, component_registry + public :: register_component_simple, initialize_registry, cleanup_registry + public :: has_component, get_available_components + public :: registry_is_initialized, registry_get_size + + ! 新增工厂相关接口 + public :: factory_ptr_type, component_registry_entry + public :: register_component_with_factory + public :: create_component_from_registry + public :: has_factory_component + public :: list_factory_components + + ! ==================== TYPE DEFINITIONS ==================== + + ! 原有类型定义 + type :: component_info + character(len=32) :: category = "" + character(len=32) :: name = "" + integer :: order = 0 + contains + procedure :: print => ci_print + end type component_info + + ! 新增:工厂指针类型 + type :: factory_ptr_type + procedure(factory_procedure), pointer, nopass :: ptr => null() + end type factory_ptr_type + + ! 新增:带工厂的注册条目 + type :: component_registry_entry + character(len=32) :: category = "" + character(len=32) :: name = "" + integer :: order = 0 + type(factory_ptr_type) :: factory + logical :: has_factory = .false. + contains + procedure :: print_with_factory => entry_print + procedure :: can_create => entry_has_factory + end type component_registry_entry + + ! 原有注册表类型(扩展以支持工厂) + type :: component_registry_type + private + ! 简单注册组件(向后兼容) + type(component_info), allocatable :: simple_components(:) + integer :: simple_count = 0 + + ! 带工厂的组件(新增) + type(component_registry_entry), allocatable :: factory_components(:) + integer :: factory_count = 0 + + integer :: capacity = 100 + logical :: verbose = .true. + logical :: initialized = .false. + contains + ! 简单注册方法 + procedure :: register => cr_register ! ← 保持原名 + procedure :: get => cr_get ! ← 保持原名 + procedure :: list_simple => cr_list_simple ! ← 新增别名 + + ! 工厂注册方法 + procedure :: register_with_factory => cr_register_with_factory + procedure :: get_with_factory => cr_get_with_factory + procedure :: list_with_factory => cr_list_with_factory + procedure :: create_from_factory => cr_create_from_factory + + ! 通用方法 + procedure :: clear => cr_clear + procedure :: size => cr_size + procedure :: total_size => cr_total_size + procedure :: is_initialized => cr_is_initialized + end type component_registry_type + + ! Global registry instance + type(component_registry_type), save :: component_registry + +contains + + ! ==================== PUBLIC API ==================== + + ! Initialize registry (保持不变) + subroutine initialize_registry(initial_capacity, verbose) + integer, optional, intent(in) :: initial_capacity + logical, optional, intent(in) :: verbose + + if (component_registry%initialized) then + if (component_registry%verbose) then + print *, "[INFO] Registry already initialized" + end if + return + end if + + if (present(initial_capacity)) then + component_registry%capacity = max(10, initial_capacity) + end if + + if (present(verbose)) then + component_registry%verbose = verbose + end if + + ! Allocate arrays for both types + allocate(component_registry%simple_components(component_registry%capacity)) + allocate(component_registry%factory_components(component_registry%capacity)) + + component_registry%initialized = .true. + component_registry%simple_count = 0 + component_registry%factory_count = 0 + + if (component_registry%verbose) then + print *, "[INIT] Registry initialized, capacity:", component_registry%capacity + print *, " Supports both simple and factory-based registration" + end if + end subroutine initialize_registry + + ! Cleanup registry (保持不变) + subroutine cleanup_registry + call component_registry%clear() + if (component_registry%verbose) then + print *, "[CLEANUP] Registry cleaned up" + end if + end subroutine cleanup_registry + + ! ==================== SIMPLE REGISTRATION (向后兼容) ==================== + + ! Simple registration (no factory) - 保持不变 + subroutine register_component_simple(category, name) + character(len=*), intent(in) :: category, name + + type(component_info) :: info + + info%category = to_lower(trim(adjustl(category))) + info%name = to_lower(trim(adjustl(name))) + info%order = 0 + + call component_registry%register(info) + end subroutine register_component_simple + + ! Check if component exists (简单注册) + function has_component(category, name) result(found) + character(len=*), intent(in) :: category, name + logical :: found + + type(component_info) :: info + character(len=32) :: cat_lower, name_lower + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + info = component_registry%get(cat_lower, name_lower) + found = (len_trim(info%category) > 0) + end function has_component + + ! Get available components in a category (简单注册) + subroutine get_available_components(category, names, orders) + character(len=*), intent(in) :: category + character(len=:), allocatable, intent(out), optional :: names(:) + integer, allocatable, intent(out), optional :: orders(:) + + character(len=32) :: cat_lower + integer :: i, count, idx + type(component_info) :: info + + cat_lower = to_lower(trim(adjustl(category))) + + ! Count components in this category + count = 0 + do i = 1, component_registry%simple_count + if (component_registry%simple_components(i)%category == cat_lower) then + count = count + 1 + end if + end do + + ! Allocate arrays if requested + if (present(names)) then + allocate(character(len=32) :: names(count)) + end if + + if (present(orders)) then + allocate(orders(count)) + end if + + ! Fill arrays + idx = 1 + do i = 1, component_registry%simple_count + if (component_registry%simple_components(i)%category == cat_lower) then + info = component_registry%simple_components(i) + if (present(names)) then + names(idx) = info%name + end if + if (present(orders)) then + orders(idx) = info%order + end if + idx = idx + 1 + end if + end do + end subroutine get_available_components + + ! ==================== FACTORY REGISTRATION (新增) ==================== + + ! Register component with factory function + subroutine register_component_with_factory(category, name, factory_func, order) + character(len=*), intent(in) :: category, name + procedure(factory_procedure) :: factory_func + integer, optional, intent(in) :: order + + type(component_registry_entry) :: entry + character(len=32) :: cat_lower, name_lower + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + entry%category = cat_lower + entry%name = name_lower + + if (present(order)) then + entry%order = order + else + entry%order = 0 + end if + + entry%factory%ptr => factory_func + entry%has_factory = .true. + + call component_registry%register_with_factory(entry) + end subroutine register_component_with_factory + + ! Create component from registry using factory + function create_component_from_registry(category, name) result(instance) + character(len=*), intent(in) :: category, name + class(*), allocatable :: instance + + character(len=32) :: cat_lower, name_lower + integer :: status + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + call component_registry%create_from_factory(cat_lower, name_lower, instance, status) + + if (status /= 0) then + if (component_registry%verbose) then + print *, "[ERROR] Failed to create component: ", trim(category), ".", trim(name) + end if + end if + end function create_component_from_registry + + ! Check if component has factory + function has_factory_component(category, name) result(found) + character(len=*), intent(in) :: category, name + logical :: found + + character(len=32) :: cat_lower, name_lower + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + found = .false. + + if (.not. component_registry%initialized) return + + ! 简化的查找逻辑(实际应调用内部方法) + found = component_registry%factory_count > 0 ! 占位实现 + end function has_factory_component + + ! List all factory components + subroutine list_factory_components(category) + character(len=*), optional, intent(in) :: category + + if (present(category)) then + call component_registry%list_with_factory(category) + else + call component_registry%list_with_factory("") + end if + end subroutine list_factory_components + + ! ==================== PUBLIC UTILITY FUNCTIONS ==================== + + ! Public function to check if registry is initialized + function registry_is_initialized() result(is_initialized) + logical :: is_initialized + is_initialized = component_registry%is_initialized() + end function registry_is_initialized + + ! Public function to get total registry size + function registry_get_size() result(size_val) + integer :: size_val + size_val = component_registry%total_size() + end function registry_get_size + + ! ==================== COMPONENT INFO METHODS ==================== + + subroutine ci_print(this) + class(component_info), intent(in) :: this + + if (this%order > 0) then + print *, " [", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ")]" + else + print *, " [", trim(this%category), ".", trim(this%name), "]" + end if + end subroutine ci_print + + subroutine entry_print(this) + class(component_registry_entry), intent(in) :: this + + if (this%has_factory) then + if (this%order > 0) then + print *, " [FACTORY] ", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ")" + else + print *, " [FACTORY] ", trim(this%category), ".", trim(this%name) + end if + else + if (this%order > 0) then + print *, " [SIMPLE] ", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ")" + else + print *, " [SIMPLE] ", trim(this%category), ".", trim(this%name) + end if + end if + end subroutine entry_print + + logical function entry_has_factory(this) + class(component_registry_entry), intent(in) :: this + entry_has_factory = this%has_factory + end function entry_has_factory + + ! ==================== REGISTRY INTERNAL METHODS ==================== + + ! ---------- Simple Registration Methods ---------- + + subroutine cr_register(this, info) + class(component_registry_type), intent(inout) :: this + type(component_info), intent(in) :: info + + type(component_info), allocatable :: temp(:) + integer :: i + + if (.not. this%initialized) then + error stop "[ERROR] Registry not initialized, call initialize_registry first" + end if + + ! Check if already exists + do i = 1, this%simple_count + if (this%simple_components(i)%category == info%category .and. & + this%simple_components(i)%name == info%name) then + if (this%verbose) then + print *, "[WARN] Overwriting simple component: ", & + trim(info%category), ".", trim(info%name) + end if + this%simple_components(i) = info + return + end if + end do + + ! Expand array if needed + if (this%simple_count >= this%capacity) then + this%capacity = this%capacity * 2 + allocate(temp(this%capacity)) + temp(1:this%simple_count) = this%simple_components(1:this%simple_count) + call move_alloc(temp, this%simple_components) + + if (this%verbose) then + print *, "[INFO] Simple registry expanded to capacity:", this%capacity + end if + end if + + ! Add component + this%simple_count = this%simple_count + 1 + this%simple_components(this%simple_count) = info + + if (this%verbose) then + print *, "[OK] Registered simple: ", trim(info%category), ".", trim(info%name) + end if + end subroutine cr_register + + function cr_get(this, category, name) result(info) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category, name + type(component_info) :: info + + integer :: i + + ! Initialize return value as empty + info%category = "" + info%name = "" + info%order = 0 + + if (.not. this%initialized) then + return + end if + + do i = 1, this%simple_count + if (this%simple_components(i)%category == category .and. & + this%simple_components(i)%name == name) then + info = this%simple_components(i) + return + end if + end do + end function cr_get + + subroutine cr_list_simple(this) + class(component_registry_type), intent(in) :: this + integer :: i + + if (.not. this%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + if (this%simple_count == 0) then + print *, "[INFO] No simple components registered" + return + end if + + print *, "=== Simple Registry Contents (", this%simple_count, " components) ===" + do i = 1, this%simple_count + call this%simple_components(i)%print() + end do + print *, "==========================================" + end subroutine cr_list_simple + + ! ---------- Factory Registration Methods ---------- + + subroutine cr_register_with_factory(this, entry) + class(component_registry_type), intent(inout) :: this + type(component_registry_entry), intent(in) :: entry + + type(component_registry_entry), allocatable :: temp(:) + integer :: i + + if (.not. this%initialized) then + error stop "[ERROR] Registry not initialized, call initialize_registry first" + end if + + ! Check if already exists + do i = 1, this%factory_count + if (this%factory_components(i)%category == entry%category .and. & + this%factory_components(i)%name == entry%name) then + if (this%verbose) then + print *, "[WARN] Overwriting factory component: ", & + trim(entry%category), ".", trim(entry%name) + end if + this%factory_components(i) = entry + return + end if + end do + + ! Expand array if needed + if (this%factory_count >= this%capacity) then + this%capacity = this%capacity * 2 + allocate(temp(this%capacity)) + temp(1:this%factory_count) = this%factory_components(1:this%factory_count) + call move_alloc(temp, this%factory_components) + + if (this%verbose) then + print *, "[INFO] Factory registry expanded to capacity:", this%capacity + end if + end if + + ! Add component + this%factory_count = this%factory_count + 1 + this%factory_components(this%factory_count) = entry + + if (this%verbose) then + print *, "[FACTORY] Registered with factory: ", & + trim(entry%category), ".", trim(entry%name) + end if + end subroutine cr_register_with_factory + + function cr_get_with_factory(this, category, name) result(entry) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category, name + type(component_registry_entry) :: entry + + integer :: i + + ! Initialize return value as empty + entry%category = "" + entry%name = "" + entry%order = 0 + entry%factory%ptr => null() + entry%has_factory = .false. + + if (.not. this%initialized) then + return + end if + + do i = 1, this%factory_count + if (this%factory_components(i)%category == category .and. & + this%factory_components(i)%name == name) then + entry = this%factory_components(i) + return + end if + end do + end function cr_get_with_factory + + subroutine cr_list_with_factory(this, category) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category + + character(len=32) :: cat_lower + integer :: i, count + + if (.not. this%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + cat_lower = to_lower(trim(adjustl(category))) + + if (this%factory_count == 0) then + print *, "[INFO] No factory components registered" + return + end if + + ! Count components in this category + count = 0 + do i = 1, this%factory_count + if (len_trim(cat_lower) == 0 .or. & + this%factory_components(i)%category == cat_lower) then + count = count + 1 + end if + end do + + if (count == 0) then + if (len_trim(cat_lower) > 0) then + print *, "[INFO] No factory components in category: ", trim(cat_lower) + else + print *, "[INFO] No factory components registered" + end if + return + end if + + print *, "=== Factory Registry Contents (", count, " components) ===" + do i = 1, this%factory_count + if (len_trim(cat_lower) == 0 .or. & + this%factory_components(i)%category == cat_lower) then + call this%factory_components(i)%print_with_factory() + end if + end do + print *, "==========================================" + end subroutine cr_list_with_factory + + subroutine cr_create_from_factory(this, category, name, instance, status) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category, name + class(*), allocatable, intent(out) :: instance + integer, intent(out) :: status + + type(component_registry_entry) :: entry + integer :: i + + status = -1 ! Default: not found + + if (.not. this%initialized) then + if (this%verbose) then + print *, "[ERROR] Registry not initialized" + end if + return + end if + + ! Find the entry + do i = 1, this%factory_count + if (this%factory_components(i)%category == category .and. & + this%factory_components(i)%name == name) then + entry = this%factory_components(i) + + if (entry%has_factory .and. associated(entry%factory%ptr)) then + ! Call the factory function + call entry%factory%ptr(instance) + status = 0 ! Success + + if (this%verbose) then + print *, "[FACTORY] Created component: ", & + trim(category), ".", trim(name) + end if + else + status = -2 ! No factory function + if (this%verbose) then + print *, "[ERROR] No factory function for: ", & + trim(category), ".", trim(name) + end if + end if + return + end if + end do + + ! If we get here, component not found + if (this%verbose) then + print *, "[ERROR] Factory component not found: ", & + trim(category), ".", trim(name) + end if + end subroutine cr_create_from_factory + + ! ---------- Common Methods ---------- + + subroutine cr_clear(this) + class(component_registry_type), intent(inout) :: this + + if (allocated(this%simple_components)) then + deallocate(this%simple_components) + end if + + if (allocated(this%factory_components)) then + deallocate(this%factory_components) + end if + + this%simple_count = 0 + this%factory_count = 0 + this%capacity = 100 + this%initialized = .false. + end subroutine cr_clear + + integer function cr_total_size(this) + class(component_registry_type), intent(in) :: this + cr_total_size = this%simple_count + this%factory_count + end function cr_total_size + + integer function cr_size(this) + class(component_registry_type), intent(in) :: this + cr_size = this%simple_count ! Backward compatibility + end function cr_size + + logical function cr_is_initialized(this) + class(component_registry_type), intent(in) :: this + cr_is_initialized = this%initialized + end function cr_is_initialized + + ! ==================== UTILITY FUNCTIONS ==================== + + function to_lower(str) result(lower_str) + character(len=*), intent(in) :: str + character(len=len(str)) :: lower_str + integer :: i + + do i = 1, len(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + lower_str(i:i) = char(ichar(str(i:i)) + 32) + else + lower_str(i:i) = str(i:i) + end if + end do + end function to_lower + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02a/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02a/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..46964ce79 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02a/src/infrastructure/CMakeLists.txt @@ -0,0 +1,20 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "配置基础设施模块...") + +add_library(infrastructure STATIC + config.f90 + mesh.f90 +) + +target_link_libraries(infrastructure PRIVATE core) + +target_include_directories(infrastructure PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS infrastructure + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "基础设施模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02a/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/02a/src/infrastructure/config.f90 new file mode 100644 index 000000000..d3b1e0dfb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02a/src/infrastructure/config.f90 @@ -0,0 +1,90 @@ +! src/infrastructure/config.f90 +module config_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, cfd_config, config_print, config_with_reconstruction + + ! CFD configuration type + type :: cfd_config + character(len=20) :: ic_type = "step" + character(len=20) :: recon_scheme = "eno" + character(len=20) :: flux_type = "rusanov" + integer :: rk_order = 1 + real(real64) :: wave_speed = 1.0_real64 + real(real64) :: final_time = 0.625_real64 + real(real64) :: dt = 0.025_real64 + character(len=20) :: boundary_type = "periodic" + real(real64) :: left_boundary_value = 1.0_real64 + real(real64) :: right_boundary_value = 2.0_real64 + integer :: spatial_order = 2 + logical :: verbose = .true. + end type cfd_config + +contains + + subroutine config_print(this) + type(cfd_config), intent(in) :: this + + print *, "=== CFD Configuration ===" + print *, "Initial condition: ", trim(this%ic_type) + print *, "Reconstruction: ", trim(this%recon_scheme), " (order:", this%spatial_order, ")" + print *, "Flux type: ", trim(this%flux_type) + print *, "Time integration: RK", this%rk_order + print *, "Wave speed: ", this%wave_speed + print *, "Final time: ", this%final_time + print *, "Time step: ", this%dt + print *, "Boundary: ", trim(this%boundary_type) + if (trim(this%boundary_type) == 'dirichlet') then + print *, " Dirichlet values: [", this%left_boundary_value, ", ", & + this%right_boundary_value, "]" + end if + print *, "===============================" + end subroutine config_print + + subroutine config_with_reconstruction(this, scheme, order) + type(cfd_config), intent(inout) :: this + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + character(len=20) :: scheme_lower + + ! Convert to lowercase + scheme_lower = scheme + call to_lower_inplace(scheme_lower) + this%recon_scheme = trim(adjustl(scheme_lower)) + + ! Set order + if (present(order)) then + this%spatial_order = order + else + ! Smart defaults + if (index(this%recon_scheme, 'weno') > 0) then + this%spatial_order = 5 + else if (trim(this%recon_scheme) == 'eno') then + this%spatial_order = 3 + else + print *, "[ERROR] Unsupported reconstruction scheme: ", trim(this%recon_scheme) + return + end if + end if + + if (this%verbose) then + print *, "[CONFIG] Reconstruction: ", trim(this%recon_scheme), & + " Order: ", this%spatial_order + end if + end subroutine config_with_reconstruction + + subroutine to_lower_inplace(str) + character(len=*), intent(inout) :: str + integer :: i + + do i = 1, len_trim(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + str(i:i) = char(ichar(str(i:i)) + 32) + end if + end do + end subroutine to_lower_inplace + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02a/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/02a/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..896969bd0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02a/src/infrastructure/mesh.f90 @@ -0,0 +1,74 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, mesh_type, mesh_init, mesh_print_info + + ! mesh + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer :: ncells = 40 + integer :: nnodes + integer :: nx + real(wp), allocatable :: x(:) ! + real(wp), allocatable :: xcc(:) ! + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer, optional, intent(in) :: ncells + + integer :: i + + ! Set + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! computation + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! node + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! cell + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== ===" + print *, ": [", this%xmin, ", ", this%xmax, "]" + print *, ": ", this%ncells + print *, ": ", this%nnodes + print *, " dx: ", this%dx + print *, " L: ", this%L + print *, "==========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..66874a7ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/CMakeLists.txt @@ -0,0 +1,7 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..3fde7746b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,28 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +# Start with just the base module +add_library(flux STATIC + base.f90 + rusanov.f90 +) + +#target_link_libraries(flux PRIVATE core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 明确设置不使用 /Qm64 选项 +if(CMAKE_Fortran_COMPILER_ID MATCHES "IntelLLVM|Intel") + set_target_properties(flux PROPERTIES + Fortran_FLAGS "${CMAKE_Fortran_FLAGS} /Qm64" # 明确设置,避免继承问题 + ) +endif() + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/flux/base.f90 new file mode 100644 index 000000000..7080a7ae4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/flux/base.f90 @@ -0,0 +1,30 @@ +!src/numerics/flux/base.f90 +module flux_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, flux_calculator_base + + ! Base flux calculator type + type :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure :: info => flux_info + procedure :: print_basic_info => flux_print_basic ! 添加辅助方法 + end type flux_calculator_base + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "Flux calculator information:" + print *, " Name: ", trim(this%name) + end subroutine flux_info + + subroutine flux_print_basic(this) + class(flux_calculator_base), intent(in) :: this + print *, " Name: ", trim(this%name) + end subroutine flux_print_basic + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/flux/rusanov.f90 b/example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/flux/rusanov.f90 new file mode 100644 index 000000000..7140f710b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/flux/rusanov.f90 @@ -0,0 +1,41 @@ +! src/numerics/flux/rusanov.f90 +module rusanov_flux_module + use, intrinsic :: iso_fortran_env, only: real64 + use flux_base_module, only: flux_calculator_base + implicit none + + private + public :: real64, rusanov_flux + + type, extends(flux_calculator_base) :: rusanov_flux + real(real64) :: wave_speed_default = 1.0_real64 + contains + procedure :: info => rusanov_info + end type rusanov_flux + + ! 构造函数接口 + interface rusanov_flux + module procedure create_rusanov_flux + end interface + +contains + + ! 构造函数 + type(rusanov_flux) function create_rusanov_flux() result(this) + this%name = "Rusanov" + this%wave_speed_default = 1.0_real64 + end function create_rusanov_flux + + subroutine rusanov_info(this) + class(rusanov_flux), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Flux calculator information:" + call this%print_basic_info() + + ! 添加Rusanov特有信息 + print *, " Type: Rusanov flux" + print *, " Default wave speed: ", this%wave_speed_default + end subroutine rusanov_info + +end module rusanov_flux_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..5e4b938d5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,22 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "Configuring reconstructor module...") + +# Start with just the base module +add_library(reconstructor STATIC + base.f90 + eno.f90 + weno3.f90 +) + +target_link_libraries(reconstructor PRIVATE core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Reconstructor module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..53798d021 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/reconstructor/base.f90 @@ -0,0 +1,33 @@ +!src/numerics/reconstructor/base.f90 +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, reconstructor_base + + ! Base reconstructor type + type :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + contains + procedure :: info => reconstructor_info + procedure :: print_basic_info => reconstructor_print_basic ! 添加一个辅助方法 + end type reconstructor_base + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + print *, "Reconstructor information:" + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_info + + subroutine reconstructor_print_basic(this) + class(reconstructor_base), intent(in) :: this + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_print_basic + +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/reconstructor/eno.f90 b/example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/reconstructor/eno.f90 new file mode 100644 index 000000000..a468b82ef --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/reconstructor/eno.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/eno.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, eno_reconstructor + + type, extends(reconstructor_base) :: eno_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => eno_info + end type eno_reconstructor + + ! 构造函数接口 + interface eno_reconstructor + module procedure create_eno_reconstructor + end interface + +contains + + ! 构造函数 + type(eno_reconstructor) function create_eno_reconstructor() result(this) + this%name = "ENO" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_eno_reconstructor + + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加ENO特有信息 + print *, " Type: ENO reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/reconstructor/weno3.f90 b/example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/reconstructor/weno3.f90 new file mode 100644 index 000000000..5e9542915 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02a/src/numerics/reconstructor/weno3.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/weno3.f90 +module weno3_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno3_reconstructor + + type, extends(reconstructor_base) :: weno3_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno3_info + end type weno3_reconstructor + + ! 构造函数接口 + interface weno3_reconstructor + module procedure create_weno3_reconstructor + end interface + +contains + + ! 构造函数 + type(weno3_reconstructor) function create_weno3_reconstructor() result(this) + this%name = "WENO3" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_weno3_reconstructor + + subroutine weno3_info(this) + class(weno3_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加WENO3特有信息 + print *, " Type: WENO-3 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno3_info + +end module weno3_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02a/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02a/tests/CMakeLists.txt new file mode 100644 index 000000000..0cd64b0c6 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02a/tests/CMakeLists.txt @@ -0,0 +1,43 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +add_executable(test_minimal_simple test_minimal_simple.f90) + +message(STATUS "CMAKE_Fortran_MODULE_DIRECTORY=${CMAKE_Fortran_MODULE_DIRECTORY}") + +#target_include_directories( test_minimal_simple +# PRIVATE +# ${CMAKE_Fortran_MODULE_DIRECTORY} +#) + +target_link_libraries( test_minimal_simple + PRIVATE + infrastructure +) + +add_executable(test_simple_link test_simple_link.f90) +target_link_libraries(test_simple_link + PRIVATE + reconstructor + flux +) + + +#add_executable(test_factory_simple test_factory_simple.f90) + +#target_link_libraries( test_factory_simple +# PRIVATE +# core +# infrastructure +# reconstructor +# flux +#) + +add_executable(test_factory_simple test_factory_simple.f90) +target_link_libraries(test_factory_simple + PRIVATE + core + infrastructure + reconstructor + flux +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02a/tests/test_factory_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/02a/tests/test_factory_simple.f90 new file mode 100644 index 000000000..d4139bb1a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02a/tests/test_factory_simple.f90 @@ -0,0 +1,86 @@ +!tests/test_factory_simple.f90 +program test_factory_simple + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module, only: initialize_registry, cleanup_registry, & + register_component_simple, has_component + use config_module, only: cfd_config, config_print + use mesh_module, only: mesh_type + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(eno_reconstructor) :: eno + type(weno3_reconstructor) :: weno3 + type(rusanov_flux) :: rusanov + + print *, "=== Factory Pattern Simple Test ===" + print *, "" + + ! Test 1: Basic systems + print *, "1. Testing basic systems..." + print *, "-----------------------------" + call config_print(config) + + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 2: Creating reconstructors + print *, "2. Testing reconstructors..." + print *, "------------------------------" + + ! 创建并测试ENO重构器 + print *, "Creating ENO reconstructor..." + eno = eno_reconstructor() ! 使用构造函数 + call eno%info() ! 必须调用info方法 + + print *, "" + print *, "Creating WENO3 reconstructor..." + weno3 = weno3_reconstructor() ! 使用构造函数 + call weno3%info() ! 必须调用info方法 + print *, "" + + ! Test 3: Creating flux calculator + print *, "3. Testing flux calculator..." + print *, "-------------------------------" + + print *, "Creating Rusanov flux calculator..." + rusanov = rusanov_flux() ! 使用构造函数 + call rusanov%info() ! 必须调用info方法 + print *, "" + + ! Test 4: Registry integration + print *, "4. Testing registry..." + print *, "----------------------" + + call initialize_registry(verbose=.true.) + + ! Register components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("flux", "rusanov") + + ! Check registration + if (has_component("reconstructor", "eno")) then + print *, "[OK] ENO reconstructor registered successfully" + end if + + if (has_component("reconstructor", "weno3")) then + print *, "[OK] WENO3 reconstructor registered successfully" + end if + + if (has_component("flux", "rusanov")) then + print *, "[OK] Rusanov flux registered successfully" + end if + + print *, "" + call cleanup_registry() + + print *, "=== Factory pattern simple test completed successfully ===" + +end program test_factory_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02a/tests/test_minimal_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/02a/tests/test_minimal_simple.f90 new file mode 100644 index 000000000..cc6753b5f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02a/tests/test_minimal_simple.f90 @@ -0,0 +1,84 @@ +! tests/test_minimal_simple.f90 +program test_minimal_simple + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Simple Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_simple() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components (simple version) + print *, "5. Testing registry functionality" + print *, "---------------------------------" + print *, "Registry initialized: ", registry_is_initialized() + print *, "Number of components: ", registry_get_size() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Simple test completed successfully ===" + +end program test_minimal_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02a/tests/test_simple_link.f90 b/example/1d-linear-convection/weno3/fortran/registry/02a/tests/test_simple_link.f90 new file mode 100644 index 000000000..27b165b00 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02a/tests/test_simple_link.f90 @@ -0,0 +1,108 @@ +! tests/test_minimal.f90 +program test_minimal + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i ! Declare loop variable here + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_simple() + print *, "Registry size: ", component_registry%size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components + print *, "5. Testing available components" + print *, "--------------------------------" + + ! Use a separate block to avoid allocation issues + call test_available_components() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Minimal test completed successfully ===" + +contains + + ! Internal subroutine for testing available components + subroutine test_available_components() + character(len=:), allocatable :: names(:) + integer, allocatable :: orders(:) + integer :: j + + call get_available_components("reconstructor", names, orders) + + if (allocated(names)) then + print *, "Reconstructors:" + do j = 1, size(names) + print *, " - ", trim(names(j)) + if (allocated(orders)) then + print *, " Order: ", orders(j) + end if + end do + else + print *, "No reconstructors found" + end if + end subroutine test_available_components + +end program test_minimal \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02b/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02b/CMakeLists.txt new file mode 100644 index 000000000..ef66d5848 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02b/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02b/README.md b/example/1d-linear-convection/weno3/fortran/registry/02b/README.md new file mode 100644 index 000000000..c4889757b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02b/README.md @@ -0,0 +1,221 @@ +# Fortran CFD Project + +基于工厂模式和注册系统的CFD求解器框架。 + +## 项目结构 + +``` +fortran_cfd/ +├── CMakeLists.txt # 根CMake配置 +├── scripts/ # 构建脚本 +│ ├── build.py # Python构建脚本(推荐) +│ ├── build.bat # Batch包装脚本 +│ ├── build.ps1 # PowerShell包装脚本 +│ └── requirements.txt # Python依赖 +├── src/ # 源代码 +│ ├── CMakeLists.txt +│ ├── core/ # 核心模块(注册系统、工厂接口) +│ ├── infrastructure/ # 基础设施(配置、网格) +│ └── numerics/ # 数值方法 +├── tests/ # 测试 +│ ├── CMakeLists.txt +│ ├── test_minimal_simple.f90 # 简单测试 +│ └── test_factory.f90 # 工厂模式测试 +└── README.md # 项目说明(本文件) +``` + +## 快速开始 + +### 使用Python脚本(推荐) + +```bash +# 进入脚本目录 +cd scripts + +# 基本构建 +python build.py + +# 清理并构建 +python build.py --clean + +# 发布构建 +python build.py --build-type Release --clean + +# 并行构建(使用所有核心) +python build.py -j + +# 不运行测试 +python build.py --no-tests + +# 查看帮助 +python build.py --help +``` + +### 使用批处理脚本 + +```bash +cd scripts +.\build.bat +``` + +### 使用PowerShell脚本 + +```powershell +cd scripts +.\build.ps1 +``` + +## 手动构建 + +```bash +# 设置Intel环境 +cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && powershell' + +# 配置项目 +mkdir build +cd build +cmake -G "Visual Studio 17 2022" -A x64 -T fortran=ifx .. + +# 构建项目 +cmake --build . --config Debug + +# 运行测试 +Debug\test_simple.exe +Debug\test_factory.exe +``` + +## 功能特性 + +✅ 组件注册系统 +✅ 工厂模式 +✅ ENO/WENO重构器 +✅ Rusanov通量计算器 +✅ 可扩展架构 +✅ 自动化测试 + +## 依赖 + +- Intel oneAPI(包含ifx编译器) +- CMake 3.12+ +- Python 3.6+(仅用于构建脚本) + +## 源代码文件 + +### 核心模块 +- `src/core/registry.f90` - 组件注册系统 +- `src/core/factory_interfaces.f90` - 工厂接口 + +### 基础设施 +- `src/infrastructure/config.f90` - 配置管理 +- `src/infrastructure/mesh.f90` - 网格生成 + +### 数值方法 +- `src/numerics/reconstructor/base.f90` - 重构器基类 +- `src/numerics/reconstructor/eno.f90` - ENO重构器 +- `src/numerics/reconstructor/weno3.f90` - WENO-3重构器 +- `src/numerics/flux/base.f90` - 通量计算器基类 +- `src/numerics/flux/rusanov.f90` - Rusanov通量 + +### 测试 +- `tests/test_minimal_simple.f90` - 简单功能测试 +- `tests/test_factory.f90` - 工厂模式测试 + +## 构建脚本 + +### Python脚本 (build.py) +主构建脚本,支持: +- 自动设置Intel oneAPI环境 +- 参数化构建选项 +- 并行编译 +- 自动化测试 +- 彩色输出 + +### Batch脚本 (build.bat) +Windows批处理包装器,调用Python脚本。 + +### PowerShell脚本 (build.ps1) +PowerShell包装器,调用Python脚本。 + +## 示例输出 + +成功构建后,会看到类似输出: + +``` +============================================================ + Fortran CFD Project Builder +============================================================ + +[1/4] Setting up Intel Fortran compiler... +✓ Intel oneAPI environment configured + +[2/4] Preparing build directory... +✓ Cleaned build directory + +[3/4] Configuring project... + $ cmake -G Visual Studio 17 2022 -A x64 -T fortran=ifx -DCMAKE_BUILD_TYPE=Debug .. +✓ CMake configuration successful + +[4/4] Building project... + $ cmake --build . --config Debug +✓ Build completed in 12.3 seconds + +============================================================ + Running Tests +============================================================ + +[TEST] Simple functionality test... +✓ Simple test passed + +[TEST] Factory pattern test... +✓ Factory test passed + +============================================================ + Build Summary +============================================================ +✓ Build directory: D:\path\to\build +✓ Build type: Debug +✓ Total time: 15.2s +``` + +## 开发指南 + +### 添加新组件 + +1. 在相应模块中创建Fortran源文件 +2. 实现工厂函数 +3. 使用`register_component_with_factory`注册组件 +4. 添加测试 + +### 扩展架构 + +项目采用模块化设计: +1. **注册系统** - 管理所有可用的组件 +2. **工厂模式** - 动态创建组件实例 +3. **抽象接口** - 确保组件兼容性 +4. **配置驱动** - 通过配置文件控制行为 + +## 故障排除 + +### 常见问题 + +1. **Intel环境未找到** + - 检查Intel oneAPI安装路径 + - 手动运行`setvars.bat` + +2. **CMake配置失败** + - 确保使用正确的生成器:`Visual Studio 17 2022` + - 检查Fortran编译器是否可用 + +3. **构建失败** + - 检查依赖项是否完整 + - 查看详细的错误信息 + +### 调试建议 + +- 使用`--verbose`参数获取详细输出 +- 检查`build/CMakeCache.txt`了解配置详情 +- 查看编译器错误日志 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02b/scripts/build.bat b/example/1d-linear-convection/weno3/fortran/registry/02b/scripts/build.bat new file mode 100644 index 000000000..6fd6dc031 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02b/scripts/build.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Project Builder +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python build script with full Intel environment support... +echo. + +python build.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Build failed + pause + exit /b 1 +) + +echo. +echo [INFO] Build completed successfully! +echo. +echo [INFO] To run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02b/scripts/build.py b/example/1d-linear-convection/weno3/fortran/registry/02b/scripts/build.py new file mode 100644 index 000000000..3bf6d5375 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02b/scripts/build.py @@ -0,0 +1,629 @@ +#!/usr/bin/env python3 +""" +Fortran CFD Project Builder - 完整Python解决方案 +在Python内部处理Intel oneAPI环境配置 +""" + +import os +import sys +import subprocess +import shutil +import argparse +import time +import platform +import tempfile +from pathlib import Path + +class IntelEnvironment: + """Intel oneAPI环境管理器""" + + def __init__(self): + self.setvars_path = None + self.env_vars = {} + + def find_setvars(self): + """查找setvars.bat文件""" + possible_paths = [ + r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat", + r"C:\Program Files\Intel\oneAPI\setvars.bat", + r"C:\Program Files (x86)\Intel\oneAPI\compiler\latest\env\vars.bat", + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\setvars.bat"), + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\compiler\latest\env\vars.bat"), + ] + + for path in possible_paths: + if os.path.exists(path): + self.setvars_path = path + return True + + return False + + def setup_environment(self): + """设置Intel环境""" + if not self.find_setvars(): + return False + + try: + # 创建临时的批处理文件来捕获环境变量 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + f.write(f'@echo off\n') + f.write(f'call "{self.setvars_path}" >nul 2>&1\n') + f.write(f'set\n') # 输出所有环境变量 + temp_bat = f.name + + # 运行批处理文件并捕获输出 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + shell=True + ) + + # 解析环境变量 + for line in result.stdout.split('\n'): + line = line.strip() + if '=' in line: + key, value = line.split('=', 1) + self.env_vars[key.strip()] = value.strip() + + # 清理临时文件 + os.unlink(temp_bat) + + # 更新当前进程的环境变量 + os.environ.update(self.env_vars) + + return True + + except Exception as e: + print(f"设置Intel环境失败: {e}") + return False + + def get_compiler_info(self): + """获取编译器信息""" + info = {} + + # 检查ifx编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + env={**os.environ, **self.env_vars} if self.env_vars else os.environ + ) + + if result.returncode == 0: + for line in result.stdout.split('\n'): + if 'Version' in line or '版本' in line: + info['ifx_version'] = line.strip() + break + except: + pass + + # 检查环境变量 + info['ifx_root'] = self.env_vars.get('IFX_ROOT', '') + info['compiler_root'] = self.env_vars.get('ONEAPI_ROOT', '') + + return info + +class BuildSystem: + """构建系统主类""" + + def __init__(self): + self.project_root = Path(__file__).parent.parent + self.build_dir = self.project_root / "build" + self.intel_env = IntelEnvironment() + + # 设置控制台编码 + if sys.platform == "win32": + try: + import ctypes + # 设置控制台输出为UTF-8 + ctypes.windll.kernel32.SetConsoleOutputCP(65001) + except: + pass + + def print_header(self, text): + """打印标题""" + print(f"\n{'='*70}") + print(f" {text}") + print(f"{'='*70}\n") + + def print_step(self, step, total, message): + """打印步骤""" + print(f"[{step}/{total}] {message}...") + + def print_success(self, message): + """打印成功""" + print(f"\033[92m✓ {message}\033[0m") + + def print_error(self, message): + """打印错误""" + print(f"\033[91m✗ {message}\033[0m") + + def print_warning(self, message): + """打印警告""" + print(f"\033[93m! {message}\033[0m") + + def print_info(self, message): + """打印信息""" + print(f"\033[94mℹ {message}\033[0m") + + def check_prerequisites(self): + """检查前提条件""" + self.print_step(1, 6, "检查前提条件") + + # 检查Python版本 + python_version = sys.version.split()[0] + self.print_info(f"Python版本: {python_version}") + + # 检查平台 + self.print_info(f"平台: {platform.system()} {platform.release()}") + self.print_info(f"处理器核心数: {os.cpu_count()}") + + # 检查CMake + try: + result = subprocess.run( + ["cmake", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + version_line = result.stdout.split('\n')[0] + self.print_success(f"CMake: {version_line}") + else: + self.print_error("CMake未找到") + return False + except FileNotFoundError: + self.print_error("CMake未安装") + return False + + return True + + def setup_intel_environment(self, args): + """设置Intel环境""" + self.print_step(2, 6, "配置Intel oneAPI环境") + + if not self.intel_env.find_setvars(): + self.print_warning("未找到Intel oneAPI setvars.bat") + self.print_info("将尝试使用系统环境中的编译器") + + # 检查是否能直接访问编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + self.print_success("Intel编译器在系统PATH中找到") + return True + else: + self.print_warning("Intel编译器未在PATH中找到") + except: + self.print_warning("无法访问Intel编译器") + + return True # 继续,让CMake自己找编译器 + + # 设置环境 + if self.intel_env.setup_environment(): + compiler_info = self.intel_env.get_compiler_info() + + if compiler_info.get('ifx_version'): + self.print_success(f"Intel Fortran编译器: {compiler_info['ifx_version']}") + elif compiler_info.get('ifx_root'): + self.print_success(f"Intel编译器路径: {compiler_info['ifx_root']}") + else: + self.print_success("Intel oneAPI环境配置完成") + + return True + else: + self.print_warning("Intel环境配置失败,将继续使用系统环境") + return True + + def clean_build_directory(self, args): + """清理构建目录""" + if args.clean and self.build_dir.exists(): + self.print_info("清理构建目录...") + try: + shutil.rmtree(self.build_dir) + self.print_success("构建目录已清理") + except Exception as e: + self.print_error(f"清理失败: {e}") + if not args.force: + return False + return True + + def run_command(self, cmd, cwd=None, check=True, env=None): + """运行命令""" + if isinstance(cmd, list): + cmd_str = ' '.join(str(c) for c in cmd if c) + else: + cmd_str = str(cmd) + + print(f" \033[96m$\033[0m {cmd_str}") + + try: + # 合并环境变量 + exec_env = os.environ.copy() + if env: + exec_env.update(env) + if self.intel_env.env_vars: + exec_env.update(self.intel_env.env_vars) + + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=False, + env=exec_env + ) + + # 处理输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or '完成' in line or '生成' in line: + print(f" \033[92m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + if check and result.returncode != 0: + self.print_error(f"命令执行失败,退出码: {result.returncode}") + return False + + return True + + except Exception as e: + self.print_error(f"命令执行异常: {e}") + return False + + def configure_cmake(self, args): + """配置CMake""" + self.print_step(3, 6, "配置CMake项目") + + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + f"-DCMAKE_BUILD_TYPE={args.build_type}", + ] + + if args.compiler == "ifx": + cmake_cmd.extend(["-T", "fortran=ifx"]) + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + success = self.run_command(cmake_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("CMake配置完成") + else: + self.print_error("CMake配置失败") + + return success + + def build_project(self, args): + """构建项目""" + self.print_step(4, 6, "构建项目") + + build_cmd = [ + "cmake", + "--build", ".", + "--config", args.build_type, + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + success = self.run_command(build_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("项目构建完成") + else: + self.print_error("构建失败") + + return success + + def run_tests_with_environment(self, test_exe): + """运行单个测试,确保有Intel环境""" + try: + # 创建临时的批处理文件来运行测试 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'"{test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'"{test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + return result + + except Exception as e: + print(f"运行测试失败: {e}") + return None + + def run_tests(self, args): + """运行测试""" + self.print_step(5, 6, "运行测试") + + # 查找测试可执行文件 + test_dir = self.build_dir / "bin" / args.build_type + if not test_dir.exists(): + test_dir = self.build_dir / "bin" + if not test_dir.exists(): + test_dir = self.build_dir + + test_files = list(test_dir.glob("test_*.exe")) + + if not test_files: + self.print_warning("未找到测试程序") + return True + + all_passed = True + + for test_exe in sorted(test_files): + test_name = test_exe.stem + self.print_info(f"运行测试: {test_name}") + print(f" {'-'*50}") + + # 运行测试 + result = self.run_tests_with_environment(str(test_exe)) + + if result is None: + self.print_error(f" {test_name} 运行失败") + all_passed = False + continue + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + print(f" {line}") + + if result.returncode == 0: + self.print_success(f" {test_name} 通过") + else: + self.print_error(f" {test_name} 失败 (退出码: {result.returncode})") + all_passed = False + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + print() # 空行 + + return all_passed + + def create_test_runner(self, args): + """创建独立的测试运行器""" + self.print_step(6, 6, "创建测试运行器") + + runner_path = self.build_dir / "run_tests.bat" + + content = f'''@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Test Runner +echo ======================================== +echo. + +REM Setup Intel oneAPI environment +set "SETVARS_PATH={self.intel_env.setvars_path or ''}" +if exist "%SETVARS_PATH%" ( + call "%SETVARS_PATH%" >nul + echo [INFO] Intel environment configured +) else ( + echo [WARNING] Intel environment not found + echo [WARNING] Tests may fail without runtime libraries +) + +echo. + +REM Run all test executables +set "TEST_COUNT=0" +set "PASS_COUNT=0" + +for %%f in ("bin\\{args.build_type}\\test_*.exe") do ( + set /a TEST_COUNT+=1 + echo [TEST %%f] + echo {'-'*50} + + %%f + if errorlevel 1 ( + echo [FAILED] %%f + ) else ( + echo [PASSED] %%f + set /a PASS_COUNT+=1 + ) + echo. +) + +echo ======================================== +echo Tests: %PASS_COUNT%/%TEST_COUNT% passed +if %PASS_COUNT% equ %TEST_COUNT% ( + echo [SUCCESS] All tests passed! +) else ( + echo [FAILURE] Some tests failed +) +echo ======================================== + +pause +''' + + with open(runner_path, 'w', encoding='utf-8') as f: + f.write(content) + + self.print_success(f"测试运行器已创建: {runner_path}") + self.print_info(f"使用方法: cd build && run_tests.bat") + + return runner_path + + def generate_report(self, args, build_time, tests_passed): + """生成构建报告""" + self.print_header("构建完成") + + print(f"项目: {self.project_root.name}") + print(f"构建类型: {args.build_type}") + print(f"编译器: {args.compiler}") + print(f"并行作业: {args.jobs}") + print(f"总耗时: {build_time:.1f}秒") + print(f"测试结果: {'全部通过' if tests_passed else '有失败'}") + + # 显示生成的可执行文件 + bin_dir = self.build_dir / "bin" / args.build_type + if bin_dir.exists(): + print(f"\n生成的可执行文件:") + for exe in sorted(bin_dir.glob("*.exe")): + size_mb = exe.stat().st_size / (1024 * 1024) + print(f" • {exe.name} ({size_mb:.2f} MB)") + + # 显示测试运行器信息 + runner_path = self.build_dir / "run_tests.bat" + if runner_path.exists(): + print(f"\n独立测试运行器:") + print(f" • {runner_path.name}") + print(f" 在Intel oneAPI环境中运行所有测试") + + def run(self): + """运行构建系统""" + parser = argparse.ArgumentParser( + description="Fortran CFD项目构建工具", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认构建 + %(prog)s --clean # 清理后构建 + %(prog)s --build-type Release # Release构建 + %(prog)s --no-tests # 只构建,不运行测试 + %(prog)s -j8 --verbose # 8线程并行构建,详细输出 + """ + ) + + parser.add_argument("--build-type", choices=["Debug", "Release"], + default="Debug", help="构建类型") + parser.add_argument("--compiler", choices=["ifx", "ifort"], + default="ifx", help="Fortran编译器") + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-tests", action="store_true", + help="跳过测试") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + + args = parser.parse_args() + + # 开始构建 + start_time = time.time() + + self.print_header("Fortran CFD 项目构建系统") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 检查前提条件 + if not self.check_prerequisites(): + if not args.force: + return 1 + + # 2. 设置Intel环境 + if not self.setup_intel_environment(args): + if not args.force: + return 1 + + # 3. 清理目录 + if not self.clean_build_directory(args): + if not args.force: + return 1 + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 4. 配置CMake + if not self.configure_cmake(args): + if not args.force: + return 1 + + # 5. 构建项目 + if not self.build_project(args): + if not args.force: + return 1 + + # 6. 运行测试和创建测试运行器 + tests_passed = True + if not args.no_tests: + tests_passed = self.run_tests(args) + + # 创建测试运行器 + self.create_test_runner(args) # 传递 args 参数 + + # 7. 生成报告 + build_time = time.time() - start_time + self.generate_report(args, build_time, tests_passed) + + return 0 if tests_passed else 1 + + except KeyboardInterrupt: + self.print_error("\n构建被用户中断") + return 1 + except Exception as e: + self.print_error(f"构建过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + builder = BuildSystem() + return builder.run() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02b/scripts/requirements.txt b/example/1d-linear-convection/weno3/fortran/registry/02b/scripts/requirements.txt new file mode 100644 index 000000000..d21a12b45 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02b/scripts/requirements.txt @@ -0,0 +1,2 @@ +# 构建脚本的Python依赖(可选) +# 目前没有特殊依赖,保持空文件或添加未来可能需要的包 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02b/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02b/src/CMakeLists.txt new file mode 100644 index 000000000..f958422d2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02b/src/CMakeLists.txt @@ -0,0 +1,15 @@ +# ==================== src\CMakeLists.txt ==================== + +# src/CMakeLists.txt +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(core) +add_subdirectory(infrastructure) +add_subdirectory(numerics) +add_subdirectory(manager) + +message(STATUS "源代码目录配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02b/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02b/src/core/CMakeLists.txt new file mode 100644 index 000000000..3dbf384cb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02b/src/core/CMakeLists.txt @@ -0,0 +1,25 @@ +# src/core/CMakeLists.txt +message(STATUS "配置核心模块...") + +add_library(core STATIC + registry.f90 + factory_interfaces.f90 + #component_manager.f90 # ← 添加这一行 +) + +target_include_directories(core PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 安装规则 +install(TARGETS core + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) +install(FILES + ${CMAKE_Fortran_MODULE_DIRECTORY}/registry_module.mod + ${CMAKE_Fortran_MODULE_DIRECTORY}/factory_interfaces.mod + DESTINATION include/fortran_cfd/core +) + +message(STATUS "核心模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02b/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/02b/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..a960167c3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02b/src/core/factory_interfaces.f90 @@ -0,0 +1,17 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, factory_procedure + + ! Factory procedure interface + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02b/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/02b/src/core/registry.f90 new file mode 100644 index 000000000..d620f5ec9 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02b/src/core/registry.f90 @@ -0,0 +1,656 @@ +! src/core/registry.f90 +module registry_module + use, intrinsic :: iso_fortran_env, only: real64 + use factory_interfaces, only: factory_procedure + implicit none + + private + + ! ==================== PUBLIC INTERFACE ==================== + ! 原有接口保持不变 + public :: real64, component_info, component_registry + public :: register_component_simple, initialize_registry, cleanup_registry + public :: has_component, get_available_components + public :: registry_is_initialized, registry_get_size + + ! 新增工厂相关接口 + public :: factory_ptr_type, component_registry_entry + public :: register_component_with_factory + public :: create_component_from_registry + public :: has_factory_component + public :: list_factory_components + + ! ==================== TYPE DEFINITIONS ==================== + + ! 原有类型定义 + type :: component_info + character(len=32) :: category = "" + character(len=32) :: name = "" + integer :: order = 0 + contains + procedure :: print => ci_print + end type component_info + + ! 新增:工厂指针类型 + type :: factory_ptr_type + procedure(factory_procedure), pointer, nopass :: ptr => null() + end type factory_ptr_type + + ! 新增:带工厂的注册条目 + type :: component_registry_entry + character(len=32) :: category = "" + character(len=32) :: name = "" + integer :: order = 0 + type(factory_ptr_type) :: factory + logical :: has_factory = .false. + contains + procedure :: print_with_factory => entry_print + procedure :: can_create => entry_has_factory + end type component_registry_entry + + ! 原有注册表类型(扩展以支持工厂) + type :: component_registry_type + private + ! 简单注册组件(向后兼容) + type(component_info), allocatable :: simple_components(:) + integer :: simple_count = 0 + + ! 带工厂的组件(新增) + type(component_registry_entry), allocatable :: factory_components(:) + integer :: factory_count = 0 + + integer :: capacity = 100 + logical :: verbose = .true. + logical :: initialized = .false. + contains + ! 简单注册方法 + procedure :: register => cr_register ! ← 保持原名 + procedure :: get => cr_get ! ← 保持原名 + procedure :: list_simple => cr_list_simple ! ← 新增别名 + + ! 工厂注册方法 + procedure :: register_with_factory => cr_register_with_factory + procedure :: get_with_factory => cr_get_with_factory + procedure :: list_with_factory => cr_list_with_factory + procedure :: create_from_factory => cr_create_from_factory + + ! 通用方法 + procedure :: clear => cr_clear + procedure :: size => cr_size + procedure :: total_size => cr_total_size + procedure :: is_initialized => cr_is_initialized + end type component_registry_type + + ! Global registry instance + type(component_registry_type), save :: component_registry + +contains + + ! ==================== PUBLIC API ==================== + + ! Initialize registry (保持不变) + subroutine initialize_registry(initial_capacity, verbose) + integer, optional, intent(in) :: initial_capacity + logical, optional, intent(in) :: verbose + + if (component_registry%initialized) then + if (component_registry%verbose) then + print *, "[INFO] Registry already initialized" + end if + return + end if + + if (present(initial_capacity)) then + component_registry%capacity = max(10, initial_capacity) + end if + + if (present(verbose)) then + component_registry%verbose = verbose + end if + + ! Allocate arrays for both types + allocate(component_registry%simple_components(component_registry%capacity)) + allocate(component_registry%factory_components(component_registry%capacity)) + + component_registry%initialized = .true. + component_registry%simple_count = 0 + component_registry%factory_count = 0 + + if (component_registry%verbose) then + print *, "[INIT] Registry initialized, capacity:", component_registry%capacity + print *, " Supports both simple and factory-based registration" + end if + end subroutine initialize_registry + + ! Cleanup registry (保持不变) + subroutine cleanup_registry + call component_registry%clear() + if (component_registry%verbose) then + print *, "[CLEANUP] Registry cleaned up" + end if + end subroutine cleanup_registry + + ! ==================== SIMPLE REGISTRATION (向后兼容) ==================== + + ! Simple registration (no factory) - 保持不变 + subroutine register_component_simple(category, name) + character(len=*), intent(in) :: category, name + + type(component_info) :: info + + info%category = to_lower(trim(adjustl(category))) + info%name = to_lower(trim(adjustl(name))) + info%order = 0 + + call component_registry%register(info) + end subroutine register_component_simple + + ! Check if component exists (简单注册) + function has_component(category, name) result(found) + character(len=*), intent(in) :: category, name + logical :: found + + type(component_info) :: info + character(len=32) :: cat_lower, name_lower + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + info = component_registry%get(cat_lower, name_lower) + found = (len_trim(info%category) > 0) + end function has_component + + ! Get available components in a category (简单注册) + subroutine get_available_components(category, names, orders) + character(len=*), intent(in) :: category + character(len=:), allocatable, intent(out), optional :: names(:) + integer, allocatable, intent(out), optional :: orders(:) + + character(len=32) :: cat_lower + integer :: i, count, idx + type(component_info) :: info + + cat_lower = to_lower(trim(adjustl(category))) + + ! Count components in this category + count = 0 + do i = 1, component_registry%simple_count + if (component_registry%simple_components(i)%category == cat_lower) then + count = count + 1 + end if + end do + + ! Allocate arrays if requested + if (present(names)) then + allocate(character(len=32) :: names(count)) + end if + + if (present(orders)) then + allocate(orders(count)) + end if + + ! Fill arrays + idx = 1 + do i = 1, component_registry%simple_count + if (component_registry%simple_components(i)%category == cat_lower) then + info = component_registry%simple_components(i) + if (present(names)) then + names(idx) = info%name + end if + if (present(orders)) then + orders(idx) = info%order + end if + idx = idx + 1 + end if + end do + end subroutine get_available_components + + ! ==================== FACTORY REGISTRATION (新增) ==================== + + ! Register component with factory function + subroutine register_component_with_factory(category, name, factory_func, order) + character(len=*), intent(in) :: category, name + procedure(factory_procedure) :: factory_func + integer, optional, intent(in) :: order + + type(component_registry_entry) :: entry + character(len=32) :: cat_lower, name_lower + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + entry%category = cat_lower + entry%name = name_lower + + if (present(order)) then + entry%order = order + else + entry%order = 0 + end if + + entry%factory%ptr => factory_func + entry%has_factory = .true. + + call component_registry%register_with_factory(entry) + end subroutine register_component_with_factory + + ! Create component from registry using factory + function create_component_from_registry(category, name) result(instance) + character(len=*), intent(in) :: category, name + class(*), allocatable :: instance + + character(len=32) :: cat_lower, name_lower + integer :: status + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + call component_registry%create_from_factory(cat_lower, name_lower, instance, status) + + if (status /= 0) then + if (component_registry%verbose) then + print *, "[ERROR] Failed to create component: ", trim(category), ".", trim(name) + end if + end if + end function create_component_from_registry + + ! Check if component has factory + function has_factory_component(category, name) result(found) + character(len=*), intent(in) :: category, name + logical :: found + + character(len=32) :: cat_lower, name_lower + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + found = .false. + + if (.not. component_registry%initialized) return + + ! 简化的查找逻辑(实际应调用内部方法) + found = component_registry%factory_count > 0 ! 占位实现 + end function has_factory_component + + ! List all factory components + subroutine list_factory_components(category) + character(len=*), optional, intent(in) :: category + + if (present(category)) then + call component_registry%list_with_factory(category) + else + call component_registry%list_with_factory("") + end if + end subroutine list_factory_components + + ! ==================== PUBLIC UTILITY FUNCTIONS ==================== + + ! Public function to check if registry is initialized + function registry_is_initialized() result(is_initialized) + logical :: is_initialized + is_initialized = component_registry%is_initialized() + end function registry_is_initialized + + ! Public function to get total registry size + function registry_get_size() result(size_val) + integer :: size_val + size_val = component_registry%total_size() + end function registry_get_size + + ! ==================== COMPONENT INFO METHODS ==================== + + subroutine ci_print(this) + class(component_info), intent(in) :: this + + if (this%order > 0) then + print *, " [", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ")]" + else + print *, " [", trim(this%category), ".", trim(this%name), "]" + end if + end subroutine ci_print + + subroutine entry_print(this) + class(component_registry_entry), intent(in) :: this + + if (this%has_factory) then + if (this%order > 0) then + print *, " [FACTORY] ", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ")" + else + print *, " [FACTORY] ", trim(this%category), ".", trim(this%name) + end if + else + if (this%order > 0) then + print *, " [SIMPLE] ", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ")" + else + print *, " [SIMPLE] ", trim(this%category), ".", trim(this%name) + end if + end if + end subroutine entry_print + + logical function entry_has_factory(this) + class(component_registry_entry), intent(in) :: this + entry_has_factory = this%has_factory + end function entry_has_factory + + ! ==================== REGISTRY INTERNAL METHODS ==================== + + ! ---------- Simple Registration Methods ---------- + + subroutine cr_register(this, info) + class(component_registry_type), intent(inout) :: this + type(component_info), intent(in) :: info + + type(component_info), allocatable :: temp(:) + integer :: i + + if (.not. this%initialized) then + error stop "[ERROR] Registry not initialized, call initialize_registry first" + end if + + ! Check if already exists + do i = 1, this%simple_count + if (this%simple_components(i)%category == info%category .and. & + this%simple_components(i)%name == info%name) then + if (this%verbose) then + print *, "[WARN] Overwriting simple component: ", & + trim(info%category), ".", trim(info%name) + end if + this%simple_components(i) = info + return + end if + end do + + ! Expand array if needed + if (this%simple_count >= this%capacity) then + this%capacity = this%capacity * 2 + allocate(temp(this%capacity)) + temp(1:this%simple_count) = this%simple_components(1:this%simple_count) + call move_alloc(temp, this%simple_components) + + if (this%verbose) then + print *, "[INFO] Simple registry expanded to capacity:", this%capacity + end if + end if + + ! Add component + this%simple_count = this%simple_count + 1 + this%simple_components(this%simple_count) = info + + if (this%verbose) then + print *, "[OK] Registered simple: ", trim(info%category), ".", trim(info%name) + end if + end subroutine cr_register + + function cr_get(this, category, name) result(info) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category, name + type(component_info) :: info + + integer :: i + + ! Initialize return value as empty + info%category = "" + info%name = "" + info%order = 0 + + if (.not. this%initialized) then + return + end if + + do i = 1, this%simple_count + if (this%simple_components(i)%category == category .and. & + this%simple_components(i)%name == name) then + info = this%simple_components(i) + return + end if + end do + end function cr_get + + subroutine cr_list_simple(this) + class(component_registry_type), intent(in) :: this + integer :: i + + if (.not. this%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + if (this%simple_count == 0) then + print *, "[INFO] No simple components registered" + return + end if + + print *, "=== Simple Registry Contents (", this%simple_count, " components) ===" + do i = 1, this%simple_count + call this%simple_components(i)%print() + end do + print *, "==========================================" + end subroutine cr_list_simple + + ! ---------- Factory Registration Methods ---------- + + subroutine cr_register_with_factory(this, entry) + class(component_registry_type), intent(inout) :: this + type(component_registry_entry), intent(in) :: entry + + type(component_registry_entry), allocatable :: temp(:) + integer :: i + + if (.not. this%initialized) then + error stop "[ERROR] Registry not initialized, call initialize_registry first" + end if + + ! Check if already exists + do i = 1, this%factory_count + if (this%factory_components(i)%category == entry%category .and. & + this%factory_components(i)%name == entry%name) then + if (this%verbose) then + print *, "[WARN] Overwriting factory component: ", & + trim(entry%category), ".", trim(entry%name) + end if + this%factory_components(i) = entry + return + end if + end do + + ! Expand array if needed + if (this%factory_count >= this%capacity) then + this%capacity = this%capacity * 2 + allocate(temp(this%capacity)) + temp(1:this%factory_count) = this%factory_components(1:this%factory_count) + call move_alloc(temp, this%factory_components) + + if (this%verbose) then + print *, "[INFO] Factory registry expanded to capacity:", this%capacity + end if + end if + + ! Add component + this%factory_count = this%factory_count + 1 + this%factory_components(this%factory_count) = entry + + if (this%verbose) then + print *, "[FACTORY] Registered with factory: ", & + trim(entry%category), ".", trim(entry%name) + end if + end subroutine cr_register_with_factory + + function cr_get_with_factory(this, category, name) result(entry) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category, name + type(component_registry_entry) :: entry + + integer :: i + + ! Initialize return value as empty + entry%category = "" + entry%name = "" + entry%order = 0 + entry%factory%ptr => null() + entry%has_factory = .false. + + if (.not. this%initialized) then + return + end if + + do i = 1, this%factory_count + if (this%factory_components(i)%category == category .and. & + this%factory_components(i)%name == name) then + entry = this%factory_components(i) + return + end if + end do + end function cr_get_with_factory + + subroutine cr_list_with_factory(this, category) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category + + character(len=32) :: cat_lower + integer :: i, count + + if (.not. this%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + cat_lower = to_lower(trim(adjustl(category))) + + if (this%factory_count == 0) then + print *, "[INFO] No factory components registered" + return + end if + + ! Count components in this category + count = 0 + do i = 1, this%factory_count + if (len_trim(cat_lower) == 0 .or. & + this%factory_components(i)%category == cat_lower) then + count = count + 1 + end if + end do + + if (count == 0) then + if (len_trim(cat_lower) > 0) then + print *, "[INFO] No factory components in category: ", trim(cat_lower) + else + print *, "[INFO] No factory components registered" + end if + return + end if + + print *, "=== Factory Registry Contents (", count, " components) ===" + do i = 1, this%factory_count + if (len_trim(cat_lower) == 0 .or. & + this%factory_components(i)%category == cat_lower) then + call this%factory_components(i)%print_with_factory() + end if + end do + print *, "==========================================" + end subroutine cr_list_with_factory + + subroutine cr_create_from_factory(this, category, name, instance, status) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category, name + class(*), allocatable, intent(out) :: instance + integer, intent(out) :: status + + type(component_registry_entry) :: entry + integer :: i + + status = -1 ! Default: not found + + if (.not. this%initialized) then + if (this%verbose) then + print *, "[ERROR] Registry not initialized" + end if + return + end if + + ! Find the entry + do i = 1, this%factory_count + if (this%factory_components(i)%category == category .and. & + this%factory_components(i)%name == name) then + entry = this%factory_components(i) + + if (entry%has_factory .and. associated(entry%factory%ptr)) then + ! Call the factory function + call entry%factory%ptr(instance) + status = 0 ! Success + + if (this%verbose) then + print *, "[FACTORY] Created component: ", & + trim(category), ".", trim(name) + end if + else + status = -2 ! No factory function + if (this%verbose) then + print *, "[ERROR] No factory function for: ", & + trim(category), ".", trim(name) + end if + end if + return + end if + end do + + ! If we get here, component not found + if (this%verbose) then + print *, "[ERROR] Factory component not found: ", & + trim(category), ".", trim(name) + end if + end subroutine cr_create_from_factory + + ! ---------- Common Methods ---------- + + subroutine cr_clear(this) + class(component_registry_type), intent(inout) :: this + + if (allocated(this%simple_components)) then + deallocate(this%simple_components) + end if + + if (allocated(this%factory_components)) then + deallocate(this%factory_components) + end if + + this%simple_count = 0 + this%factory_count = 0 + this%capacity = 100 + this%initialized = .false. + end subroutine cr_clear + + integer function cr_total_size(this) + class(component_registry_type), intent(in) :: this + cr_total_size = this%simple_count + this%factory_count + end function cr_total_size + + integer function cr_size(this) + class(component_registry_type), intent(in) :: this + cr_size = this%simple_count ! Backward compatibility + end function cr_size + + logical function cr_is_initialized(this) + class(component_registry_type), intent(in) :: this + cr_is_initialized = this%initialized + end function cr_is_initialized + + ! ==================== UTILITY FUNCTIONS ==================== + + function to_lower(str) result(lower_str) + character(len=*), intent(in) :: str + character(len=len(str)) :: lower_str + integer :: i + + do i = 1, len(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + lower_str(i:i) = char(ichar(str(i:i)) + 32) + else + lower_str(i:i) = str(i:i) + end if + end do + end function to_lower + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02b/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02b/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..46964ce79 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02b/src/infrastructure/CMakeLists.txt @@ -0,0 +1,20 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "配置基础设施模块...") + +add_library(infrastructure STATIC + config.f90 + mesh.f90 +) + +target_link_libraries(infrastructure PRIVATE core) + +target_include_directories(infrastructure PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS infrastructure + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "基础设施模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02b/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/02b/src/infrastructure/config.f90 new file mode 100644 index 000000000..d3b1e0dfb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02b/src/infrastructure/config.f90 @@ -0,0 +1,90 @@ +! src/infrastructure/config.f90 +module config_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, cfd_config, config_print, config_with_reconstruction + + ! CFD configuration type + type :: cfd_config + character(len=20) :: ic_type = "step" + character(len=20) :: recon_scheme = "eno" + character(len=20) :: flux_type = "rusanov" + integer :: rk_order = 1 + real(real64) :: wave_speed = 1.0_real64 + real(real64) :: final_time = 0.625_real64 + real(real64) :: dt = 0.025_real64 + character(len=20) :: boundary_type = "periodic" + real(real64) :: left_boundary_value = 1.0_real64 + real(real64) :: right_boundary_value = 2.0_real64 + integer :: spatial_order = 2 + logical :: verbose = .true. + end type cfd_config + +contains + + subroutine config_print(this) + type(cfd_config), intent(in) :: this + + print *, "=== CFD Configuration ===" + print *, "Initial condition: ", trim(this%ic_type) + print *, "Reconstruction: ", trim(this%recon_scheme), " (order:", this%spatial_order, ")" + print *, "Flux type: ", trim(this%flux_type) + print *, "Time integration: RK", this%rk_order + print *, "Wave speed: ", this%wave_speed + print *, "Final time: ", this%final_time + print *, "Time step: ", this%dt + print *, "Boundary: ", trim(this%boundary_type) + if (trim(this%boundary_type) == 'dirichlet') then + print *, " Dirichlet values: [", this%left_boundary_value, ", ", & + this%right_boundary_value, "]" + end if + print *, "===============================" + end subroutine config_print + + subroutine config_with_reconstruction(this, scheme, order) + type(cfd_config), intent(inout) :: this + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + character(len=20) :: scheme_lower + + ! Convert to lowercase + scheme_lower = scheme + call to_lower_inplace(scheme_lower) + this%recon_scheme = trim(adjustl(scheme_lower)) + + ! Set order + if (present(order)) then + this%spatial_order = order + else + ! Smart defaults + if (index(this%recon_scheme, 'weno') > 0) then + this%spatial_order = 5 + else if (trim(this%recon_scheme) == 'eno') then + this%spatial_order = 3 + else + print *, "[ERROR] Unsupported reconstruction scheme: ", trim(this%recon_scheme) + return + end if + end if + + if (this%verbose) then + print *, "[CONFIG] Reconstruction: ", trim(this%recon_scheme), & + " Order: ", this%spatial_order + end if + end subroutine config_with_reconstruction + + subroutine to_lower_inplace(str) + character(len=*), intent(inout) :: str + integer :: i + + do i = 1, len_trim(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + str(i:i) = char(ichar(str(i:i)) + 32) + end if + end do + end subroutine to_lower_inplace + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02b/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/02b/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..896969bd0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02b/src/infrastructure/mesh.f90 @@ -0,0 +1,74 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, mesh_type, mesh_init, mesh_print_info + + ! mesh + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer :: ncells = 40 + integer :: nnodes + integer :: nx + real(wp), allocatable :: x(:) ! + real(wp), allocatable :: xcc(:) ! + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer, optional, intent(in) :: ncells + + integer :: i + + ! Set + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! computation + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! node + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! cell + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== ===" + print *, ": [", this%xmin, ", ", this%xmax, "]" + print *, ": ", this%ncells + print *, ": ", this%nnodes + print *, " dx: ", this%dx + print *, " L: ", this%L + print *, "==========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02b/src/manager/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02b/src/manager/CMakeLists.txt new file mode 100644 index 000000000..00c8bf49b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02b/src/manager/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/manager/CMakeLists.txt +message(STATUS "配置管理器模块...") + +# 创建管理器库 +add_library(manager STATIC + component_manager.f90 + component_factory.f90 +) + +# 明确依赖关系:管理器依赖所有其他模块 +target_link_libraries(manager + PRIVATE + core + infrastructure + reconstructor + flux +) + +# 设置模块输出目录 +set_target_properties(manager PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "管理器模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02b/src/manager/component_factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/02b/src/manager/component_factory.f90 new file mode 100644 index 000000000..114fedea5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02b/src/manager/component_factory.f90 @@ -0,0 +1,127 @@ +! src/manager/component_factory.f90 +module component_factory_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use config_module, only: cfd_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + use eno_reconstructor_module, only: eno_reconstructor, create_eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor, create_weno3_reconstructor + use rusanov_flux_module, only: rusanov_flux, create_rusanov_flux + + implicit none + private + public :: wp, create_reconstructor, create_flux_calculator + + ! 错误代码 + integer, parameter :: CM_SUCCESS = 0 + integer, parameter :: CM_ERROR_UNKNOWN_SCHEME = 1 + integer, parameter :: CM_ERROR_UNKNOWN_FLUX = 2 + integer, parameter :: CM_ERROR_INVALID_ORDER = 3 + +contains + + ! ==================== 重构器创建 ==================== + + function create_reconstructor(config, status) result(recon) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(reconstructor_base), allocatable :: recon + + character(len=20) :: scheme + integer :: order, error_code + + scheme = trim(adjustl(config%recon_scheme)) + order = config%spatial_order + + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating reconstructor: ", scheme, " order=", order + end if + + select case(scheme) + case('eno') + allocate(eno_reconstructor :: recon) + select type(recon) + type is(eno_reconstructor) + recon = create_eno_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case('weno3') + allocate(weno3_reconstructor :: recon) + select type(recon) + type is(weno3_reconstructor) + recon = create_weno3_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case default + error_code = CM_ERROR_UNKNOWN_SCHEME + if (config%verbose) then + print *, "[ERROR] Unknown reconstructor scheme: ", scheme + print *, " Available: eno, weno3" + end if + end select + + ! 检查阶数有效性 + if (error_code == CM_SUCCESS) then + if (order < 1) then + error_code = CM_ERROR_INVALID_ORDER + if (config%verbose) then + print *, "[ERROR] Invalid spatial order: ", order + end if + end if + end if + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Reconstructor creation failed" + end if + end function create_reconstructor + + ! ==================== 通量计算器创建 ==================== + + function create_flux_calculator(config, status) result(flux) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(flux_calculator_base), allocatable :: flux + + character(len=20) :: flux_type + integer :: error_code + + flux_type = trim(adjustl(config%flux_type)) + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating flux calculator: ", flux_type + end if + + select case(flux_type) + case('rusanov') + allocate(rusanov_flux :: flux) + select type(flux) + type is(rusanov_flux) + flux = create_rusanov_flux() + flux%wave_speed_default = config%wave_speed + end select + + case default + error_code = CM_ERROR_UNKNOWN_FLUX + if (config%verbose) then + print *, "[ERROR] Unknown flux type: ", flux_type + print *, " Available: rusanov" + end if + end select + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Flux calculator creation failed" + end if + end function create_flux_calculator + +end module component_factory_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02b/src/manager/component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/02b/src/manager/component_manager.f90 new file mode 100644 index 000000000..9e095c257 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02b/src/manager/component_manager.f90 @@ -0,0 +1,75 @@ +! src/manager/component_manager.f90 +module component_manager_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use config_module, only: cfd_config + use component_factory_module, only: create_reconstructor, create_flux_calculator + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + + implicit none + private + public :: wp, component_manager_info, validate_config + public :: create_reconstructor, create_flux_calculator + +contains + + ! ==================== 配置验证 ==================== + + function validate_config(config) result(is_valid) + type(cfd_config), intent(in) :: config + logical :: is_valid + + integer :: status + class(reconstructor_base), allocatable :: test_recon + class(flux_calculator_base), allocatable :: test_flux + + is_valid = .false. + + ! 测试创建重构器 + test_recon = create_reconstructor(config, status) + if (status /= 0) then + if (config%verbose) then + print *, "[CONFIG VALIDATION] Invalid reconstructor configuration" + end if + return + end if + + ! 测试创建通量计算器 + test_flux = create_flux_calculator(config, status) + if (status /= 0) then + if (config%verbose) then + print *, "[CONFIG VALIDATION] Invalid flux configuration" + end if + return + end if + + ! 清理测试组件 + if (allocated(test_recon)) deallocate(test_recon) + if (allocated(test_flux)) deallocate(test_flux) + + is_valid = .true. + + if (config%verbose) then + print *, "[CONFIG VALIDATION] Configuration is valid" + end if + end function validate_config + + ! ==================== 信息显示 ==================== + + subroutine component_manager_info() + print *, "=== Component Manager ===" + print *, "Available reconstructors:" + print *, " - eno (orders: 1-7)" + print *, " - weno3 (order: 3)" + print *, "" + print *, "Available flux calculators:" + print *, " - rusanov" + print *, "" + print *, "Features:" + print *, " - Configuration validation" + print *, " - Component creation from config" + print *, " - Error handling with status codes" + print *, "=========================" + end subroutine component_manager_info + +end module component_manager_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..66874a7ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/CMakeLists.txt @@ -0,0 +1,7 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..3fde7746b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,28 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +# Start with just the base module +add_library(flux STATIC + base.f90 + rusanov.f90 +) + +#target_link_libraries(flux PRIVATE core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 明确设置不使用 /Qm64 选项 +if(CMAKE_Fortran_COMPILER_ID MATCHES "IntelLLVM|Intel") + set_target_properties(flux PROPERTIES + Fortran_FLAGS "${CMAKE_Fortran_FLAGS} /Qm64" # 明确设置,避免继承问题 + ) +endif() + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/flux/base.f90 new file mode 100644 index 000000000..7080a7ae4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/flux/base.f90 @@ -0,0 +1,30 @@ +!src/numerics/flux/base.f90 +module flux_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, flux_calculator_base + + ! Base flux calculator type + type :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure :: info => flux_info + procedure :: print_basic_info => flux_print_basic ! 添加辅助方法 + end type flux_calculator_base + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "Flux calculator information:" + print *, " Name: ", trim(this%name) + end subroutine flux_info + + subroutine flux_print_basic(this) + class(flux_calculator_base), intent(in) :: this + print *, " Name: ", trim(this%name) + end subroutine flux_print_basic + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/flux/rusanov.f90 b/example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/flux/rusanov.f90 new file mode 100644 index 000000000..daa9e3bb5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/flux/rusanov.f90 @@ -0,0 +1,41 @@ +! src/numerics/flux/rusanov.f90 +module rusanov_flux_module + use, intrinsic :: iso_fortran_env, only: real64 + use flux_base_module, only: flux_calculator_base + implicit none + + private + public :: real64, rusanov_flux, create_rusanov_flux + + type, extends(flux_calculator_base) :: rusanov_flux + real(real64) :: wave_speed_default = 1.0_real64 + contains + procedure :: info => rusanov_info + end type rusanov_flux + + ! 构造函数接口 + interface rusanov_flux + module procedure create_rusanov_flux + end interface + +contains + + ! 构造函数 + type(rusanov_flux) function create_rusanov_flux() result(this) + this%name = "Rusanov" + this%wave_speed_default = 1.0_real64 + end function create_rusanov_flux + + subroutine rusanov_info(this) + class(rusanov_flux), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Flux calculator information:" + call this%print_basic_info() + + ! 添加Rusanov特有信息 + print *, " Type: Rusanov flux" + print *, " Default wave speed: ", this%wave_speed_default + end subroutine rusanov_info + +end module rusanov_flux_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..5e4b938d5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,22 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "Configuring reconstructor module...") + +# Start with just the base module +add_library(reconstructor STATIC + base.f90 + eno.f90 + weno3.f90 +) + +target_link_libraries(reconstructor PRIVATE core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Reconstructor module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..53798d021 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/reconstructor/base.f90 @@ -0,0 +1,33 @@ +!src/numerics/reconstructor/base.f90 +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, reconstructor_base + + ! Base reconstructor type + type :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + contains + procedure :: info => reconstructor_info + procedure :: print_basic_info => reconstructor_print_basic ! 添加一个辅助方法 + end type reconstructor_base + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + print *, "Reconstructor information:" + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_info + + subroutine reconstructor_print_basic(this) + class(reconstructor_base), intent(in) :: this + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_print_basic + +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/reconstructor/eno.f90 b/example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/reconstructor/eno.f90 new file mode 100644 index 000000000..f973e8b32 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/reconstructor/eno.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/eno.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, eno_reconstructor, create_eno_reconstructor ! ← 添加这个 + + type, extends(reconstructor_base) :: eno_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => eno_info + end type eno_reconstructor + + ! 构造函数接口 + interface eno_reconstructor + module procedure create_eno_reconstructor + end interface + +contains + + ! 构造函数 + type(eno_reconstructor) function create_eno_reconstructor() result(this) + this%name = "ENO" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_eno_reconstructor + + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加ENO特有信息 + print *, " Type: ENO reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/reconstructor/weno3.f90 b/example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/reconstructor/weno3.f90 new file mode 100644 index 000000000..d5b7a7477 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02b/src/numerics/reconstructor/weno3.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/weno3.f90 +module weno3_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno3_reconstructor, create_weno3_reconstructor + + type, extends(reconstructor_base) :: weno3_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno3_info + end type weno3_reconstructor + + ! 构造函数接口 + interface weno3_reconstructor + module procedure create_weno3_reconstructor + end interface + +contains + + ! 构造函数 + type(weno3_reconstructor) function create_weno3_reconstructor() result(this) + this%name = "WENO3" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_weno3_reconstructor + + subroutine weno3_info(this) + class(weno3_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加WENO3特有信息 + print *, " Type: WENO-3 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno3_info + +end module weno3_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02b/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02b/tests/CMakeLists.txt new file mode 100644 index 000000000..1008c1b53 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02b/tests/CMakeLists.txt @@ -0,0 +1,42 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +add_executable(test_minimal_simple test_minimal_simple.f90) + +message(STATUS "CMAKE_Fortran_MODULE_DIRECTORY=${CMAKE_Fortran_MODULE_DIRECTORY}") + +#target_include_directories( test_minimal_simple +# PRIVATE +# ${CMAKE_Fortran_MODULE_DIRECTORY} +#) + +target_link_libraries( test_minimal_simple + PRIVATE + infrastructure +) + +add_executable(test_simple_link test_simple_link.f90) +target_link_libraries(test_simple_link + PRIVATE + reconstructor + flux +) + + +add_executable(test_factory_simple test_factory_simple.f90) +target_link_libraries(test_factory_simple + PRIVATE + core + infrastructure + reconstructor + flux +) + +# 更新测试链接 +add_executable(test_component_manager test_component_manager.f90) + +target_link_libraries(test_component_manager + PRIVATE + manager # ← 链接到新的管理器库 + infrastructure +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02b/tests/test_component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/02b/tests/test_component_manager.f90 new file mode 100644 index 000000000..f60c35056 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02b/tests/test_component_manager.f90 @@ -0,0 +1,111 @@ +! tests/test_component_manager.f90 +program test_component_manager + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use component_manager_module, only: create_reconstructor, create_flux_calculator + use component_manager_module, only: component_manager_info, validate_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + implicit none + + type(cfd_config) :: config + class(reconstructor_base), allocatable :: recon + class(flux_calculator_base), allocatable :: flux + integer :: status + logical :: is_valid + + print *, "=== Component Manager Test ===" + print *, "" + + ! 显示组件管理器信息 + call component_manager_info() + print *, "" + + ! 测试1: 基本配置 + print *, "1. Testing basic ENO3 + Rusanov configuration..." + print *, "-----------------------------------------------" + + config%verbose = .true. + call config_print(config) + + ! 配置ENO3重构 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + call config_print(config) + print *, "" + + ! 验证配置 + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Configuration is valid" + else + print *, "[ERROR] Configuration is invalid" + end if + print *, "" + + ! 测试2: 创建组件 + print *, "2. Testing component creation..." + print *, "--------------------------------" + + ! 创建重构器(带状态检查) + recon = create_reconstructor(config, status) + if (status == 0) then + print *, "[OK] Reconstructor created successfully" + call recon%info() + else + print *, "[ERROR] Failed to create reconstructor, code:", status + end if + print *, "" + + ! 创建通量计算器 + flux = create_flux_calculator(config, status) + if (status == 0) then + print *, "[OK] Flux calculator created successfully" + call flux%info() + else + print *, "[ERROR] Failed to create flux calculator, code:", status + end if + print *, "" + + ! 测试3: WENO3重构测试 + print *, "3. Testing WENO3 configuration..." + print *, "---------------------------------" + + call config_with_reconstruction(config, "weno3", 3) + + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] WENO3 configuration is valid" + + ! 创建WENO3重构器 + recon = create_reconstructor(config) + call recon%info() + else + print *, "[ERROR] WENO3 configuration is invalid" + end if + print *, "" + + ! 测试4: 错误配置测试 + print *, "4. Testing invalid configuration..." + print *, "-----------------------------------" + + config%recon_scheme = "unknown_scheme" + config%flux_type = "unknown_flux" + + is_valid = validate_config(config) + if (.not. is_valid) then + print *, "[OK] Invalid configuration correctly rejected" + else + print *, "[ERROR] Invalid configuration should have been rejected" + end if + + ! 清理 + if (allocated(recon)) deallocate(recon) + if (allocated(flux)) deallocate(flux) + + print *, "" + print *, "=== Component manager test completed successfully ===" + +end program test_component_manager \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02b/tests/test_factory_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/02b/tests/test_factory_simple.f90 new file mode 100644 index 000000000..d4139bb1a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02b/tests/test_factory_simple.f90 @@ -0,0 +1,86 @@ +!tests/test_factory_simple.f90 +program test_factory_simple + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module, only: initialize_registry, cleanup_registry, & + register_component_simple, has_component + use config_module, only: cfd_config, config_print + use mesh_module, only: mesh_type + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(eno_reconstructor) :: eno + type(weno3_reconstructor) :: weno3 + type(rusanov_flux) :: rusanov + + print *, "=== Factory Pattern Simple Test ===" + print *, "" + + ! Test 1: Basic systems + print *, "1. Testing basic systems..." + print *, "-----------------------------" + call config_print(config) + + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 2: Creating reconstructors + print *, "2. Testing reconstructors..." + print *, "------------------------------" + + ! 创建并测试ENO重构器 + print *, "Creating ENO reconstructor..." + eno = eno_reconstructor() ! 使用构造函数 + call eno%info() ! 必须调用info方法 + + print *, "" + print *, "Creating WENO3 reconstructor..." + weno3 = weno3_reconstructor() ! 使用构造函数 + call weno3%info() ! 必须调用info方法 + print *, "" + + ! Test 3: Creating flux calculator + print *, "3. Testing flux calculator..." + print *, "-------------------------------" + + print *, "Creating Rusanov flux calculator..." + rusanov = rusanov_flux() ! 使用构造函数 + call rusanov%info() ! 必须调用info方法 + print *, "" + + ! Test 4: Registry integration + print *, "4. Testing registry..." + print *, "----------------------" + + call initialize_registry(verbose=.true.) + + ! Register components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("flux", "rusanov") + + ! Check registration + if (has_component("reconstructor", "eno")) then + print *, "[OK] ENO reconstructor registered successfully" + end if + + if (has_component("reconstructor", "weno3")) then + print *, "[OK] WENO3 reconstructor registered successfully" + end if + + if (has_component("flux", "rusanov")) then + print *, "[OK] Rusanov flux registered successfully" + end if + + print *, "" + call cleanup_registry() + + print *, "=== Factory pattern simple test completed successfully ===" + +end program test_factory_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02b/tests/test_minimal_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/02b/tests/test_minimal_simple.f90 new file mode 100644 index 000000000..cc6753b5f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02b/tests/test_minimal_simple.f90 @@ -0,0 +1,84 @@ +! tests/test_minimal_simple.f90 +program test_minimal_simple + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Simple Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_simple() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components (simple version) + print *, "5. Testing registry functionality" + print *, "---------------------------------" + print *, "Registry initialized: ", registry_is_initialized() + print *, "Number of components: ", registry_get_size() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Simple test completed successfully ===" + +end program test_minimal_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02b/tests/test_simple_link.f90 b/example/1d-linear-convection/weno3/fortran/registry/02b/tests/test_simple_link.f90 new file mode 100644 index 000000000..27b165b00 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02b/tests/test_simple_link.f90 @@ -0,0 +1,108 @@ +! tests/test_minimal.f90 +program test_minimal + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i ! Declare loop variable here + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_simple() + print *, "Registry size: ", component_registry%size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components + print *, "5. Testing available components" + print *, "--------------------------------" + + ! Use a separate block to avoid allocation issues + call test_available_components() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Minimal test completed successfully ===" + +contains + + ! Internal subroutine for testing available components + subroutine test_available_components() + character(len=:), allocatable :: names(:) + integer, allocatable :: orders(:) + integer :: j + + call get_available_components("reconstructor", names, orders) + + if (allocated(names)) then + print *, "Reconstructors:" + do j = 1, size(names) + print *, " - ", trim(names(j)) + if (allocated(orders)) then + print *, " Order: ", orders(j) + end if + end do + else + print *, "No reconstructors found" + end if + end subroutine test_available_components + +end program test_minimal \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02c/CMakeLists.txt new file mode 100644 index 000000000..ef66d5848 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/README.md b/example/1d-linear-convection/weno3/fortran/registry/02c/README.md new file mode 100644 index 000000000..c4889757b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/README.md @@ -0,0 +1,221 @@ +# Fortran CFD Project + +基于工厂模式和注册系统的CFD求解器框架。 + +## 项目结构 + +``` +fortran_cfd/ +├── CMakeLists.txt # 根CMake配置 +├── scripts/ # 构建脚本 +│ ├── build.py # Python构建脚本(推荐) +│ ├── build.bat # Batch包装脚本 +│ ├── build.ps1 # PowerShell包装脚本 +│ └── requirements.txt # Python依赖 +├── src/ # 源代码 +│ ├── CMakeLists.txt +│ ├── core/ # 核心模块(注册系统、工厂接口) +│ ├── infrastructure/ # 基础设施(配置、网格) +│ └── numerics/ # 数值方法 +├── tests/ # 测试 +│ ├── CMakeLists.txt +│ ├── test_minimal_simple.f90 # 简单测试 +│ └── test_factory.f90 # 工厂模式测试 +└── README.md # 项目说明(本文件) +``` + +## 快速开始 + +### 使用Python脚本(推荐) + +```bash +# 进入脚本目录 +cd scripts + +# 基本构建 +python build.py + +# 清理并构建 +python build.py --clean + +# 发布构建 +python build.py --build-type Release --clean + +# 并行构建(使用所有核心) +python build.py -j + +# 不运行测试 +python build.py --no-tests + +# 查看帮助 +python build.py --help +``` + +### 使用批处理脚本 + +```bash +cd scripts +.\build.bat +``` + +### 使用PowerShell脚本 + +```powershell +cd scripts +.\build.ps1 +``` + +## 手动构建 + +```bash +# 设置Intel环境 +cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && powershell' + +# 配置项目 +mkdir build +cd build +cmake -G "Visual Studio 17 2022" -A x64 -T fortran=ifx .. + +# 构建项目 +cmake --build . --config Debug + +# 运行测试 +Debug\test_simple.exe +Debug\test_factory.exe +``` + +## 功能特性 + +✅ 组件注册系统 +✅ 工厂模式 +✅ ENO/WENO重构器 +✅ Rusanov通量计算器 +✅ 可扩展架构 +✅ 自动化测试 + +## 依赖 + +- Intel oneAPI(包含ifx编译器) +- CMake 3.12+ +- Python 3.6+(仅用于构建脚本) + +## 源代码文件 + +### 核心模块 +- `src/core/registry.f90` - 组件注册系统 +- `src/core/factory_interfaces.f90` - 工厂接口 + +### 基础设施 +- `src/infrastructure/config.f90` - 配置管理 +- `src/infrastructure/mesh.f90` - 网格生成 + +### 数值方法 +- `src/numerics/reconstructor/base.f90` - 重构器基类 +- `src/numerics/reconstructor/eno.f90` - ENO重构器 +- `src/numerics/reconstructor/weno3.f90` - WENO-3重构器 +- `src/numerics/flux/base.f90` - 通量计算器基类 +- `src/numerics/flux/rusanov.f90` - Rusanov通量 + +### 测试 +- `tests/test_minimal_simple.f90` - 简单功能测试 +- `tests/test_factory.f90` - 工厂模式测试 + +## 构建脚本 + +### Python脚本 (build.py) +主构建脚本,支持: +- 自动设置Intel oneAPI环境 +- 参数化构建选项 +- 并行编译 +- 自动化测试 +- 彩色输出 + +### Batch脚本 (build.bat) +Windows批处理包装器,调用Python脚本。 + +### PowerShell脚本 (build.ps1) +PowerShell包装器,调用Python脚本。 + +## 示例输出 + +成功构建后,会看到类似输出: + +``` +============================================================ + Fortran CFD Project Builder +============================================================ + +[1/4] Setting up Intel Fortran compiler... +✓ Intel oneAPI environment configured + +[2/4] Preparing build directory... +✓ Cleaned build directory + +[3/4] Configuring project... + $ cmake -G Visual Studio 17 2022 -A x64 -T fortran=ifx -DCMAKE_BUILD_TYPE=Debug .. +✓ CMake configuration successful + +[4/4] Building project... + $ cmake --build . --config Debug +✓ Build completed in 12.3 seconds + +============================================================ + Running Tests +============================================================ + +[TEST] Simple functionality test... +✓ Simple test passed + +[TEST] Factory pattern test... +✓ Factory test passed + +============================================================ + Build Summary +============================================================ +✓ Build directory: D:\path\to\build +✓ Build type: Debug +✓ Total time: 15.2s +``` + +## 开发指南 + +### 添加新组件 + +1. 在相应模块中创建Fortran源文件 +2. 实现工厂函数 +3. 使用`register_component_with_factory`注册组件 +4. 添加测试 + +### 扩展架构 + +项目采用模块化设计: +1. **注册系统** - 管理所有可用的组件 +2. **工厂模式** - 动态创建组件实例 +3. **抽象接口** - 确保组件兼容性 +4. **配置驱动** - 通过配置文件控制行为 + +## 故障排除 + +### 常见问题 + +1. **Intel环境未找到** + - 检查Intel oneAPI安装路径 + - 手动运行`setvars.bat` + +2. **CMake配置失败** + - 确保使用正确的生成器:`Visual Studio 17 2022` + - 检查Fortran编译器是否可用 + +3. **构建失败** + - 检查依赖项是否完整 + - 查看详细的错误信息 + +### 调试建议 + +- 使用`--verbose`参数获取详细输出 +- 检查`build/CMakeCache.txt`了解配置详情 +- 查看编译器错误日志 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/scripts/build.bat b/example/1d-linear-convection/weno3/fortran/registry/02c/scripts/build.bat new file mode 100644 index 000000000..6fd6dc031 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/scripts/build.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Project Builder +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python build script with full Intel environment support... +echo. + +python build.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Build failed + pause + exit /b 1 +) + +echo. +echo [INFO] Build completed successfully! +echo. +echo [INFO] To run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/scripts/build.py b/example/1d-linear-convection/weno3/fortran/registry/02c/scripts/build.py new file mode 100644 index 000000000..3bf6d5375 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/scripts/build.py @@ -0,0 +1,629 @@ +#!/usr/bin/env python3 +""" +Fortran CFD Project Builder - 完整Python解决方案 +在Python内部处理Intel oneAPI环境配置 +""" + +import os +import sys +import subprocess +import shutil +import argparse +import time +import platform +import tempfile +from pathlib import Path + +class IntelEnvironment: + """Intel oneAPI环境管理器""" + + def __init__(self): + self.setvars_path = None + self.env_vars = {} + + def find_setvars(self): + """查找setvars.bat文件""" + possible_paths = [ + r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat", + r"C:\Program Files\Intel\oneAPI\setvars.bat", + r"C:\Program Files (x86)\Intel\oneAPI\compiler\latest\env\vars.bat", + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\setvars.bat"), + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\compiler\latest\env\vars.bat"), + ] + + for path in possible_paths: + if os.path.exists(path): + self.setvars_path = path + return True + + return False + + def setup_environment(self): + """设置Intel环境""" + if not self.find_setvars(): + return False + + try: + # 创建临时的批处理文件来捕获环境变量 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + f.write(f'@echo off\n') + f.write(f'call "{self.setvars_path}" >nul 2>&1\n') + f.write(f'set\n') # 输出所有环境变量 + temp_bat = f.name + + # 运行批处理文件并捕获输出 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + shell=True + ) + + # 解析环境变量 + for line in result.stdout.split('\n'): + line = line.strip() + if '=' in line: + key, value = line.split('=', 1) + self.env_vars[key.strip()] = value.strip() + + # 清理临时文件 + os.unlink(temp_bat) + + # 更新当前进程的环境变量 + os.environ.update(self.env_vars) + + return True + + except Exception as e: + print(f"设置Intel环境失败: {e}") + return False + + def get_compiler_info(self): + """获取编译器信息""" + info = {} + + # 检查ifx编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + env={**os.environ, **self.env_vars} if self.env_vars else os.environ + ) + + if result.returncode == 0: + for line in result.stdout.split('\n'): + if 'Version' in line or '版本' in line: + info['ifx_version'] = line.strip() + break + except: + pass + + # 检查环境变量 + info['ifx_root'] = self.env_vars.get('IFX_ROOT', '') + info['compiler_root'] = self.env_vars.get('ONEAPI_ROOT', '') + + return info + +class BuildSystem: + """构建系统主类""" + + def __init__(self): + self.project_root = Path(__file__).parent.parent + self.build_dir = self.project_root / "build" + self.intel_env = IntelEnvironment() + + # 设置控制台编码 + if sys.platform == "win32": + try: + import ctypes + # 设置控制台输出为UTF-8 + ctypes.windll.kernel32.SetConsoleOutputCP(65001) + except: + pass + + def print_header(self, text): + """打印标题""" + print(f"\n{'='*70}") + print(f" {text}") + print(f"{'='*70}\n") + + def print_step(self, step, total, message): + """打印步骤""" + print(f"[{step}/{total}] {message}...") + + def print_success(self, message): + """打印成功""" + print(f"\033[92m✓ {message}\033[0m") + + def print_error(self, message): + """打印错误""" + print(f"\033[91m✗ {message}\033[0m") + + def print_warning(self, message): + """打印警告""" + print(f"\033[93m! {message}\033[0m") + + def print_info(self, message): + """打印信息""" + print(f"\033[94mℹ {message}\033[0m") + + def check_prerequisites(self): + """检查前提条件""" + self.print_step(1, 6, "检查前提条件") + + # 检查Python版本 + python_version = sys.version.split()[0] + self.print_info(f"Python版本: {python_version}") + + # 检查平台 + self.print_info(f"平台: {platform.system()} {platform.release()}") + self.print_info(f"处理器核心数: {os.cpu_count()}") + + # 检查CMake + try: + result = subprocess.run( + ["cmake", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + version_line = result.stdout.split('\n')[0] + self.print_success(f"CMake: {version_line}") + else: + self.print_error("CMake未找到") + return False + except FileNotFoundError: + self.print_error("CMake未安装") + return False + + return True + + def setup_intel_environment(self, args): + """设置Intel环境""" + self.print_step(2, 6, "配置Intel oneAPI环境") + + if not self.intel_env.find_setvars(): + self.print_warning("未找到Intel oneAPI setvars.bat") + self.print_info("将尝试使用系统环境中的编译器") + + # 检查是否能直接访问编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + self.print_success("Intel编译器在系统PATH中找到") + return True + else: + self.print_warning("Intel编译器未在PATH中找到") + except: + self.print_warning("无法访问Intel编译器") + + return True # 继续,让CMake自己找编译器 + + # 设置环境 + if self.intel_env.setup_environment(): + compiler_info = self.intel_env.get_compiler_info() + + if compiler_info.get('ifx_version'): + self.print_success(f"Intel Fortran编译器: {compiler_info['ifx_version']}") + elif compiler_info.get('ifx_root'): + self.print_success(f"Intel编译器路径: {compiler_info['ifx_root']}") + else: + self.print_success("Intel oneAPI环境配置完成") + + return True + else: + self.print_warning("Intel环境配置失败,将继续使用系统环境") + return True + + def clean_build_directory(self, args): + """清理构建目录""" + if args.clean and self.build_dir.exists(): + self.print_info("清理构建目录...") + try: + shutil.rmtree(self.build_dir) + self.print_success("构建目录已清理") + except Exception as e: + self.print_error(f"清理失败: {e}") + if not args.force: + return False + return True + + def run_command(self, cmd, cwd=None, check=True, env=None): + """运行命令""" + if isinstance(cmd, list): + cmd_str = ' '.join(str(c) for c in cmd if c) + else: + cmd_str = str(cmd) + + print(f" \033[96m$\033[0m {cmd_str}") + + try: + # 合并环境变量 + exec_env = os.environ.copy() + if env: + exec_env.update(env) + if self.intel_env.env_vars: + exec_env.update(self.intel_env.env_vars) + + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=False, + env=exec_env + ) + + # 处理输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or '完成' in line or '生成' in line: + print(f" \033[92m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + if check and result.returncode != 0: + self.print_error(f"命令执行失败,退出码: {result.returncode}") + return False + + return True + + except Exception as e: + self.print_error(f"命令执行异常: {e}") + return False + + def configure_cmake(self, args): + """配置CMake""" + self.print_step(3, 6, "配置CMake项目") + + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + f"-DCMAKE_BUILD_TYPE={args.build_type}", + ] + + if args.compiler == "ifx": + cmake_cmd.extend(["-T", "fortran=ifx"]) + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + success = self.run_command(cmake_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("CMake配置完成") + else: + self.print_error("CMake配置失败") + + return success + + def build_project(self, args): + """构建项目""" + self.print_step(4, 6, "构建项目") + + build_cmd = [ + "cmake", + "--build", ".", + "--config", args.build_type, + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + success = self.run_command(build_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("项目构建完成") + else: + self.print_error("构建失败") + + return success + + def run_tests_with_environment(self, test_exe): + """运行单个测试,确保有Intel环境""" + try: + # 创建临时的批处理文件来运行测试 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'"{test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'"{test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + return result + + except Exception as e: + print(f"运行测试失败: {e}") + return None + + def run_tests(self, args): + """运行测试""" + self.print_step(5, 6, "运行测试") + + # 查找测试可执行文件 + test_dir = self.build_dir / "bin" / args.build_type + if not test_dir.exists(): + test_dir = self.build_dir / "bin" + if not test_dir.exists(): + test_dir = self.build_dir + + test_files = list(test_dir.glob("test_*.exe")) + + if not test_files: + self.print_warning("未找到测试程序") + return True + + all_passed = True + + for test_exe in sorted(test_files): + test_name = test_exe.stem + self.print_info(f"运行测试: {test_name}") + print(f" {'-'*50}") + + # 运行测试 + result = self.run_tests_with_environment(str(test_exe)) + + if result is None: + self.print_error(f" {test_name} 运行失败") + all_passed = False + continue + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + print(f" {line}") + + if result.returncode == 0: + self.print_success(f" {test_name} 通过") + else: + self.print_error(f" {test_name} 失败 (退出码: {result.returncode})") + all_passed = False + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + print() # 空行 + + return all_passed + + def create_test_runner(self, args): + """创建独立的测试运行器""" + self.print_step(6, 6, "创建测试运行器") + + runner_path = self.build_dir / "run_tests.bat" + + content = f'''@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Test Runner +echo ======================================== +echo. + +REM Setup Intel oneAPI environment +set "SETVARS_PATH={self.intel_env.setvars_path or ''}" +if exist "%SETVARS_PATH%" ( + call "%SETVARS_PATH%" >nul + echo [INFO] Intel environment configured +) else ( + echo [WARNING] Intel environment not found + echo [WARNING] Tests may fail without runtime libraries +) + +echo. + +REM Run all test executables +set "TEST_COUNT=0" +set "PASS_COUNT=0" + +for %%f in ("bin\\{args.build_type}\\test_*.exe") do ( + set /a TEST_COUNT+=1 + echo [TEST %%f] + echo {'-'*50} + + %%f + if errorlevel 1 ( + echo [FAILED] %%f + ) else ( + echo [PASSED] %%f + set /a PASS_COUNT+=1 + ) + echo. +) + +echo ======================================== +echo Tests: %PASS_COUNT%/%TEST_COUNT% passed +if %PASS_COUNT% equ %TEST_COUNT% ( + echo [SUCCESS] All tests passed! +) else ( + echo [FAILURE] Some tests failed +) +echo ======================================== + +pause +''' + + with open(runner_path, 'w', encoding='utf-8') as f: + f.write(content) + + self.print_success(f"测试运行器已创建: {runner_path}") + self.print_info(f"使用方法: cd build && run_tests.bat") + + return runner_path + + def generate_report(self, args, build_time, tests_passed): + """生成构建报告""" + self.print_header("构建完成") + + print(f"项目: {self.project_root.name}") + print(f"构建类型: {args.build_type}") + print(f"编译器: {args.compiler}") + print(f"并行作业: {args.jobs}") + print(f"总耗时: {build_time:.1f}秒") + print(f"测试结果: {'全部通过' if tests_passed else '有失败'}") + + # 显示生成的可执行文件 + bin_dir = self.build_dir / "bin" / args.build_type + if bin_dir.exists(): + print(f"\n生成的可执行文件:") + for exe in sorted(bin_dir.glob("*.exe")): + size_mb = exe.stat().st_size / (1024 * 1024) + print(f" • {exe.name} ({size_mb:.2f} MB)") + + # 显示测试运行器信息 + runner_path = self.build_dir / "run_tests.bat" + if runner_path.exists(): + print(f"\n独立测试运行器:") + print(f" • {runner_path.name}") + print(f" 在Intel oneAPI环境中运行所有测试") + + def run(self): + """运行构建系统""" + parser = argparse.ArgumentParser( + description="Fortran CFD项目构建工具", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认构建 + %(prog)s --clean # 清理后构建 + %(prog)s --build-type Release # Release构建 + %(prog)s --no-tests # 只构建,不运行测试 + %(prog)s -j8 --verbose # 8线程并行构建,详细输出 + """ + ) + + parser.add_argument("--build-type", choices=["Debug", "Release"], + default="Debug", help="构建类型") + parser.add_argument("--compiler", choices=["ifx", "ifort"], + default="ifx", help="Fortran编译器") + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-tests", action="store_true", + help="跳过测试") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + + args = parser.parse_args() + + # 开始构建 + start_time = time.time() + + self.print_header("Fortran CFD 项目构建系统") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 检查前提条件 + if not self.check_prerequisites(): + if not args.force: + return 1 + + # 2. 设置Intel环境 + if not self.setup_intel_environment(args): + if not args.force: + return 1 + + # 3. 清理目录 + if not self.clean_build_directory(args): + if not args.force: + return 1 + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 4. 配置CMake + if not self.configure_cmake(args): + if not args.force: + return 1 + + # 5. 构建项目 + if not self.build_project(args): + if not args.force: + return 1 + + # 6. 运行测试和创建测试运行器 + tests_passed = True + if not args.no_tests: + tests_passed = self.run_tests(args) + + # 创建测试运行器 + self.create_test_runner(args) # 传递 args 参数 + + # 7. 生成报告 + build_time = time.time() - start_time + self.generate_report(args, build_time, tests_passed) + + return 0 if tests_passed else 1 + + except KeyboardInterrupt: + self.print_error("\n构建被用户中断") + return 1 + except Exception as e: + self.print_error(f"构建过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + builder = BuildSystem() + return builder.run() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/scripts/requirements.txt b/example/1d-linear-convection/weno3/fortran/registry/02c/scripts/requirements.txt new file mode 100644 index 000000000..d21a12b45 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/scripts/requirements.txt @@ -0,0 +1,2 @@ +# 构建脚本的Python依赖(可选) +# 目前没有特殊依赖,保持空文件或添加未来可能需要的包 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02c/src/CMakeLists.txt new file mode 100644 index 000000000..f958422d2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/src/CMakeLists.txt @@ -0,0 +1,15 @@ +# ==================== src\CMakeLists.txt ==================== + +# src/CMakeLists.txt +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(core) +add_subdirectory(infrastructure) +add_subdirectory(numerics) +add_subdirectory(manager) + +message(STATUS "源代码目录配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02c/src/core/CMakeLists.txt new file mode 100644 index 000000000..3dbf384cb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/src/core/CMakeLists.txt @@ -0,0 +1,25 @@ +# src/core/CMakeLists.txt +message(STATUS "配置核心模块...") + +add_library(core STATIC + registry.f90 + factory_interfaces.f90 + #component_manager.f90 # ← 添加这一行 +) + +target_include_directories(core PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 安装规则 +install(TARGETS core + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) +install(FILES + ${CMAKE_Fortran_MODULE_DIRECTORY}/registry_module.mod + ${CMAKE_Fortran_MODULE_DIRECTORY}/factory_interfaces.mod + DESTINATION include/fortran_cfd/core +) + +message(STATUS "核心模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/02c/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..a960167c3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/src/core/factory_interfaces.f90 @@ -0,0 +1,17 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, factory_procedure + + ! Factory procedure interface + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/02c/src/core/registry.f90 new file mode 100644 index 000000000..d620f5ec9 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/src/core/registry.f90 @@ -0,0 +1,656 @@ +! src/core/registry.f90 +module registry_module + use, intrinsic :: iso_fortran_env, only: real64 + use factory_interfaces, only: factory_procedure + implicit none + + private + + ! ==================== PUBLIC INTERFACE ==================== + ! 原有接口保持不变 + public :: real64, component_info, component_registry + public :: register_component_simple, initialize_registry, cleanup_registry + public :: has_component, get_available_components + public :: registry_is_initialized, registry_get_size + + ! 新增工厂相关接口 + public :: factory_ptr_type, component_registry_entry + public :: register_component_with_factory + public :: create_component_from_registry + public :: has_factory_component + public :: list_factory_components + + ! ==================== TYPE DEFINITIONS ==================== + + ! 原有类型定义 + type :: component_info + character(len=32) :: category = "" + character(len=32) :: name = "" + integer :: order = 0 + contains + procedure :: print => ci_print + end type component_info + + ! 新增:工厂指针类型 + type :: factory_ptr_type + procedure(factory_procedure), pointer, nopass :: ptr => null() + end type factory_ptr_type + + ! 新增:带工厂的注册条目 + type :: component_registry_entry + character(len=32) :: category = "" + character(len=32) :: name = "" + integer :: order = 0 + type(factory_ptr_type) :: factory + logical :: has_factory = .false. + contains + procedure :: print_with_factory => entry_print + procedure :: can_create => entry_has_factory + end type component_registry_entry + + ! 原有注册表类型(扩展以支持工厂) + type :: component_registry_type + private + ! 简单注册组件(向后兼容) + type(component_info), allocatable :: simple_components(:) + integer :: simple_count = 0 + + ! 带工厂的组件(新增) + type(component_registry_entry), allocatable :: factory_components(:) + integer :: factory_count = 0 + + integer :: capacity = 100 + logical :: verbose = .true. + logical :: initialized = .false. + contains + ! 简单注册方法 + procedure :: register => cr_register ! ← 保持原名 + procedure :: get => cr_get ! ← 保持原名 + procedure :: list_simple => cr_list_simple ! ← 新增别名 + + ! 工厂注册方法 + procedure :: register_with_factory => cr_register_with_factory + procedure :: get_with_factory => cr_get_with_factory + procedure :: list_with_factory => cr_list_with_factory + procedure :: create_from_factory => cr_create_from_factory + + ! 通用方法 + procedure :: clear => cr_clear + procedure :: size => cr_size + procedure :: total_size => cr_total_size + procedure :: is_initialized => cr_is_initialized + end type component_registry_type + + ! Global registry instance + type(component_registry_type), save :: component_registry + +contains + + ! ==================== PUBLIC API ==================== + + ! Initialize registry (保持不变) + subroutine initialize_registry(initial_capacity, verbose) + integer, optional, intent(in) :: initial_capacity + logical, optional, intent(in) :: verbose + + if (component_registry%initialized) then + if (component_registry%verbose) then + print *, "[INFO] Registry already initialized" + end if + return + end if + + if (present(initial_capacity)) then + component_registry%capacity = max(10, initial_capacity) + end if + + if (present(verbose)) then + component_registry%verbose = verbose + end if + + ! Allocate arrays for both types + allocate(component_registry%simple_components(component_registry%capacity)) + allocate(component_registry%factory_components(component_registry%capacity)) + + component_registry%initialized = .true. + component_registry%simple_count = 0 + component_registry%factory_count = 0 + + if (component_registry%verbose) then + print *, "[INIT] Registry initialized, capacity:", component_registry%capacity + print *, " Supports both simple and factory-based registration" + end if + end subroutine initialize_registry + + ! Cleanup registry (保持不变) + subroutine cleanup_registry + call component_registry%clear() + if (component_registry%verbose) then + print *, "[CLEANUP] Registry cleaned up" + end if + end subroutine cleanup_registry + + ! ==================== SIMPLE REGISTRATION (向后兼容) ==================== + + ! Simple registration (no factory) - 保持不变 + subroutine register_component_simple(category, name) + character(len=*), intent(in) :: category, name + + type(component_info) :: info + + info%category = to_lower(trim(adjustl(category))) + info%name = to_lower(trim(adjustl(name))) + info%order = 0 + + call component_registry%register(info) + end subroutine register_component_simple + + ! Check if component exists (简单注册) + function has_component(category, name) result(found) + character(len=*), intent(in) :: category, name + logical :: found + + type(component_info) :: info + character(len=32) :: cat_lower, name_lower + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + info = component_registry%get(cat_lower, name_lower) + found = (len_trim(info%category) > 0) + end function has_component + + ! Get available components in a category (简单注册) + subroutine get_available_components(category, names, orders) + character(len=*), intent(in) :: category + character(len=:), allocatable, intent(out), optional :: names(:) + integer, allocatable, intent(out), optional :: orders(:) + + character(len=32) :: cat_lower + integer :: i, count, idx + type(component_info) :: info + + cat_lower = to_lower(trim(adjustl(category))) + + ! Count components in this category + count = 0 + do i = 1, component_registry%simple_count + if (component_registry%simple_components(i)%category == cat_lower) then + count = count + 1 + end if + end do + + ! Allocate arrays if requested + if (present(names)) then + allocate(character(len=32) :: names(count)) + end if + + if (present(orders)) then + allocate(orders(count)) + end if + + ! Fill arrays + idx = 1 + do i = 1, component_registry%simple_count + if (component_registry%simple_components(i)%category == cat_lower) then + info = component_registry%simple_components(i) + if (present(names)) then + names(idx) = info%name + end if + if (present(orders)) then + orders(idx) = info%order + end if + idx = idx + 1 + end if + end do + end subroutine get_available_components + + ! ==================== FACTORY REGISTRATION (新增) ==================== + + ! Register component with factory function + subroutine register_component_with_factory(category, name, factory_func, order) + character(len=*), intent(in) :: category, name + procedure(factory_procedure) :: factory_func + integer, optional, intent(in) :: order + + type(component_registry_entry) :: entry + character(len=32) :: cat_lower, name_lower + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + entry%category = cat_lower + entry%name = name_lower + + if (present(order)) then + entry%order = order + else + entry%order = 0 + end if + + entry%factory%ptr => factory_func + entry%has_factory = .true. + + call component_registry%register_with_factory(entry) + end subroutine register_component_with_factory + + ! Create component from registry using factory + function create_component_from_registry(category, name) result(instance) + character(len=*), intent(in) :: category, name + class(*), allocatable :: instance + + character(len=32) :: cat_lower, name_lower + integer :: status + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + call component_registry%create_from_factory(cat_lower, name_lower, instance, status) + + if (status /= 0) then + if (component_registry%verbose) then + print *, "[ERROR] Failed to create component: ", trim(category), ".", trim(name) + end if + end if + end function create_component_from_registry + + ! Check if component has factory + function has_factory_component(category, name) result(found) + character(len=*), intent(in) :: category, name + logical :: found + + character(len=32) :: cat_lower, name_lower + + cat_lower = to_lower(trim(adjustl(category))) + name_lower = to_lower(trim(adjustl(name))) + + found = .false. + + if (.not. component_registry%initialized) return + + ! 简化的查找逻辑(实际应调用内部方法) + found = component_registry%factory_count > 0 ! 占位实现 + end function has_factory_component + + ! List all factory components + subroutine list_factory_components(category) + character(len=*), optional, intent(in) :: category + + if (present(category)) then + call component_registry%list_with_factory(category) + else + call component_registry%list_with_factory("") + end if + end subroutine list_factory_components + + ! ==================== PUBLIC UTILITY FUNCTIONS ==================== + + ! Public function to check if registry is initialized + function registry_is_initialized() result(is_initialized) + logical :: is_initialized + is_initialized = component_registry%is_initialized() + end function registry_is_initialized + + ! Public function to get total registry size + function registry_get_size() result(size_val) + integer :: size_val + size_val = component_registry%total_size() + end function registry_get_size + + ! ==================== COMPONENT INFO METHODS ==================== + + subroutine ci_print(this) + class(component_info), intent(in) :: this + + if (this%order > 0) then + print *, " [", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ")]" + else + print *, " [", trim(this%category), ".", trim(this%name), "]" + end if + end subroutine ci_print + + subroutine entry_print(this) + class(component_registry_entry), intent(in) :: this + + if (this%has_factory) then + if (this%order > 0) then + print *, " [FACTORY] ", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ")" + else + print *, " [FACTORY] ", trim(this%category), ".", trim(this%name) + end if + else + if (this%order > 0) then + print *, " [SIMPLE] ", trim(this%category), ".", trim(this%name), & + " (order:", this%order, ")" + else + print *, " [SIMPLE] ", trim(this%category), ".", trim(this%name) + end if + end if + end subroutine entry_print + + logical function entry_has_factory(this) + class(component_registry_entry), intent(in) :: this + entry_has_factory = this%has_factory + end function entry_has_factory + + ! ==================== REGISTRY INTERNAL METHODS ==================== + + ! ---------- Simple Registration Methods ---------- + + subroutine cr_register(this, info) + class(component_registry_type), intent(inout) :: this + type(component_info), intent(in) :: info + + type(component_info), allocatable :: temp(:) + integer :: i + + if (.not. this%initialized) then + error stop "[ERROR] Registry not initialized, call initialize_registry first" + end if + + ! Check if already exists + do i = 1, this%simple_count + if (this%simple_components(i)%category == info%category .and. & + this%simple_components(i)%name == info%name) then + if (this%verbose) then + print *, "[WARN] Overwriting simple component: ", & + trim(info%category), ".", trim(info%name) + end if + this%simple_components(i) = info + return + end if + end do + + ! Expand array if needed + if (this%simple_count >= this%capacity) then + this%capacity = this%capacity * 2 + allocate(temp(this%capacity)) + temp(1:this%simple_count) = this%simple_components(1:this%simple_count) + call move_alloc(temp, this%simple_components) + + if (this%verbose) then + print *, "[INFO] Simple registry expanded to capacity:", this%capacity + end if + end if + + ! Add component + this%simple_count = this%simple_count + 1 + this%simple_components(this%simple_count) = info + + if (this%verbose) then + print *, "[OK] Registered simple: ", trim(info%category), ".", trim(info%name) + end if + end subroutine cr_register + + function cr_get(this, category, name) result(info) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category, name + type(component_info) :: info + + integer :: i + + ! Initialize return value as empty + info%category = "" + info%name = "" + info%order = 0 + + if (.not. this%initialized) then + return + end if + + do i = 1, this%simple_count + if (this%simple_components(i)%category == category .and. & + this%simple_components(i)%name == name) then + info = this%simple_components(i) + return + end if + end do + end function cr_get + + subroutine cr_list_simple(this) + class(component_registry_type), intent(in) :: this + integer :: i + + if (.not. this%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + if (this%simple_count == 0) then + print *, "[INFO] No simple components registered" + return + end if + + print *, "=== Simple Registry Contents (", this%simple_count, " components) ===" + do i = 1, this%simple_count + call this%simple_components(i)%print() + end do + print *, "==========================================" + end subroutine cr_list_simple + + ! ---------- Factory Registration Methods ---------- + + subroutine cr_register_with_factory(this, entry) + class(component_registry_type), intent(inout) :: this + type(component_registry_entry), intent(in) :: entry + + type(component_registry_entry), allocatable :: temp(:) + integer :: i + + if (.not. this%initialized) then + error stop "[ERROR] Registry not initialized, call initialize_registry first" + end if + + ! Check if already exists + do i = 1, this%factory_count + if (this%factory_components(i)%category == entry%category .and. & + this%factory_components(i)%name == entry%name) then + if (this%verbose) then + print *, "[WARN] Overwriting factory component: ", & + trim(entry%category), ".", trim(entry%name) + end if + this%factory_components(i) = entry + return + end if + end do + + ! Expand array if needed + if (this%factory_count >= this%capacity) then + this%capacity = this%capacity * 2 + allocate(temp(this%capacity)) + temp(1:this%factory_count) = this%factory_components(1:this%factory_count) + call move_alloc(temp, this%factory_components) + + if (this%verbose) then + print *, "[INFO] Factory registry expanded to capacity:", this%capacity + end if + end if + + ! Add component + this%factory_count = this%factory_count + 1 + this%factory_components(this%factory_count) = entry + + if (this%verbose) then + print *, "[FACTORY] Registered with factory: ", & + trim(entry%category), ".", trim(entry%name) + end if + end subroutine cr_register_with_factory + + function cr_get_with_factory(this, category, name) result(entry) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category, name + type(component_registry_entry) :: entry + + integer :: i + + ! Initialize return value as empty + entry%category = "" + entry%name = "" + entry%order = 0 + entry%factory%ptr => null() + entry%has_factory = .false. + + if (.not. this%initialized) then + return + end if + + do i = 1, this%factory_count + if (this%factory_components(i)%category == category .and. & + this%factory_components(i)%name == name) then + entry = this%factory_components(i) + return + end if + end do + end function cr_get_with_factory + + subroutine cr_list_with_factory(this, category) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category + + character(len=32) :: cat_lower + integer :: i, count + + if (.not. this%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + cat_lower = to_lower(trim(adjustl(category))) + + if (this%factory_count == 0) then + print *, "[INFO] No factory components registered" + return + end if + + ! Count components in this category + count = 0 + do i = 1, this%factory_count + if (len_trim(cat_lower) == 0 .or. & + this%factory_components(i)%category == cat_lower) then + count = count + 1 + end if + end do + + if (count == 0) then + if (len_trim(cat_lower) > 0) then + print *, "[INFO] No factory components in category: ", trim(cat_lower) + else + print *, "[INFO] No factory components registered" + end if + return + end if + + print *, "=== Factory Registry Contents (", count, " components) ===" + do i = 1, this%factory_count + if (len_trim(cat_lower) == 0 .or. & + this%factory_components(i)%category == cat_lower) then + call this%factory_components(i)%print_with_factory() + end if + end do + print *, "==========================================" + end subroutine cr_list_with_factory + + subroutine cr_create_from_factory(this, category, name, instance, status) + class(component_registry_type), intent(in) :: this + character(len=*), intent(in) :: category, name + class(*), allocatable, intent(out) :: instance + integer, intent(out) :: status + + type(component_registry_entry) :: entry + integer :: i + + status = -1 ! Default: not found + + if (.not. this%initialized) then + if (this%verbose) then + print *, "[ERROR] Registry not initialized" + end if + return + end if + + ! Find the entry + do i = 1, this%factory_count + if (this%factory_components(i)%category == category .and. & + this%factory_components(i)%name == name) then + entry = this%factory_components(i) + + if (entry%has_factory .and. associated(entry%factory%ptr)) then + ! Call the factory function + call entry%factory%ptr(instance) + status = 0 ! Success + + if (this%verbose) then + print *, "[FACTORY] Created component: ", & + trim(category), ".", trim(name) + end if + else + status = -2 ! No factory function + if (this%verbose) then + print *, "[ERROR] No factory function for: ", & + trim(category), ".", trim(name) + end if + end if + return + end if + end do + + ! If we get here, component not found + if (this%verbose) then + print *, "[ERROR] Factory component not found: ", & + trim(category), ".", trim(name) + end if + end subroutine cr_create_from_factory + + ! ---------- Common Methods ---------- + + subroutine cr_clear(this) + class(component_registry_type), intent(inout) :: this + + if (allocated(this%simple_components)) then + deallocate(this%simple_components) + end if + + if (allocated(this%factory_components)) then + deallocate(this%factory_components) + end if + + this%simple_count = 0 + this%factory_count = 0 + this%capacity = 100 + this%initialized = .false. + end subroutine cr_clear + + integer function cr_total_size(this) + class(component_registry_type), intent(in) :: this + cr_total_size = this%simple_count + this%factory_count + end function cr_total_size + + integer function cr_size(this) + class(component_registry_type), intent(in) :: this + cr_size = this%simple_count ! Backward compatibility + end function cr_size + + logical function cr_is_initialized(this) + class(component_registry_type), intent(in) :: this + cr_is_initialized = this%initialized + end function cr_is_initialized + + ! ==================== UTILITY FUNCTIONS ==================== + + function to_lower(str) result(lower_str) + character(len=*), intent(in) :: str + character(len=len(str)) :: lower_str + integer :: i + + do i = 1, len(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + lower_str(i:i) = char(ichar(str(i:i)) + 32) + else + lower_str(i:i) = str(i:i) + end if + end do + end function to_lower + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02c/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..46964ce79 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/src/infrastructure/CMakeLists.txt @@ -0,0 +1,20 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "配置基础设施模块...") + +add_library(infrastructure STATIC + config.f90 + mesh.f90 +) + +target_link_libraries(infrastructure PRIVATE core) + +target_include_directories(infrastructure PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS infrastructure + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "基础设施模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/02c/src/infrastructure/config.f90 new file mode 100644 index 000000000..d3b1e0dfb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/src/infrastructure/config.f90 @@ -0,0 +1,90 @@ +! src/infrastructure/config.f90 +module config_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, cfd_config, config_print, config_with_reconstruction + + ! CFD configuration type + type :: cfd_config + character(len=20) :: ic_type = "step" + character(len=20) :: recon_scheme = "eno" + character(len=20) :: flux_type = "rusanov" + integer :: rk_order = 1 + real(real64) :: wave_speed = 1.0_real64 + real(real64) :: final_time = 0.625_real64 + real(real64) :: dt = 0.025_real64 + character(len=20) :: boundary_type = "periodic" + real(real64) :: left_boundary_value = 1.0_real64 + real(real64) :: right_boundary_value = 2.0_real64 + integer :: spatial_order = 2 + logical :: verbose = .true. + end type cfd_config + +contains + + subroutine config_print(this) + type(cfd_config), intent(in) :: this + + print *, "=== CFD Configuration ===" + print *, "Initial condition: ", trim(this%ic_type) + print *, "Reconstruction: ", trim(this%recon_scheme), " (order:", this%spatial_order, ")" + print *, "Flux type: ", trim(this%flux_type) + print *, "Time integration: RK", this%rk_order + print *, "Wave speed: ", this%wave_speed + print *, "Final time: ", this%final_time + print *, "Time step: ", this%dt + print *, "Boundary: ", trim(this%boundary_type) + if (trim(this%boundary_type) == 'dirichlet') then + print *, " Dirichlet values: [", this%left_boundary_value, ", ", & + this%right_boundary_value, "]" + end if + print *, "===============================" + end subroutine config_print + + subroutine config_with_reconstruction(this, scheme, order) + type(cfd_config), intent(inout) :: this + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + character(len=20) :: scheme_lower + + ! Convert to lowercase + scheme_lower = scheme + call to_lower_inplace(scheme_lower) + this%recon_scheme = trim(adjustl(scheme_lower)) + + ! Set order + if (present(order)) then + this%spatial_order = order + else + ! Smart defaults + if (index(this%recon_scheme, 'weno') > 0) then + this%spatial_order = 5 + else if (trim(this%recon_scheme) == 'eno') then + this%spatial_order = 3 + else + print *, "[ERROR] Unsupported reconstruction scheme: ", trim(this%recon_scheme) + return + end if + end if + + if (this%verbose) then + print *, "[CONFIG] Reconstruction: ", trim(this%recon_scheme), & + " Order: ", this%spatial_order + end if + end subroutine config_with_reconstruction + + subroutine to_lower_inplace(str) + character(len=*), intent(inout) :: str + integer :: i + + do i = 1, len_trim(str) + if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then + str(i:i) = char(ichar(str(i:i)) + 32) + end if + end do + end subroutine to_lower_inplace + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/02c/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..896969bd0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/src/infrastructure/mesh.f90 @@ -0,0 +1,74 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, mesh_type, mesh_init, mesh_print_info + + ! mesh + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer :: ncells = 40 + integer :: nnodes + integer :: nx + real(wp), allocatable :: x(:) ! + real(wp), allocatable :: xcc(:) ! + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer, optional, intent(in) :: ncells + + integer :: i + + ! Set + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! computation + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! node + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! cell + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== ===" + print *, ": [", this%xmin, ", ", this%xmax, "]" + print *, ": ", this%ncells + print *, ": ", this%nnodes + print *, " dx: ", this%dx + print *, " L: ", this%L + print *, "==========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/src/manager/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02c/src/manager/CMakeLists.txt new file mode 100644 index 000000000..00c8bf49b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/src/manager/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/manager/CMakeLists.txt +message(STATUS "配置管理器模块...") + +# 创建管理器库 +add_library(manager STATIC + component_manager.f90 + component_factory.f90 +) + +# 明确依赖关系:管理器依赖所有其他模块 +target_link_libraries(manager + PRIVATE + core + infrastructure + reconstructor + flux +) + +# 设置模块输出目录 +set_target_properties(manager PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "管理器模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/src/manager/component_factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/02c/src/manager/component_factory.f90 new file mode 100644 index 000000000..114fedea5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/src/manager/component_factory.f90 @@ -0,0 +1,127 @@ +! src/manager/component_factory.f90 +module component_factory_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use config_module, only: cfd_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + use eno_reconstructor_module, only: eno_reconstructor, create_eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor, create_weno3_reconstructor + use rusanov_flux_module, only: rusanov_flux, create_rusanov_flux + + implicit none + private + public :: wp, create_reconstructor, create_flux_calculator + + ! 错误代码 + integer, parameter :: CM_SUCCESS = 0 + integer, parameter :: CM_ERROR_UNKNOWN_SCHEME = 1 + integer, parameter :: CM_ERROR_UNKNOWN_FLUX = 2 + integer, parameter :: CM_ERROR_INVALID_ORDER = 3 + +contains + + ! ==================== 重构器创建 ==================== + + function create_reconstructor(config, status) result(recon) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(reconstructor_base), allocatable :: recon + + character(len=20) :: scheme + integer :: order, error_code + + scheme = trim(adjustl(config%recon_scheme)) + order = config%spatial_order + + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating reconstructor: ", scheme, " order=", order + end if + + select case(scheme) + case('eno') + allocate(eno_reconstructor :: recon) + select type(recon) + type is(eno_reconstructor) + recon = create_eno_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case('weno3') + allocate(weno3_reconstructor :: recon) + select type(recon) + type is(weno3_reconstructor) + recon = create_weno3_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case default + error_code = CM_ERROR_UNKNOWN_SCHEME + if (config%verbose) then + print *, "[ERROR] Unknown reconstructor scheme: ", scheme + print *, " Available: eno, weno3" + end if + end select + + ! 检查阶数有效性 + if (error_code == CM_SUCCESS) then + if (order < 1) then + error_code = CM_ERROR_INVALID_ORDER + if (config%verbose) then + print *, "[ERROR] Invalid spatial order: ", order + end if + end if + end if + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Reconstructor creation failed" + end if + end function create_reconstructor + + ! ==================== 通量计算器创建 ==================== + + function create_flux_calculator(config, status) result(flux) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(flux_calculator_base), allocatable :: flux + + character(len=20) :: flux_type + integer :: error_code + + flux_type = trim(adjustl(config%flux_type)) + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating flux calculator: ", flux_type + end if + + select case(flux_type) + case('rusanov') + allocate(rusanov_flux :: flux) + select type(flux) + type is(rusanov_flux) + flux = create_rusanov_flux() + flux%wave_speed_default = config%wave_speed + end select + + case default + error_code = CM_ERROR_UNKNOWN_FLUX + if (config%verbose) then + print *, "[ERROR] Unknown flux type: ", flux_type + print *, " Available: rusanov" + end if + end select + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Flux calculator creation failed" + end if + end function create_flux_calculator + +end module component_factory_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/src/manager/component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/02c/src/manager/component_manager.f90 new file mode 100644 index 000000000..9e095c257 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/src/manager/component_manager.f90 @@ -0,0 +1,75 @@ +! src/manager/component_manager.f90 +module component_manager_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use config_module, only: cfd_config + use component_factory_module, only: create_reconstructor, create_flux_calculator + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + + implicit none + private + public :: wp, component_manager_info, validate_config + public :: create_reconstructor, create_flux_calculator + +contains + + ! ==================== 配置验证 ==================== + + function validate_config(config) result(is_valid) + type(cfd_config), intent(in) :: config + logical :: is_valid + + integer :: status + class(reconstructor_base), allocatable :: test_recon + class(flux_calculator_base), allocatable :: test_flux + + is_valid = .false. + + ! 测试创建重构器 + test_recon = create_reconstructor(config, status) + if (status /= 0) then + if (config%verbose) then + print *, "[CONFIG VALIDATION] Invalid reconstructor configuration" + end if + return + end if + + ! 测试创建通量计算器 + test_flux = create_flux_calculator(config, status) + if (status /= 0) then + if (config%verbose) then + print *, "[CONFIG VALIDATION] Invalid flux configuration" + end if + return + end if + + ! 清理测试组件 + if (allocated(test_recon)) deallocate(test_recon) + if (allocated(test_flux)) deallocate(test_flux) + + is_valid = .true. + + if (config%verbose) then + print *, "[CONFIG VALIDATION] Configuration is valid" + end if + end function validate_config + + ! ==================== 信息显示 ==================== + + subroutine component_manager_info() + print *, "=== Component Manager ===" + print *, "Available reconstructors:" + print *, " - eno (orders: 1-7)" + print *, " - weno3 (order: 3)" + print *, "" + print *, "Available flux calculators:" + print *, " - rusanov" + print *, "" + print *, "Features:" + print *, " - Configuration validation" + print *, " - Component creation from config" + print *, " - Error handling with status codes" + print *, "=========================" + end subroutine component_manager_info + +end module component_manager_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..66874a7ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/CMakeLists.txt @@ -0,0 +1,7 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..3fde7746b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,28 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +# Start with just the base module +add_library(flux STATIC + base.f90 + rusanov.f90 +) + +#target_link_libraries(flux PRIVATE core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 明确设置不使用 /Qm64 选项 +if(CMAKE_Fortran_COMPILER_ID MATCHES "IntelLLVM|Intel") + set_target_properties(flux PROPERTIES + Fortran_FLAGS "${CMAKE_Fortran_FLAGS} /Qm64" # 明确设置,避免继承问题 + ) +endif() + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/flux/base.f90 new file mode 100644 index 000000000..7080a7ae4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/flux/base.f90 @@ -0,0 +1,30 @@ +!src/numerics/flux/base.f90 +module flux_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, flux_calculator_base + + ! Base flux calculator type + type :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure :: info => flux_info + procedure :: print_basic_info => flux_print_basic ! 添加辅助方法 + end type flux_calculator_base + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "Flux calculator information:" + print *, " Name: ", trim(this%name) + end subroutine flux_info + + subroutine flux_print_basic(this) + class(flux_calculator_base), intent(in) :: this + print *, " Name: ", trim(this%name) + end subroutine flux_print_basic + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/flux/rusanov.f90 b/example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/flux/rusanov.f90 new file mode 100644 index 000000000..daa9e3bb5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/flux/rusanov.f90 @@ -0,0 +1,41 @@ +! src/numerics/flux/rusanov.f90 +module rusanov_flux_module + use, intrinsic :: iso_fortran_env, only: real64 + use flux_base_module, only: flux_calculator_base + implicit none + + private + public :: real64, rusanov_flux, create_rusanov_flux + + type, extends(flux_calculator_base) :: rusanov_flux + real(real64) :: wave_speed_default = 1.0_real64 + contains + procedure :: info => rusanov_info + end type rusanov_flux + + ! 构造函数接口 + interface rusanov_flux + module procedure create_rusanov_flux + end interface + +contains + + ! 构造函数 + type(rusanov_flux) function create_rusanov_flux() result(this) + this%name = "Rusanov" + this%wave_speed_default = 1.0_real64 + end function create_rusanov_flux + + subroutine rusanov_info(this) + class(rusanov_flux), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Flux calculator information:" + call this%print_basic_info() + + ! 添加Rusanov特有信息 + print *, " Type: Rusanov flux" + print *, " Default wave speed: ", this%wave_speed_default + end subroutine rusanov_info + +end module rusanov_flux_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..5e4b938d5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,22 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "Configuring reconstructor module...") + +# Start with just the base module +add_library(reconstructor STATIC + base.f90 + eno.f90 + weno3.f90 +) + +target_link_libraries(reconstructor PRIVATE core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Reconstructor module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..53798d021 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/reconstructor/base.f90 @@ -0,0 +1,33 @@ +!src/numerics/reconstructor/base.f90 +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, reconstructor_base + + ! Base reconstructor type + type :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + contains + procedure :: info => reconstructor_info + procedure :: print_basic_info => reconstructor_print_basic ! 添加一个辅助方法 + end type reconstructor_base + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + print *, "Reconstructor information:" + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_info + + subroutine reconstructor_print_basic(this) + class(reconstructor_base), intent(in) :: this + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_print_basic + +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/reconstructor/eno.f90 b/example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/reconstructor/eno.f90 new file mode 100644 index 000000000..f973e8b32 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/reconstructor/eno.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/eno.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, eno_reconstructor, create_eno_reconstructor ! ← 添加这个 + + type, extends(reconstructor_base) :: eno_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => eno_info + end type eno_reconstructor + + ! 构造函数接口 + interface eno_reconstructor + module procedure create_eno_reconstructor + end interface + +contains + + ! 构造函数 + type(eno_reconstructor) function create_eno_reconstructor() result(this) + this%name = "ENO" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_eno_reconstructor + + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加ENO特有信息 + print *, " Type: ENO reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/reconstructor/weno3.f90 b/example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/reconstructor/weno3.f90 new file mode 100644 index 000000000..d5b7a7477 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/src/numerics/reconstructor/weno3.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/weno3.f90 +module weno3_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno3_reconstructor, create_weno3_reconstructor + + type, extends(reconstructor_base) :: weno3_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno3_info + end type weno3_reconstructor + + ! 构造函数接口 + interface weno3_reconstructor + module procedure create_weno3_reconstructor + end interface + +contains + + ! 构造函数 + type(weno3_reconstructor) function create_weno3_reconstructor() result(this) + this%name = "WENO3" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_weno3_reconstructor + + subroutine weno3_info(this) + class(weno3_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加WENO3特有信息 + print *, " Type: WENO-3 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno3_info + +end module weno3_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02c/tests/CMakeLists.txt new file mode 100644 index 000000000..1c7c685bd --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/tests/CMakeLists.txt @@ -0,0 +1,50 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +add_executable(test_minimal_simple test_minimal_simple.f90) + +message(STATUS "CMAKE_Fortran_MODULE_DIRECTORY=${CMAKE_Fortran_MODULE_DIRECTORY}") + +#target_include_directories( test_minimal_simple +# PRIVATE +# ${CMAKE_Fortran_MODULE_DIRECTORY} +#) + +target_link_libraries( test_minimal_simple + PRIVATE + infrastructure +) + +add_executable(test_simple_link test_simple_link.f90) +target_link_libraries(test_simple_link + PRIVATE + reconstructor + flux +) + + +add_executable(test_factory_simple test_factory_simple.f90) +target_link_libraries(test_factory_simple + PRIVATE + core + infrastructure + reconstructor + flux +) + +# 更新测试链接 +add_executable(test_component_manager test_component_manager.f90) + +target_link_libraries(test_component_manager + PRIVATE + manager # ← 链接到新的管理器库 + infrastructure +) + +add_executable(test_architecture test_architecture.f90) +target_link_libraries(test_architecture + PRIVATE + core + infrastructure + manager +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/tests/test_architecture.f90 b/example/1d-linear-convection/weno3/fortran/registry/02c/tests/test_architecture.f90 new file mode 100644 index 000000000..1c40d4677 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/tests/test_architecture.f90 @@ -0,0 +1,117 @@ +! tests/test_architecture.f90 +program test_architecture + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module + use config_module + use mesh_module + use component_manager_module + + implicit none + + print *, "=== CFD架构测试 ===" + print *, "" + + ! 测试1: 基本系统 + call test_basic_systems() + + ! 测试2: 组件注册 + call test_registry_integration() + + ! 测试3: 配置验证 + call test_config_validation() + + ! 测试4: 完整流程 + call test_full_workflow() + + print *, "" + print *, "=== 架构测试完成 ===" + +contains + + subroutine test_basic_systems() + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "1. 测试基本系统..." + print *, "-------------------" + + ! 测试配置 + call config_print(config) + print *, "✓ 配置创建成功" + + ! 测试网格 + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "✓ 网格创建成功" + print *, "" + end subroutine test_basic_systems + + subroutine test_registry_integration() + print *, "2. 测试注册系统集成..." + print *, "------------------------" + + call initialize_registry(verbose=.true.) + + ! 注册核心组件 + print *, "注册核心组件:" + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + ! 显示注册内容 + call component_registry%list_simple() + print *, "注册表大小: ", registry_get_size() + + call cleanup_registry() + print *, "✓ 注册系统测试完成" + print *, "" + end subroutine test_registry_integration + + subroutine test_config_validation() + type(cfd_config) :: config + logical :: is_valid + + print *, "3. 测试配置验证..." + print *, "-------------------" + + ! 测试有效配置 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + print *, "测试配置:" + print *, " - 重构器: ", trim(config%recon_scheme), " 阶数: ", config%spatial_order + print *, " - 通量: ", trim(config%flux_type) + print *, " - 波速: ", config%wave_speed + + ! 这里调用验证函数(需要先实现) + ! is_valid = validate_config(config) + print *, "✓ 配置验证接口准备就绪" + print *, "" + end subroutine test_config_validation + + subroutine test_full_workflow() + print *, "4. 测试完整流程..." + print *, "-------------------" + + print *, "步骤1: 初始化系统 ✓" + print *, "步骤2: 创建网格 ✓" + print *, "步骤3: 设置配置 ✓" + print *, "步骤4: 创建组件 (待实现)" + print *, "步骤5: 初始化解 (待实现)" + print *, "步骤6: 时间推进 (待实现)" + print *, "步骤7: 输出结果 (待实现)" + print *, "" + + print *, "✓ 流程框架定义完成" + end subroutine test_full_workflow + +end program test_architecture \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/tests/test_component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/02c/tests/test_component_manager.f90 new file mode 100644 index 000000000..f60c35056 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/tests/test_component_manager.f90 @@ -0,0 +1,111 @@ +! tests/test_component_manager.f90 +program test_component_manager + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use component_manager_module, only: create_reconstructor, create_flux_calculator + use component_manager_module, only: component_manager_info, validate_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + implicit none + + type(cfd_config) :: config + class(reconstructor_base), allocatable :: recon + class(flux_calculator_base), allocatable :: flux + integer :: status + logical :: is_valid + + print *, "=== Component Manager Test ===" + print *, "" + + ! 显示组件管理器信息 + call component_manager_info() + print *, "" + + ! 测试1: 基本配置 + print *, "1. Testing basic ENO3 + Rusanov configuration..." + print *, "-----------------------------------------------" + + config%verbose = .true. + call config_print(config) + + ! 配置ENO3重构 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + call config_print(config) + print *, "" + + ! 验证配置 + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Configuration is valid" + else + print *, "[ERROR] Configuration is invalid" + end if + print *, "" + + ! 测试2: 创建组件 + print *, "2. Testing component creation..." + print *, "--------------------------------" + + ! 创建重构器(带状态检查) + recon = create_reconstructor(config, status) + if (status == 0) then + print *, "[OK] Reconstructor created successfully" + call recon%info() + else + print *, "[ERROR] Failed to create reconstructor, code:", status + end if + print *, "" + + ! 创建通量计算器 + flux = create_flux_calculator(config, status) + if (status == 0) then + print *, "[OK] Flux calculator created successfully" + call flux%info() + else + print *, "[ERROR] Failed to create flux calculator, code:", status + end if + print *, "" + + ! 测试3: WENO3重构测试 + print *, "3. Testing WENO3 configuration..." + print *, "---------------------------------" + + call config_with_reconstruction(config, "weno3", 3) + + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] WENO3 configuration is valid" + + ! 创建WENO3重构器 + recon = create_reconstructor(config) + call recon%info() + else + print *, "[ERROR] WENO3 configuration is invalid" + end if + print *, "" + + ! 测试4: 错误配置测试 + print *, "4. Testing invalid configuration..." + print *, "-----------------------------------" + + config%recon_scheme = "unknown_scheme" + config%flux_type = "unknown_flux" + + is_valid = validate_config(config) + if (.not. is_valid) then + print *, "[OK] Invalid configuration correctly rejected" + else + print *, "[ERROR] Invalid configuration should have been rejected" + end if + + ! 清理 + if (allocated(recon)) deallocate(recon) + if (allocated(flux)) deallocate(flux) + + print *, "" + print *, "=== Component manager test completed successfully ===" + +end program test_component_manager \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/tests/test_factory_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/02c/tests/test_factory_simple.f90 new file mode 100644 index 000000000..d4139bb1a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/tests/test_factory_simple.f90 @@ -0,0 +1,86 @@ +!tests/test_factory_simple.f90 +program test_factory_simple + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module, only: initialize_registry, cleanup_registry, & + register_component_simple, has_component + use config_module, only: cfd_config, config_print + use mesh_module, only: mesh_type + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(eno_reconstructor) :: eno + type(weno3_reconstructor) :: weno3 + type(rusanov_flux) :: rusanov + + print *, "=== Factory Pattern Simple Test ===" + print *, "" + + ! Test 1: Basic systems + print *, "1. Testing basic systems..." + print *, "-----------------------------" + call config_print(config) + + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 2: Creating reconstructors + print *, "2. Testing reconstructors..." + print *, "------------------------------" + + ! 创建并测试ENO重构器 + print *, "Creating ENO reconstructor..." + eno = eno_reconstructor() ! 使用构造函数 + call eno%info() ! 必须调用info方法 + + print *, "" + print *, "Creating WENO3 reconstructor..." + weno3 = weno3_reconstructor() ! 使用构造函数 + call weno3%info() ! 必须调用info方法 + print *, "" + + ! Test 3: Creating flux calculator + print *, "3. Testing flux calculator..." + print *, "-------------------------------" + + print *, "Creating Rusanov flux calculator..." + rusanov = rusanov_flux() ! 使用构造函数 + call rusanov%info() ! 必须调用info方法 + print *, "" + + ! Test 4: Registry integration + print *, "4. Testing registry..." + print *, "----------------------" + + call initialize_registry(verbose=.true.) + + ! Register components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("flux", "rusanov") + + ! Check registration + if (has_component("reconstructor", "eno")) then + print *, "[OK] ENO reconstructor registered successfully" + end if + + if (has_component("reconstructor", "weno3")) then + print *, "[OK] WENO3 reconstructor registered successfully" + end if + + if (has_component("flux", "rusanov")) then + print *, "[OK] Rusanov flux registered successfully" + end if + + print *, "" + call cleanup_registry() + + print *, "=== Factory pattern simple test completed successfully ===" + +end program test_factory_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/tests/test_minimal_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/02c/tests/test_minimal_simple.f90 new file mode 100644 index 000000000..cc6753b5f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/tests/test_minimal_simple.f90 @@ -0,0 +1,84 @@ +! tests/test_minimal_simple.f90 +program test_minimal_simple + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Simple Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_simple() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components (simple version) + print *, "5. Testing registry functionality" + print *, "---------------------------------" + print *, "Registry initialized: ", registry_is_initialized() + print *, "Number of components: ", registry_get_size() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Simple test completed successfully ===" + +end program test_minimal_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02c/tests/test_simple_link.f90 b/example/1d-linear-convection/weno3/fortran/registry/02c/tests/test_simple_link.f90 new file mode 100644 index 000000000..27b165b00 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02c/tests/test_simple_link.f90 @@ -0,0 +1,108 @@ +! tests/test_minimal.f90 +program test_minimal + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i ! Declare loop variable here + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_simple() + print *, "Registry size: ", component_registry%size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components + print *, "5. Testing available components" + print *, "--------------------------------" + + ! Use a separate block to avoid allocation issues + call test_available_components() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Minimal test completed successfully ===" + +contains + + ! Internal subroutine for testing available components + subroutine test_available_components() + character(len=:), allocatable :: names(:) + integer, allocatable :: orders(:) + integer :: j + + call get_available_components("reconstructor", names, orders) + + if (allocated(names)) then + print *, "Reconstructors:" + do j = 1, size(names) + print *, " - ", trim(names(j)) + if (allocated(orders)) then + print *, " Order: ", orders(j) + end if + end do + else + print *, "No reconstructors found" + end if + end subroutine test_available_components + +end program test_minimal \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02d/CMakeLists.txt new file mode 100644 index 000000000..ef66d5848 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/README.md b/example/1d-linear-convection/weno3/fortran/registry/02d/README.md new file mode 100644 index 000000000..c4889757b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/README.md @@ -0,0 +1,221 @@ +# Fortran CFD Project + +基于工厂模式和注册系统的CFD求解器框架。 + +## 项目结构 + +``` +fortran_cfd/ +├── CMakeLists.txt # 根CMake配置 +├── scripts/ # 构建脚本 +│ ├── build.py # Python构建脚本(推荐) +│ ├── build.bat # Batch包装脚本 +│ ├── build.ps1 # PowerShell包装脚本 +│ └── requirements.txt # Python依赖 +├── src/ # 源代码 +│ ├── CMakeLists.txt +│ ├── core/ # 核心模块(注册系统、工厂接口) +│ ├── infrastructure/ # 基础设施(配置、网格) +│ └── numerics/ # 数值方法 +├── tests/ # 测试 +│ ├── CMakeLists.txt +│ ├── test_minimal_simple.f90 # 简单测试 +│ └── test_factory.f90 # 工厂模式测试 +└── README.md # 项目说明(本文件) +``` + +## 快速开始 + +### 使用Python脚本(推荐) + +```bash +# 进入脚本目录 +cd scripts + +# 基本构建 +python build.py + +# 清理并构建 +python build.py --clean + +# 发布构建 +python build.py --build-type Release --clean + +# 并行构建(使用所有核心) +python build.py -j + +# 不运行测试 +python build.py --no-tests + +# 查看帮助 +python build.py --help +``` + +### 使用批处理脚本 + +```bash +cd scripts +.\build.bat +``` + +### 使用PowerShell脚本 + +```powershell +cd scripts +.\build.ps1 +``` + +## 手动构建 + +```bash +# 设置Intel环境 +cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && powershell' + +# 配置项目 +mkdir build +cd build +cmake -G "Visual Studio 17 2022" -A x64 -T fortran=ifx .. + +# 构建项目 +cmake --build . --config Debug + +# 运行测试 +Debug\test_simple.exe +Debug\test_factory.exe +``` + +## 功能特性 + +✅ 组件注册系统 +✅ 工厂模式 +✅ ENO/WENO重构器 +✅ Rusanov通量计算器 +✅ 可扩展架构 +✅ 自动化测试 + +## 依赖 + +- Intel oneAPI(包含ifx编译器) +- CMake 3.12+ +- Python 3.6+(仅用于构建脚本) + +## 源代码文件 + +### 核心模块 +- `src/core/registry.f90` - 组件注册系统 +- `src/core/factory_interfaces.f90` - 工厂接口 + +### 基础设施 +- `src/infrastructure/config.f90` - 配置管理 +- `src/infrastructure/mesh.f90` - 网格生成 + +### 数值方法 +- `src/numerics/reconstructor/base.f90` - 重构器基类 +- `src/numerics/reconstructor/eno.f90` - ENO重构器 +- `src/numerics/reconstructor/weno3.f90` - WENO-3重构器 +- `src/numerics/flux/base.f90` - 通量计算器基类 +- `src/numerics/flux/rusanov.f90` - Rusanov通量 + +### 测试 +- `tests/test_minimal_simple.f90` - 简单功能测试 +- `tests/test_factory.f90` - 工厂模式测试 + +## 构建脚本 + +### Python脚本 (build.py) +主构建脚本,支持: +- 自动设置Intel oneAPI环境 +- 参数化构建选项 +- 并行编译 +- 自动化测试 +- 彩色输出 + +### Batch脚本 (build.bat) +Windows批处理包装器,调用Python脚本。 + +### PowerShell脚本 (build.ps1) +PowerShell包装器,调用Python脚本。 + +## 示例输出 + +成功构建后,会看到类似输出: + +``` +============================================================ + Fortran CFD Project Builder +============================================================ + +[1/4] Setting up Intel Fortran compiler... +✓ Intel oneAPI environment configured + +[2/4] Preparing build directory... +✓ Cleaned build directory + +[3/4] Configuring project... + $ cmake -G Visual Studio 17 2022 -A x64 -T fortran=ifx -DCMAKE_BUILD_TYPE=Debug .. +✓ CMake configuration successful + +[4/4] Building project... + $ cmake --build . --config Debug +✓ Build completed in 12.3 seconds + +============================================================ + Running Tests +============================================================ + +[TEST] Simple functionality test... +✓ Simple test passed + +[TEST] Factory pattern test... +✓ Factory test passed + +============================================================ + Build Summary +============================================================ +✓ Build directory: D:\path\to\build +✓ Build type: Debug +✓ Total time: 15.2s +``` + +## 开发指南 + +### 添加新组件 + +1. 在相应模块中创建Fortran源文件 +2. 实现工厂函数 +3. 使用`register_component_with_factory`注册组件 +4. 添加测试 + +### 扩展架构 + +项目采用模块化设计: +1. **注册系统** - 管理所有可用的组件 +2. **工厂模式** - 动态创建组件实例 +3. **抽象接口** - 确保组件兼容性 +4. **配置驱动** - 通过配置文件控制行为 + +## 故障排除 + +### 常见问题 + +1. **Intel环境未找到** + - 检查Intel oneAPI安装路径 + - 手动运行`setvars.bat` + +2. **CMake配置失败** + - 确保使用正确的生成器:`Visual Studio 17 2022` + - 检查Fortran编译器是否可用 + +3. **构建失败** + - 检查依赖项是否完整 + - 查看详细的错误信息 + +### 调试建议 + +- 使用`--verbose`参数获取详细输出 +- 检查`build/CMakeCache.txt`了解配置详情 +- 查看编译器错误日志 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/scripts/build.bat b/example/1d-linear-convection/weno3/fortran/registry/02d/scripts/build.bat new file mode 100644 index 000000000..6fd6dc031 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/scripts/build.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Project Builder +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python build script with full Intel environment support... +echo. + +python build.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Build failed + pause + exit /b 1 +) + +echo. +echo [INFO] Build completed successfully! +echo. +echo [INFO] To run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/scripts/build.py b/example/1d-linear-convection/weno3/fortran/registry/02d/scripts/build.py new file mode 100644 index 000000000..3bf6d5375 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/scripts/build.py @@ -0,0 +1,629 @@ +#!/usr/bin/env python3 +""" +Fortran CFD Project Builder - 完整Python解决方案 +在Python内部处理Intel oneAPI环境配置 +""" + +import os +import sys +import subprocess +import shutil +import argparse +import time +import platform +import tempfile +from pathlib import Path + +class IntelEnvironment: + """Intel oneAPI环境管理器""" + + def __init__(self): + self.setvars_path = None + self.env_vars = {} + + def find_setvars(self): + """查找setvars.bat文件""" + possible_paths = [ + r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat", + r"C:\Program Files\Intel\oneAPI\setvars.bat", + r"C:\Program Files (x86)\Intel\oneAPI\compiler\latest\env\vars.bat", + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\setvars.bat"), + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\compiler\latest\env\vars.bat"), + ] + + for path in possible_paths: + if os.path.exists(path): + self.setvars_path = path + return True + + return False + + def setup_environment(self): + """设置Intel环境""" + if not self.find_setvars(): + return False + + try: + # 创建临时的批处理文件来捕获环境变量 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + f.write(f'@echo off\n') + f.write(f'call "{self.setvars_path}" >nul 2>&1\n') + f.write(f'set\n') # 输出所有环境变量 + temp_bat = f.name + + # 运行批处理文件并捕获输出 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + shell=True + ) + + # 解析环境变量 + for line in result.stdout.split('\n'): + line = line.strip() + if '=' in line: + key, value = line.split('=', 1) + self.env_vars[key.strip()] = value.strip() + + # 清理临时文件 + os.unlink(temp_bat) + + # 更新当前进程的环境变量 + os.environ.update(self.env_vars) + + return True + + except Exception as e: + print(f"设置Intel环境失败: {e}") + return False + + def get_compiler_info(self): + """获取编译器信息""" + info = {} + + # 检查ifx编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + env={**os.environ, **self.env_vars} if self.env_vars else os.environ + ) + + if result.returncode == 0: + for line in result.stdout.split('\n'): + if 'Version' in line or '版本' in line: + info['ifx_version'] = line.strip() + break + except: + pass + + # 检查环境变量 + info['ifx_root'] = self.env_vars.get('IFX_ROOT', '') + info['compiler_root'] = self.env_vars.get('ONEAPI_ROOT', '') + + return info + +class BuildSystem: + """构建系统主类""" + + def __init__(self): + self.project_root = Path(__file__).parent.parent + self.build_dir = self.project_root / "build" + self.intel_env = IntelEnvironment() + + # 设置控制台编码 + if sys.platform == "win32": + try: + import ctypes + # 设置控制台输出为UTF-8 + ctypes.windll.kernel32.SetConsoleOutputCP(65001) + except: + pass + + def print_header(self, text): + """打印标题""" + print(f"\n{'='*70}") + print(f" {text}") + print(f"{'='*70}\n") + + def print_step(self, step, total, message): + """打印步骤""" + print(f"[{step}/{total}] {message}...") + + def print_success(self, message): + """打印成功""" + print(f"\033[92m✓ {message}\033[0m") + + def print_error(self, message): + """打印错误""" + print(f"\033[91m✗ {message}\033[0m") + + def print_warning(self, message): + """打印警告""" + print(f"\033[93m! {message}\033[0m") + + def print_info(self, message): + """打印信息""" + print(f"\033[94mℹ {message}\033[0m") + + def check_prerequisites(self): + """检查前提条件""" + self.print_step(1, 6, "检查前提条件") + + # 检查Python版本 + python_version = sys.version.split()[0] + self.print_info(f"Python版本: {python_version}") + + # 检查平台 + self.print_info(f"平台: {platform.system()} {platform.release()}") + self.print_info(f"处理器核心数: {os.cpu_count()}") + + # 检查CMake + try: + result = subprocess.run( + ["cmake", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + version_line = result.stdout.split('\n')[0] + self.print_success(f"CMake: {version_line}") + else: + self.print_error("CMake未找到") + return False + except FileNotFoundError: + self.print_error("CMake未安装") + return False + + return True + + def setup_intel_environment(self, args): + """设置Intel环境""" + self.print_step(2, 6, "配置Intel oneAPI环境") + + if not self.intel_env.find_setvars(): + self.print_warning("未找到Intel oneAPI setvars.bat") + self.print_info("将尝试使用系统环境中的编译器") + + # 检查是否能直接访问编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + self.print_success("Intel编译器在系统PATH中找到") + return True + else: + self.print_warning("Intel编译器未在PATH中找到") + except: + self.print_warning("无法访问Intel编译器") + + return True # 继续,让CMake自己找编译器 + + # 设置环境 + if self.intel_env.setup_environment(): + compiler_info = self.intel_env.get_compiler_info() + + if compiler_info.get('ifx_version'): + self.print_success(f"Intel Fortran编译器: {compiler_info['ifx_version']}") + elif compiler_info.get('ifx_root'): + self.print_success(f"Intel编译器路径: {compiler_info['ifx_root']}") + else: + self.print_success("Intel oneAPI环境配置完成") + + return True + else: + self.print_warning("Intel环境配置失败,将继续使用系统环境") + return True + + def clean_build_directory(self, args): + """清理构建目录""" + if args.clean and self.build_dir.exists(): + self.print_info("清理构建目录...") + try: + shutil.rmtree(self.build_dir) + self.print_success("构建目录已清理") + except Exception as e: + self.print_error(f"清理失败: {e}") + if not args.force: + return False + return True + + def run_command(self, cmd, cwd=None, check=True, env=None): + """运行命令""" + if isinstance(cmd, list): + cmd_str = ' '.join(str(c) for c in cmd if c) + else: + cmd_str = str(cmd) + + print(f" \033[96m$\033[0m {cmd_str}") + + try: + # 合并环境变量 + exec_env = os.environ.copy() + if env: + exec_env.update(env) + if self.intel_env.env_vars: + exec_env.update(self.intel_env.env_vars) + + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=False, + env=exec_env + ) + + # 处理输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or '完成' in line or '生成' in line: + print(f" \033[92m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + if check and result.returncode != 0: + self.print_error(f"命令执行失败,退出码: {result.returncode}") + return False + + return True + + except Exception as e: + self.print_error(f"命令执行异常: {e}") + return False + + def configure_cmake(self, args): + """配置CMake""" + self.print_step(3, 6, "配置CMake项目") + + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + f"-DCMAKE_BUILD_TYPE={args.build_type}", + ] + + if args.compiler == "ifx": + cmake_cmd.extend(["-T", "fortran=ifx"]) + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + success = self.run_command(cmake_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("CMake配置完成") + else: + self.print_error("CMake配置失败") + + return success + + def build_project(self, args): + """构建项目""" + self.print_step(4, 6, "构建项目") + + build_cmd = [ + "cmake", + "--build", ".", + "--config", args.build_type, + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + success = self.run_command(build_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("项目构建完成") + else: + self.print_error("构建失败") + + return success + + def run_tests_with_environment(self, test_exe): + """运行单个测试,确保有Intel环境""" + try: + # 创建临时的批处理文件来运行测试 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'"{test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'"{test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + return result + + except Exception as e: + print(f"运行测试失败: {e}") + return None + + def run_tests(self, args): + """运行测试""" + self.print_step(5, 6, "运行测试") + + # 查找测试可执行文件 + test_dir = self.build_dir / "bin" / args.build_type + if not test_dir.exists(): + test_dir = self.build_dir / "bin" + if not test_dir.exists(): + test_dir = self.build_dir + + test_files = list(test_dir.glob("test_*.exe")) + + if not test_files: + self.print_warning("未找到测试程序") + return True + + all_passed = True + + for test_exe in sorted(test_files): + test_name = test_exe.stem + self.print_info(f"运行测试: {test_name}") + print(f" {'-'*50}") + + # 运行测试 + result = self.run_tests_with_environment(str(test_exe)) + + if result is None: + self.print_error(f" {test_name} 运行失败") + all_passed = False + continue + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + print(f" {line}") + + if result.returncode == 0: + self.print_success(f" {test_name} 通过") + else: + self.print_error(f" {test_name} 失败 (退出码: {result.returncode})") + all_passed = False + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + print() # 空行 + + return all_passed + + def create_test_runner(self, args): + """创建独立的测试运行器""" + self.print_step(6, 6, "创建测试运行器") + + runner_path = self.build_dir / "run_tests.bat" + + content = f'''@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Test Runner +echo ======================================== +echo. + +REM Setup Intel oneAPI environment +set "SETVARS_PATH={self.intel_env.setvars_path or ''}" +if exist "%SETVARS_PATH%" ( + call "%SETVARS_PATH%" >nul + echo [INFO] Intel environment configured +) else ( + echo [WARNING] Intel environment not found + echo [WARNING] Tests may fail without runtime libraries +) + +echo. + +REM Run all test executables +set "TEST_COUNT=0" +set "PASS_COUNT=0" + +for %%f in ("bin\\{args.build_type}\\test_*.exe") do ( + set /a TEST_COUNT+=1 + echo [TEST %%f] + echo {'-'*50} + + %%f + if errorlevel 1 ( + echo [FAILED] %%f + ) else ( + echo [PASSED] %%f + set /a PASS_COUNT+=1 + ) + echo. +) + +echo ======================================== +echo Tests: %PASS_COUNT%/%TEST_COUNT% passed +if %PASS_COUNT% equ %TEST_COUNT% ( + echo [SUCCESS] All tests passed! +) else ( + echo [FAILURE] Some tests failed +) +echo ======================================== + +pause +''' + + with open(runner_path, 'w', encoding='utf-8') as f: + f.write(content) + + self.print_success(f"测试运行器已创建: {runner_path}") + self.print_info(f"使用方法: cd build && run_tests.bat") + + return runner_path + + def generate_report(self, args, build_time, tests_passed): + """生成构建报告""" + self.print_header("构建完成") + + print(f"项目: {self.project_root.name}") + print(f"构建类型: {args.build_type}") + print(f"编译器: {args.compiler}") + print(f"并行作业: {args.jobs}") + print(f"总耗时: {build_time:.1f}秒") + print(f"测试结果: {'全部通过' if tests_passed else '有失败'}") + + # 显示生成的可执行文件 + bin_dir = self.build_dir / "bin" / args.build_type + if bin_dir.exists(): + print(f"\n生成的可执行文件:") + for exe in sorted(bin_dir.glob("*.exe")): + size_mb = exe.stat().st_size / (1024 * 1024) + print(f" • {exe.name} ({size_mb:.2f} MB)") + + # 显示测试运行器信息 + runner_path = self.build_dir / "run_tests.bat" + if runner_path.exists(): + print(f"\n独立测试运行器:") + print(f" • {runner_path.name}") + print(f" 在Intel oneAPI环境中运行所有测试") + + def run(self): + """运行构建系统""" + parser = argparse.ArgumentParser( + description="Fortran CFD项目构建工具", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认构建 + %(prog)s --clean # 清理后构建 + %(prog)s --build-type Release # Release构建 + %(prog)s --no-tests # 只构建,不运行测试 + %(prog)s -j8 --verbose # 8线程并行构建,详细输出 + """ + ) + + parser.add_argument("--build-type", choices=["Debug", "Release"], + default="Debug", help="构建类型") + parser.add_argument("--compiler", choices=["ifx", "ifort"], + default="ifx", help="Fortran编译器") + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-tests", action="store_true", + help="跳过测试") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + + args = parser.parse_args() + + # 开始构建 + start_time = time.time() + + self.print_header("Fortran CFD 项目构建系统") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 检查前提条件 + if not self.check_prerequisites(): + if not args.force: + return 1 + + # 2. 设置Intel环境 + if not self.setup_intel_environment(args): + if not args.force: + return 1 + + # 3. 清理目录 + if not self.clean_build_directory(args): + if not args.force: + return 1 + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 4. 配置CMake + if not self.configure_cmake(args): + if not args.force: + return 1 + + # 5. 构建项目 + if not self.build_project(args): + if not args.force: + return 1 + + # 6. 运行测试和创建测试运行器 + tests_passed = True + if not args.no_tests: + tests_passed = self.run_tests(args) + + # 创建测试运行器 + self.create_test_runner(args) # 传递 args 参数 + + # 7. 生成报告 + build_time = time.time() - start_time + self.generate_report(args, build_time, tests_passed) + + return 0 if tests_passed else 1 + + except KeyboardInterrupt: + self.print_error("\n构建被用户中断") + return 1 + except Exception as e: + self.print_error(f"构建过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + builder = BuildSystem() + return builder.run() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/scripts/requirements.txt b/example/1d-linear-convection/weno3/fortran/registry/02d/scripts/requirements.txt new file mode 100644 index 000000000..d21a12b45 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/scripts/requirements.txt @@ -0,0 +1,2 @@ +# 构建脚本的Python依赖(可选) +# 目前没有特殊依赖,保持空文件或添加未来可能需要的包 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02d/src/CMakeLists.txt new file mode 100644 index 000000000..d8cbd5f31 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/src/CMakeLists.txt @@ -0,0 +1,16 @@ +# ==================== src\CMakeLists.txt ==================== + +# src/CMakeLists.txt +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(base) +add_subdirectory(core) +add_subdirectory(infrastructure) +add_subdirectory(numerics) +add_subdirectory(manager) + +message(STATUS "源代码目录配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/src/base/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02d/src/base/CMakeLists.txt new file mode 100644 index 000000000..d537affd7 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/src/base/CMakeLists.txt @@ -0,0 +1,15 @@ +# src/base/CMakeLists.txt +message(STATUS "Configuring base module...") + +add_library(base STATIC + modules.f90 +) + +set_target_properties(base PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Base module configured") + +# src/core/CMakeLists.txt +message(STATUS "Configuring core module...") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/src/base/modules.f90 b/example/1d-linear-convection/weno3/fortran/registry/02d/src/base/modules.f90 new file mode 100644 index 000000000..99ef33372 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/src/base/modules.f90 @@ -0,0 +1,42 @@ +! src/base/modules.f90 +module base_modules + use, intrinsic :: iso_fortran_env, only: real64, int32 + + implicit none + + ! 公开所有基本类型 + public :: wp, ip, string_len, max_name_len + public :: cfd_config_base, component_info + + ! 工作精度类型 + integer, parameter :: wp = real64 + integer, parameter :: ip = int32 + + ! 字符串长度常量(使用数值常量) + integer, parameter :: string_len = 100 + integer, parameter :: max_name_len = 32 + + ! 基础配置类型 + type :: cfd_config_base + character(len=max_name_len) :: ic_type = "step" + character(len=max_name_len) :: recon_scheme = "eno" + character(len=max_name_len) :: flux_type = "rusanov" + integer(ip) :: rk_order = 1 + real(wp) :: wave_speed = 1.0_wp + real(wp) :: final_time = 0.625_wp + real(wp) :: dt = 0.025_wp + character(len=max_name_len) :: boundary_type = "periodic" + integer(ip) :: spatial_order = 2 + character(len=max_name_len) :: equation_type = "linear_advection" + character(len=max_name_len) :: problem_type = "linear_advection" + logical :: verbose = .true. + end type cfd_config_base + + ! 组件信息类型 + type :: component_info + character(len=max_name_len) :: category = "" + character(len=max_name_len) :: name = "" + integer(ip) :: order = 0 + end type component_info + +end module base_modules \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02d/src/core/CMakeLists.txt new file mode 100644 index 000000000..d8b8df064 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/src/core/CMakeLists.txt @@ -0,0 +1,14 @@ +# src/core/CMakeLists.txt +message(STATUS "Configuring core module...") + +add_library(core STATIC + registry.f90 +) + +target_link_libraries(core PRIVATE base) + +set_target_properties(core PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Core module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/src/core/factory_base.f90 b/example/1d-linear-convection/weno3/fortran/registry/02d/src/core/factory_base.f90 new file mode 100644 index 000000000..302418a15 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/src/core/factory_base.f90 @@ -0,0 +1,57 @@ +! src/core/factory_base.f90 +module factory_base_module + use base_modules, only: wp, ip + use registry_module, only: create_component, has_component + + implicit none + private + public :: wp, ip, factory_base, factory_create + + ! 工厂基类 + type :: factory_base + character(len=max_name_length) :: category = "" + contains + procedure :: create => factory_base_create + procedure :: get_available => factory_base_get_available + end type factory_base + + ! 便捷函数类型 + abstract interface + function factory_function_interface(category, name) result(instance) + import :: wp + character(len=*), intent(in) :: category, name + class(*), allocatable :: instance + end function factory_function_interface + end interface + +contains + + ! 创建工厂实例 + function factory_create(category) result(factory) + character(len=*), intent(in) :: category + type(factory_base) :: factory + factory%category = trim(category) + end function factory_create + + ! 工厂创建方法 + function factory_base_create(this, name) result(instance) + class(factory_base), intent(in) :: this + character(len=*), intent(in) :: name + class(*), allocatable :: instance + + instance = create_component(this%category, name) + end function factory_base_create + + ! 获取可用组件列表(简化版) + subroutine factory_base_get_available(this, names, count) + class(factory_base), intent(in) :: this + character(len=*), allocatable, intent(out) :: names(:) + integer(ip), intent(out) :: count + + ! 这里需要实现从注册表获取列表的逻辑 + ! 暂时返回空列表 + count = 0 + allocate(character(len=max_name_length) :: names(0)) + end subroutine factory_base_get_available + +end module factory_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/02d/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..a960167c3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/src/core/factory_interfaces.f90 @@ -0,0 +1,17 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, factory_procedure + + ! Factory procedure interface + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/02d/src/core/registry.f90 new file mode 100644 index 000000000..acc63edb4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/src/core/registry.f90 @@ -0,0 +1,203 @@ +! src/core/registry.f90 +module registry_module + use base_modules, only: wp, ip, max_name_len, component_info + + implicit none + private + + ! 明确公开所有需要的接口 + public :: wp, ip ! 类型参数 + public :: component_info ! 类型 + public :: registry_init, registry_cleanup ! 初始化/清理 + public :: register_component_simple ! 注册组件 + public :: has_component_simple ! 检查组件 + public :: list_components ! 列出组件 + public :: registry_is_initialized ! 检查初始化状态 ← 新增 + public :: registry_get_size ! 获取大小 ← 新增 + + ! 全局注册表 + type :: component_registry + type(component_info), allocatable :: components(:) + integer(ip) :: count = 0 + integer(ip) :: capacity = 100 + logical :: initialized = .false. + logical :: verbose = .true. + end type component_registry + + type(component_registry) :: registry + +contains + + ! ==================== 公共API ==================== + + subroutine registry_init(verbose) + logical, optional, intent(in) :: verbose + + if (registry%initialized) then + if (registry%verbose) then + print *, "[REGISTRY] Already initialized" + end if + return + end if + + if (present(verbose)) then + registry%verbose = verbose + end if + + allocate(registry%components(registry%capacity)) + registry%initialized = .true. + + if (registry%verbose) then + print *, "[REGISTRY] Initialized with capacity:", registry%capacity + end if + end subroutine registry_init + + subroutine registry_cleanup() + if (allocated(registry%components)) then + deallocate(registry%components) + end if + registry%initialized = .false. + registry%count = 0 + + if (registry%verbose) then + print *, "[REGISTRY] Cleaned up" + end if + end subroutine registry_cleanup + + subroutine register_component_simple(category, name, order) + character(len=*), intent(in) :: category, name + integer(ip), optional, intent(in) :: order + + integer(ip) :: i + type(component_info) :: info + + if (.not. registry%initialized) then + call registry_init() + end if + + ! 检查是否已存在 + do i = 1, registry%count + if (trim(registry%components(i)%category) == trim(category) .and. & + trim(registry%components(i)%name) == trim(name)) then + if (registry%verbose) then + print *, "[WARN] Overwriting component: ", trim(category), ".", trim(name) + end if + + ! 更新 + if (present(order)) then + registry%components(i)%order = order + else + registry%components(i)%order = 0 + end if + return + end if + end do + + ! 扩展数组 + if (registry%count >= registry%capacity) then + call expand_registry() + end if + + ! 添加新组件 + registry%count = registry%count + 1 + + info%category = trim(category) + info%name = trim(name) + info%order = 0 + if (present(order)) then + info%order = order + end if + + registry%components(registry%count) = info + + if (registry%verbose) then + print *, "[OK] Registered simple: ", trim(category), ".", trim(name) + end if + end subroutine register_component_simple + + logical function has_component_simple(category, name) + character(len=*), intent(in) :: category, name + + integer(ip) :: i + + has_component_simple = .false. + + if (.not. registry%initialized) return + + do i = 1, registry%count + if (trim(registry%components(i)%category) == trim(category) .and. & + trim(registry%components(i)%name) == trim(name)) then + has_component_simple = .true. + return + end if + end do + end function has_component_simple + + subroutine list_components(category) + character(len=*), optional, intent(in) :: category + + integer(ip) :: i, count + + if (.not. registry%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + if (registry%count == 0) then + print *, "[INFO] No components registered" + return + end if + + count = 0 + print *, "=== Registry Contents ===" + do i = 1, registry%count + if (.not. present(category) .or. & + trim(registry%components(i)%category) == trim(category)) then + call print_component_info(registry%components(i)) + count = count + 1 + end if + end do + + print *, "Total:", count, "components" + print *, "==========================" + end subroutine list_components + + ! ==================== 新增函数 ==================== + + logical function registry_is_initialized() + ! 检查注册表是否已初始化 + registry_is_initialized = registry%initialized + end function registry_is_initialized + + integer(ip) function registry_get_size() + ! 获取注册表中的组件数量 + registry_get_size = registry%count + end function registry_get_size + + ! ==================== 内部辅助函数 ==================== + + subroutine expand_registry() + type(component_info), allocatable :: temp(:) + + registry%capacity = registry%capacity * 2 + allocate(temp(registry%capacity)) + temp(1:registry%count) = registry%components(1:registry%count) + call move_alloc(temp, registry%components) + + if (registry%verbose) then + print *, "[INFO] Registry expanded to capacity:", registry%capacity + end if + end subroutine expand_registry + + subroutine print_component_info(info) + type(component_info), intent(in) :: info + + if (info%order > 0) then + print *, " [", trim(info%category), ".", trim(info%name), & + " (order:", info%order, ")]" + else + print *, " [", trim(info%category), ".", trim(info%name), "]" + end if + end subroutine print_component_info + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02d/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..c48396afb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/src/infrastructure/CMakeLists.txt @@ -0,0 +1,15 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "Configuring infrastructure module...") + +add_library(infrastructure STATIC + config.f90 + mesh.f90 +) + +target_link_libraries(infrastructure PRIVATE base) + +set_target_properties(infrastructure PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Infrastructure module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/02d/src/infrastructure/config.f90 new file mode 100644 index 000000000..719e14d25 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/src/infrastructure/config.f90 @@ -0,0 +1,64 @@ +! src/infrastructure/config.f90 +module config_module + use base_modules, only: wp, ip, max_name_len, cfd_config_base + + implicit none + public :: wp, ip, cfd_config, config_print, config_with_reconstruction + + ! 扩展配置类型 + type, extends(cfd_config_base) :: cfd_config + real(wp) :: left_boundary_value = 1.0_wp + real(wp) :: right_boundary_value = 2.0_wp + real(wp) :: domain_length = 2.0_wp + end type cfd_config + +contains + + subroutine config_print(cfg) + type(cfd_config), intent(in) :: cfg + + print *, "=== CFD Configuration ===" + print *, "Initial condition: ", trim(cfg%ic_type) + print *, "Reconstruction: ", trim(cfg%recon_scheme), " (order:", cfg%spatial_order, ")" + print *, "Flux type: ", trim(cfg%flux_type) + print *, "Time integration: RK", cfg%rk_order + print *, "Wave speed: ", cfg%wave_speed + print *, "Final time: ", cfg%final_time + print *, "Time step: ", cfg%dt + print *, "Boundary: ", trim(cfg%boundary_type) + print *, "===============================" + end subroutine config_print + + subroutine config_with_reconstruction(cfg, scheme, order) + type(cfd_config), intent(inout) :: cfg + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + integer :: i + + ! 转换为小写 + cfg%recon_scheme = scheme + do i = 1, len_trim(cfg%recon_scheme) + if (cfg%recon_scheme(i:i) >= 'A' .and. cfg%recon_scheme(i:i) <= 'Z') then + cfg%recon_scheme(i:i) = char(ichar(cfg%recon_scheme(i:i)) + 32) + end if + end do + + ! 设置阶数 + if (present(order)) then + cfg%spatial_order = order + else + if (index(cfg%recon_scheme, 'weno') > 0) then + cfg%spatial_order = 5 + else if (trim(cfg%recon_scheme) == 'eno') then + cfg%spatial_order = 3 + end if + end if + + if (cfg%verbose) then + print *, "[CONFIG] Reconstruction: ", trim(cfg%recon_scheme), & + " Order: ", cfg%spatial_order + end if + end subroutine config_with_reconstruction + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/src/infrastructure/domain.f90 b/example/1d-linear-convection/weno3/fortran/registry/02d/src/infrastructure/domain.f90 new file mode 100644 index 000000000..5a97c8a59 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/src/infrastructure/domain.f90 @@ -0,0 +1,80 @@ +! src/infrastructure/domain.f90 +module domain_module + use base_modules, only: wp, ip + use mesh_module, only: mesh_type + use config_module, only: cfd_config + + implicit none + private + public :: wp, ip, domain_type, domain_create, is_physical_cell + + type :: domain_type + type(cfd_config), pointer :: config => null() + type(mesh_type), pointer :: mesh => null() + integer(ip) :: nghosts = 0 + integer(ip) :: ist = 1 ! 1-based start index + integer(ip) :: ied = 1 ! 1-based end index (exclusive) + integer(ip) :: ntcells = 0 + contains + procedure :: print_info => domain_print_info + end type domain_type + +contains + + function domain_create(config, mesh) result(domain) + type(cfd_config), target, intent(in) :: config + type(mesh_type), target, intent(in) :: mesh + type(domain_type) :: domain + + domain%config => config + domain%mesh => mesh + + ! 计算ghost层数(参考Julia的_calc_nghosts) + domain%nghosts = calc_nghosts(config) + domain%ist = domain%nghosts + 1 + domain%ied = domain%ist + mesh%ncells - 1 + domain%ntcells = mesh%ncells + 2 * domain%nghosts + + if (config%verbose) then + print *, "[DOMAIN] Created with nghosts = ", domain%nghosts + print *, " Physical cells: ", domain%ist, " to ", domain%ied + print *, " Total cells: ", domain%ntcells + end if + end function domain_create + + function calc_nghosts(config) result(nghosts) + type(cfd_config), intent(in) :: config + integer(ip) :: nghosts + + character(len=max_name_length) :: scheme + + scheme = trim(config%recon_scheme) + + if (scheme == "eno") then + nghosts = config%spatial_order + else if (index(scheme, 'weno') > 0) then + nghosts = config%spatial_order / 2 + 1 + else + print *, "[ERROR] Unknown reconstruction scheme: ", trim(scheme) + nghosts = 2 ! 默认值 + end if + end function calc_nghosts + + logical function is_physical_cell(this, idx) + class(domain_type), intent(in) :: this + integer(ip), intent(in) :: idx + is_physical_cell = (idx >= this%ist .and. idx < this%ied) + end function is_physical_cell + + subroutine domain_print_info(this) + class(domain_type), intent(in) :: this + + print *, "=== Domain Information ===" + print *, "Ghost layers: ", this%nghosts + print *, "Physical cells: ", this%ist, " to ", this%ied - 1 + print *, "Total cells: ", this%ntcells + print *, "Mesh cells: ", this%mesh%ncells + print *, "==========================" + end subroutine domain_print_info + +end module domain_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/02d/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..f810f3a1b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/src/infrastructure/mesh.f90 @@ -0,0 +1,73 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use base_modules, only: wp, ip + + implicit none + public :: wp, ip, mesh_type, mesh_init, mesh_print_info + + ! 网格类型 + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer(ip) :: ncells = 40 + integer(ip) :: nnodes + integer(ip) :: nx + real(wp), allocatable :: x(:) ! 节点坐标 + real(wp), allocatable :: xcc(:) ! 单元中心坐标 + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer(ip), optional, intent(in) :: ncells + + integer(ip) :: i + + ! 设置参数 + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! 计算 + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! 分配内存 + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! 生成节点坐标 + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! 生成单元中心坐标 + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== Mesh Information ===" + print *, "Domain: [", this%xmin, ", ", this%xmax, "]" + print *, "Cells: ", this%ncells + print *, "Nodes: ", this%nnodes + print *, "dx: ", this%dx + print *, "L: ", this%L + print *, "========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/src/infrastructure/solution.f90 b/example/1d-linear-convection/weno3/fortran/registry/02d/src/infrastructure/solution.f90 new file mode 100644 index 000000000..5f9ed0878 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/src/infrastructure/solution.f90 @@ -0,0 +1,114 @@ +! src/infrastructure/solution.f90 +module solution_module + use base_modules, only: wp, ip + use domain_module, only: domain_type + + implicit none + private + public :: wp, ip, solution_type, solution_create, solution_reset + + type :: solution_type + type(domain_type), pointer :: domain => null() + real(wp), allocatable :: u(:) ! 当前解(包含ghost) + real(wp), allocatable :: un(:) ! 旧解 + real(wp), allocatable :: q_face_left(:) ! 左界面值 + real(wp), allocatable :: q_face_right(:)! 右界面值 + real(wp), allocatable :: flux(:) ! 通量 + real(wp), allocatable :: res(:) ! 残差 + contains + procedure :: initialize => solution_initialize + procedure :: update_old_field => solution_update_old_field + procedure :: print_info => solution_print_info + end type solution_type + +contains + + function solution_create(domain) result(solution) + type(domain_type), target, intent(in) :: domain + type(solution_type) :: solution + + integer(ip) :: ncells, nnodes, ntcells + + solution%domain => domain + + ncells = domain%mesh%ncells + nnodes = domain%mesh%nnodes + ntcells = domain%ntcells + + allocate(solution%u(ntcells), source=0.0_wp) + allocate(solution%un(ntcells), source=0.0_wp) + allocate(solution%q_face_left(nnodes), source=0.0_wp) + allocate(solution%q_face_right(nnodes), source=0.0_wp) + allocate(solution%flux(nnodes), source=0.0_wp) + allocate(solution%res(ncells), source=0.0_wp) + + if (domain%config%verbose) then + print *, "[SOLUTION] Created with arrays:" + print *, " u size: ", size(solution%u) + print *, " flux size: ", size(solution%flux) + print *, " res size: ", size(solution%res) + end if + end function solution_create + + subroutine solution_initialize(this, initial_values) + class(solution_type), intent(inout) :: this + real(wp), intent(in), optional :: initial_values(:) + + integer(ip) :: i, idx + type(domain_type), pointer :: domain + + domain => this%domain + + if (present(initial_values)) then + ! 应用初始值到物理区域 + do i = domain%ist, domain%ied - 1 + idx = i - domain%ist + 1 + if (idx <= size(initial_values)) then + this%u(i) = initial_values(idx) + end if + end do + else + ! 默认为0 + this%u = 0.0_wp + end if + + ! 同步旧场 + call this%update_old_field() + + if (domain%config%verbose) then + print *, "[SOLUTION] Initialized" + end if + end subroutine solution_initialize + + subroutine solution_update_old_field(this) + class(solution_type), intent(inout) :: this + this%un = this%u + end subroutine solution_update_old_field + + subroutine solution_reset(this) + class(solution_type), intent(inout) :: this + this%u = 0.0_wp + this%un = 0.0_wp + this%q_face_left = 0.0_wp + this%q_face_right = 0.0_wp + this%flux = 0.0_wp + this%res = 0.0_wp + end subroutine solution_reset + + subroutine solution_print_info(this) + class(solution_type), intent(in) :: this + + print *, "=== Solution Information ===" + print *, "u size: ", size(this%u) + print *, "un size: ", size(this%un) + print *, "q_face_left size: ", size(this%q_face_left) + print *, "q_face_right size: ", size(this%q_face_right) + print *, "flux size: ", size(this%flux) + print *, "res size: ", size(this%res) + if (allocated(this%u)) then + print *, "u range: ", minval(this%u), " to ", maxval(this%u) + end if + print *, "============================" + end subroutine solution_print_info + +end module solution_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/src/manager/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02d/src/manager/CMakeLists.txt new file mode 100644 index 000000000..00c8bf49b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/src/manager/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/manager/CMakeLists.txt +message(STATUS "配置管理器模块...") + +# 创建管理器库 +add_library(manager STATIC + component_manager.f90 + component_factory.f90 +) + +# 明确依赖关系:管理器依赖所有其他模块 +target_link_libraries(manager + PRIVATE + core + infrastructure + reconstructor + flux +) + +# 设置模块输出目录 +set_target_properties(manager PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "管理器模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/src/manager/component_factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/02d/src/manager/component_factory.f90 new file mode 100644 index 000000000..114fedea5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/src/manager/component_factory.f90 @@ -0,0 +1,127 @@ +! src/manager/component_factory.f90 +module component_factory_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use config_module, only: cfd_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + use eno_reconstructor_module, only: eno_reconstructor, create_eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor, create_weno3_reconstructor + use rusanov_flux_module, only: rusanov_flux, create_rusanov_flux + + implicit none + private + public :: wp, create_reconstructor, create_flux_calculator + + ! 错误代码 + integer, parameter :: CM_SUCCESS = 0 + integer, parameter :: CM_ERROR_UNKNOWN_SCHEME = 1 + integer, parameter :: CM_ERROR_UNKNOWN_FLUX = 2 + integer, parameter :: CM_ERROR_INVALID_ORDER = 3 + +contains + + ! ==================== 重构器创建 ==================== + + function create_reconstructor(config, status) result(recon) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(reconstructor_base), allocatable :: recon + + character(len=20) :: scheme + integer :: order, error_code + + scheme = trim(adjustl(config%recon_scheme)) + order = config%spatial_order + + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating reconstructor: ", scheme, " order=", order + end if + + select case(scheme) + case('eno') + allocate(eno_reconstructor :: recon) + select type(recon) + type is(eno_reconstructor) + recon = create_eno_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case('weno3') + allocate(weno3_reconstructor :: recon) + select type(recon) + type is(weno3_reconstructor) + recon = create_weno3_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case default + error_code = CM_ERROR_UNKNOWN_SCHEME + if (config%verbose) then + print *, "[ERROR] Unknown reconstructor scheme: ", scheme + print *, " Available: eno, weno3" + end if + end select + + ! 检查阶数有效性 + if (error_code == CM_SUCCESS) then + if (order < 1) then + error_code = CM_ERROR_INVALID_ORDER + if (config%verbose) then + print *, "[ERROR] Invalid spatial order: ", order + end if + end if + end if + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Reconstructor creation failed" + end if + end function create_reconstructor + + ! ==================== 通量计算器创建 ==================== + + function create_flux_calculator(config, status) result(flux) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(flux_calculator_base), allocatable :: flux + + character(len=20) :: flux_type + integer :: error_code + + flux_type = trim(adjustl(config%flux_type)) + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating flux calculator: ", flux_type + end if + + select case(flux_type) + case('rusanov') + allocate(rusanov_flux :: flux) + select type(flux) + type is(rusanov_flux) + flux = create_rusanov_flux() + flux%wave_speed_default = config%wave_speed + end select + + case default + error_code = CM_ERROR_UNKNOWN_FLUX + if (config%verbose) then + print *, "[ERROR] Unknown flux type: ", flux_type + print *, " Available: rusanov" + end if + end select + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Flux calculator creation failed" + end if + end function create_flux_calculator + +end module component_factory_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/src/manager/component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/02d/src/manager/component_manager.f90 new file mode 100644 index 000000000..9e095c257 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/src/manager/component_manager.f90 @@ -0,0 +1,75 @@ +! src/manager/component_manager.f90 +module component_manager_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use config_module, only: cfd_config + use component_factory_module, only: create_reconstructor, create_flux_calculator + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + + implicit none + private + public :: wp, component_manager_info, validate_config + public :: create_reconstructor, create_flux_calculator + +contains + + ! ==================== 配置验证 ==================== + + function validate_config(config) result(is_valid) + type(cfd_config), intent(in) :: config + logical :: is_valid + + integer :: status + class(reconstructor_base), allocatable :: test_recon + class(flux_calculator_base), allocatable :: test_flux + + is_valid = .false. + + ! 测试创建重构器 + test_recon = create_reconstructor(config, status) + if (status /= 0) then + if (config%verbose) then + print *, "[CONFIG VALIDATION] Invalid reconstructor configuration" + end if + return + end if + + ! 测试创建通量计算器 + test_flux = create_flux_calculator(config, status) + if (status /= 0) then + if (config%verbose) then + print *, "[CONFIG VALIDATION] Invalid flux configuration" + end if + return + end if + + ! 清理测试组件 + if (allocated(test_recon)) deallocate(test_recon) + if (allocated(test_flux)) deallocate(test_flux) + + is_valid = .true. + + if (config%verbose) then + print *, "[CONFIG VALIDATION] Configuration is valid" + end if + end function validate_config + + ! ==================== 信息显示 ==================== + + subroutine component_manager_info() + print *, "=== Component Manager ===" + print *, "Available reconstructors:" + print *, " - eno (orders: 1-7)" + print *, " - weno3 (order: 3)" + print *, "" + print *, "Available flux calculators:" + print *, " - rusanov" + print *, "" + print *, "Features:" + print *, " - Configuration validation" + print *, " - Component creation from config" + print *, " - Error handling with status codes" + print *, "=========================" + end subroutine component_manager_info + +end module component_manager_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..66874a7ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/CMakeLists.txt @@ -0,0 +1,7 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..3fde7746b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,28 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +# Start with just the base module +add_library(flux STATIC + base.f90 + rusanov.f90 +) + +#target_link_libraries(flux PRIVATE core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 明确设置不使用 /Qm64 选项 +if(CMAKE_Fortran_COMPILER_ID MATCHES "IntelLLVM|Intel") + set_target_properties(flux PROPERTIES + Fortran_FLAGS "${CMAKE_Fortran_FLAGS} /Qm64" # 明确设置,避免继承问题 + ) +endif() + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/flux/base.f90 new file mode 100644 index 000000000..7080a7ae4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/flux/base.f90 @@ -0,0 +1,30 @@ +!src/numerics/flux/base.f90 +module flux_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, flux_calculator_base + + ! Base flux calculator type + type :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure :: info => flux_info + procedure :: print_basic_info => flux_print_basic ! 添加辅助方法 + end type flux_calculator_base + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "Flux calculator information:" + print *, " Name: ", trim(this%name) + end subroutine flux_info + + subroutine flux_print_basic(this) + class(flux_calculator_base), intent(in) :: this + print *, " Name: ", trim(this%name) + end subroutine flux_print_basic + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/flux/rusanov.f90 b/example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/flux/rusanov.f90 new file mode 100644 index 000000000..daa9e3bb5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/flux/rusanov.f90 @@ -0,0 +1,41 @@ +! src/numerics/flux/rusanov.f90 +module rusanov_flux_module + use, intrinsic :: iso_fortran_env, only: real64 + use flux_base_module, only: flux_calculator_base + implicit none + + private + public :: real64, rusanov_flux, create_rusanov_flux + + type, extends(flux_calculator_base) :: rusanov_flux + real(real64) :: wave_speed_default = 1.0_real64 + contains + procedure :: info => rusanov_info + end type rusanov_flux + + ! 构造函数接口 + interface rusanov_flux + module procedure create_rusanov_flux + end interface + +contains + + ! 构造函数 + type(rusanov_flux) function create_rusanov_flux() result(this) + this%name = "Rusanov" + this%wave_speed_default = 1.0_real64 + end function create_rusanov_flux + + subroutine rusanov_info(this) + class(rusanov_flux), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Flux calculator information:" + call this%print_basic_info() + + ! 添加Rusanov特有信息 + print *, " Type: Rusanov flux" + print *, " Default wave speed: ", this%wave_speed_default + end subroutine rusanov_info + +end module rusanov_flux_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..5e4b938d5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,22 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "Configuring reconstructor module...") + +# Start with just the base module +add_library(reconstructor STATIC + base.f90 + eno.f90 + weno3.f90 +) + +target_link_libraries(reconstructor PRIVATE core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Reconstructor module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..53798d021 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/reconstructor/base.f90 @@ -0,0 +1,33 @@ +!src/numerics/reconstructor/base.f90 +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, reconstructor_base + + ! Base reconstructor type + type :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + contains + procedure :: info => reconstructor_info + procedure :: print_basic_info => reconstructor_print_basic ! 添加一个辅助方法 + end type reconstructor_base + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + print *, "Reconstructor information:" + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_info + + subroutine reconstructor_print_basic(this) + class(reconstructor_base), intent(in) :: this + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_print_basic + +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/reconstructor/eno.f90 b/example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/reconstructor/eno.f90 new file mode 100644 index 000000000..f973e8b32 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/reconstructor/eno.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/eno.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, eno_reconstructor, create_eno_reconstructor ! ← 添加这个 + + type, extends(reconstructor_base) :: eno_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => eno_info + end type eno_reconstructor + + ! 构造函数接口 + interface eno_reconstructor + module procedure create_eno_reconstructor + end interface + +contains + + ! 构造函数 + type(eno_reconstructor) function create_eno_reconstructor() result(this) + this%name = "ENO" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_eno_reconstructor + + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加ENO特有信息 + print *, " Type: ENO reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/reconstructor/weno3.f90 b/example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/reconstructor/weno3.f90 new file mode 100644 index 000000000..d5b7a7477 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/src/numerics/reconstructor/weno3.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/weno3.f90 +module weno3_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno3_reconstructor, create_weno3_reconstructor + + type, extends(reconstructor_base) :: weno3_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno3_info + end type weno3_reconstructor + + ! 构造函数接口 + interface weno3_reconstructor + module procedure create_weno3_reconstructor + end interface + +contains + + ! 构造函数 + type(weno3_reconstructor) function create_weno3_reconstructor() result(this) + this%name = "WENO3" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_weno3_reconstructor + + subroutine weno3_info(this) + class(weno3_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加WENO3特有信息 + print *, " Type: WENO-3 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno3_info + +end module weno3_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02d/tests/CMakeLists.txt new file mode 100644 index 000000000..9c8da3d58 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/tests/CMakeLists.txt @@ -0,0 +1,69 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +#add_executable(test_minimal_simple test_minimal_simple.f90) +# +#message(STATUS "CMAKE_Fortran_MODULE_DIRECTORY=${CMAKE_Fortran_MODULE_DIRECTORY}") +# +#target_link_libraries( test_minimal_simple +# PRIVATE +# infrastructure +#) +# +#add_executable(test_simple_link test_simple_link.f90) +#target_link_libraries(test_simple_link +# PRIVATE +# reconstructor +# flux +#) +# +# +#add_executable(test_factory_simple test_factory_simple.f90) +#target_link_libraries(test_factory_simple +# PRIVATE +# core +# infrastructure +# reconstructor +# flux +#) + +# 更新测试链接 +#add_executable(test_component_manager test_component_manager.f90) +# +#target_link_libraries(test_component_manager +# PRIVATE +# manager # ← 链接到新的管理器库 +# infrastructure +#) +# +#add_executable(test_architecture test_architecture.f90) +#target_link_libraries(test_architecture +# PRIVATE +# core +# infrastructure +# manager +#) + +#add_executable(test_solver_framework test_solver_framework.f90) +#target_link_libraries(test_solver_framework +# PRIVATE +# core +# infrastructure +# manager +#) +# +#add_executable(test_cfd_architecture test_cfd_architecture.f90) +#target_link_libraries(test_cfd_architecture +# PRIVATE +# base +# core +# infrastructure +#) + + +add_executable(test_basic_only test_basic_only.f90) +target_link_libraries(test_basic_only + PRIVATE + infrastructure + core +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_architecture.f90 b/example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_architecture.f90 new file mode 100644 index 000000000..1c40d4677 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_architecture.f90 @@ -0,0 +1,117 @@ +! tests/test_architecture.f90 +program test_architecture + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module + use config_module + use mesh_module + use component_manager_module + + implicit none + + print *, "=== CFD架构测试 ===" + print *, "" + + ! 测试1: 基本系统 + call test_basic_systems() + + ! 测试2: 组件注册 + call test_registry_integration() + + ! 测试3: 配置验证 + call test_config_validation() + + ! 测试4: 完整流程 + call test_full_workflow() + + print *, "" + print *, "=== 架构测试完成 ===" + +contains + + subroutine test_basic_systems() + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "1. 测试基本系统..." + print *, "-------------------" + + ! 测试配置 + call config_print(config) + print *, "✓ 配置创建成功" + + ! 测试网格 + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "✓ 网格创建成功" + print *, "" + end subroutine test_basic_systems + + subroutine test_registry_integration() + print *, "2. 测试注册系统集成..." + print *, "------------------------" + + call initialize_registry(verbose=.true.) + + ! 注册核心组件 + print *, "注册核心组件:" + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + ! 显示注册内容 + call component_registry%list_simple() + print *, "注册表大小: ", registry_get_size() + + call cleanup_registry() + print *, "✓ 注册系统测试完成" + print *, "" + end subroutine test_registry_integration + + subroutine test_config_validation() + type(cfd_config) :: config + logical :: is_valid + + print *, "3. 测试配置验证..." + print *, "-------------------" + + ! 测试有效配置 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + print *, "测试配置:" + print *, " - 重构器: ", trim(config%recon_scheme), " 阶数: ", config%spatial_order + print *, " - 通量: ", trim(config%flux_type) + print *, " - 波速: ", config%wave_speed + + ! 这里调用验证函数(需要先实现) + ! is_valid = validate_config(config) + print *, "✓ 配置验证接口准备就绪" + print *, "" + end subroutine test_config_validation + + subroutine test_full_workflow() + print *, "4. 测试完整流程..." + print *, "-------------------" + + print *, "步骤1: 初始化系统 ✓" + print *, "步骤2: 创建网格 ✓" + print *, "步骤3: 设置配置 ✓" + print *, "步骤4: 创建组件 (待实现)" + print *, "步骤5: 初始化解 (待实现)" + print *, "步骤6: 时间推进 (待实现)" + print *, "步骤7: 输出结果 (待实现)" + print *, "" + + print *, "✓ 流程框架定义完成" + end subroutine test_full_workflow + +end program test_architecture \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_basic_only.f90 b/example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_basic_only.f90 new file mode 100644 index 000000000..20901ddcc --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_basic_only.f90 @@ -0,0 +1,56 @@ +! tests/test_basic_only.f90 +program test_basic_only + ! 只测试最基本的功能,不依赖复杂模块 + use config_module, only: cfd_config, config_print, wp + use mesh_module, only: mesh_type + use registry_module, only: registry_init, registry_cleanup, & + register_component_simple, list_components + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "=== BASIC TEST - Minimal Functionality ===" + print *, "" + + ! 测试1: 配置 + print *, "1. Testing configuration..." + print *, "----------------------------" + call config_print(config) + print *, "" + + ! 测试2: 网格 + print *, "2. Testing mesh..." + print *, "------------------" + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=5) + print *, "Mesh initialized:" + print *, " Cells: ", mesh%ncells + print *, " Nodes: ", mesh%nnodes + print *, " dx: ", mesh%dx + print *, "" + + ! 测试3: 注册系统 + print *, "3. Testing registry..." + print *, "----------------------" + + call registry_init() + + ! 注册组件(使用简化版本) + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("flux", "rusanov") + + ! 列出组件 + call list_components() + print *, "" + + ! 清理 + call registry_cleanup() + + print *, "=== TEST PASSED ===" + print *, "✓ Configuration works" + print *, "✓ Mesh works" + print *, "✓ Registry works" + +end program test_basic_only \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_cfd_architecture.f90 b/example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_cfd_architecture.f90 new file mode 100644 index 000000000..1c40d4677 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_cfd_architecture.f90 @@ -0,0 +1,117 @@ +! tests/test_architecture.f90 +program test_architecture + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module + use config_module + use mesh_module + use component_manager_module + + implicit none + + print *, "=== CFD架构测试 ===" + print *, "" + + ! 测试1: 基本系统 + call test_basic_systems() + + ! 测试2: 组件注册 + call test_registry_integration() + + ! 测试3: 配置验证 + call test_config_validation() + + ! 测试4: 完整流程 + call test_full_workflow() + + print *, "" + print *, "=== 架构测试完成 ===" + +contains + + subroutine test_basic_systems() + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "1. 测试基本系统..." + print *, "-------------------" + + ! 测试配置 + call config_print(config) + print *, "✓ 配置创建成功" + + ! 测试网格 + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "✓ 网格创建成功" + print *, "" + end subroutine test_basic_systems + + subroutine test_registry_integration() + print *, "2. 测试注册系统集成..." + print *, "------------------------" + + call initialize_registry(verbose=.true.) + + ! 注册核心组件 + print *, "注册核心组件:" + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + ! 显示注册内容 + call component_registry%list_simple() + print *, "注册表大小: ", registry_get_size() + + call cleanup_registry() + print *, "✓ 注册系统测试完成" + print *, "" + end subroutine test_registry_integration + + subroutine test_config_validation() + type(cfd_config) :: config + logical :: is_valid + + print *, "3. 测试配置验证..." + print *, "-------------------" + + ! 测试有效配置 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + print *, "测试配置:" + print *, " - 重构器: ", trim(config%recon_scheme), " 阶数: ", config%spatial_order + print *, " - 通量: ", trim(config%flux_type) + print *, " - 波速: ", config%wave_speed + + ! 这里调用验证函数(需要先实现) + ! is_valid = validate_config(config) + print *, "✓ 配置验证接口准备就绪" + print *, "" + end subroutine test_config_validation + + subroutine test_full_workflow() + print *, "4. 测试完整流程..." + print *, "-------------------" + + print *, "步骤1: 初始化系统 ✓" + print *, "步骤2: 创建网格 ✓" + print *, "步骤3: 设置配置 ✓" + print *, "步骤4: 创建组件 (待实现)" + print *, "步骤5: 初始化解 (待实现)" + print *, "步骤6: 时间推进 (待实现)" + print *, "步骤7: 输出结果 (待实现)" + print *, "" + + print *, "✓ 流程框架定义完成" + end subroutine test_full_workflow + +end program test_architecture \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_component_manager.f90 new file mode 100644 index 000000000..f60c35056 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_component_manager.f90 @@ -0,0 +1,111 @@ +! tests/test_component_manager.f90 +program test_component_manager + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use component_manager_module, only: create_reconstructor, create_flux_calculator + use component_manager_module, only: component_manager_info, validate_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + implicit none + + type(cfd_config) :: config + class(reconstructor_base), allocatable :: recon + class(flux_calculator_base), allocatable :: flux + integer :: status + logical :: is_valid + + print *, "=== Component Manager Test ===" + print *, "" + + ! 显示组件管理器信息 + call component_manager_info() + print *, "" + + ! 测试1: 基本配置 + print *, "1. Testing basic ENO3 + Rusanov configuration..." + print *, "-----------------------------------------------" + + config%verbose = .true. + call config_print(config) + + ! 配置ENO3重构 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + call config_print(config) + print *, "" + + ! 验证配置 + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Configuration is valid" + else + print *, "[ERROR] Configuration is invalid" + end if + print *, "" + + ! 测试2: 创建组件 + print *, "2. Testing component creation..." + print *, "--------------------------------" + + ! 创建重构器(带状态检查) + recon = create_reconstructor(config, status) + if (status == 0) then + print *, "[OK] Reconstructor created successfully" + call recon%info() + else + print *, "[ERROR] Failed to create reconstructor, code:", status + end if + print *, "" + + ! 创建通量计算器 + flux = create_flux_calculator(config, status) + if (status == 0) then + print *, "[OK] Flux calculator created successfully" + call flux%info() + else + print *, "[ERROR] Failed to create flux calculator, code:", status + end if + print *, "" + + ! 测试3: WENO3重构测试 + print *, "3. Testing WENO3 configuration..." + print *, "---------------------------------" + + call config_with_reconstruction(config, "weno3", 3) + + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] WENO3 configuration is valid" + + ! 创建WENO3重构器 + recon = create_reconstructor(config) + call recon%info() + else + print *, "[ERROR] WENO3 configuration is invalid" + end if + print *, "" + + ! 测试4: 错误配置测试 + print *, "4. Testing invalid configuration..." + print *, "-----------------------------------" + + config%recon_scheme = "unknown_scheme" + config%flux_type = "unknown_flux" + + is_valid = validate_config(config) + if (.not. is_valid) then + print *, "[OK] Invalid configuration correctly rejected" + else + print *, "[ERROR] Invalid configuration should have been rejected" + end if + + ! 清理 + if (allocated(recon)) deallocate(recon) + if (allocated(flux)) deallocate(flux) + + print *, "" + print *, "=== Component manager test completed successfully ===" + +end program test_component_manager \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_factory_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_factory_simple.f90 new file mode 100644 index 000000000..d4139bb1a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_factory_simple.f90 @@ -0,0 +1,86 @@ +!tests/test_factory_simple.f90 +program test_factory_simple + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module, only: initialize_registry, cleanup_registry, & + register_component_simple, has_component + use config_module, only: cfd_config, config_print + use mesh_module, only: mesh_type + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(eno_reconstructor) :: eno + type(weno3_reconstructor) :: weno3 + type(rusanov_flux) :: rusanov + + print *, "=== Factory Pattern Simple Test ===" + print *, "" + + ! Test 1: Basic systems + print *, "1. Testing basic systems..." + print *, "-----------------------------" + call config_print(config) + + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 2: Creating reconstructors + print *, "2. Testing reconstructors..." + print *, "------------------------------" + + ! 创建并测试ENO重构器 + print *, "Creating ENO reconstructor..." + eno = eno_reconstructor() ! 使用构造函数 + call eno%info() ! 必须调用info方法 + + print *, "" + print *, "Creating WENO3 reconstructor..." + weno3 = weno3_reconstructor() ! 使用构造函数 + call weno3%info() ! 必须调用info方法 + print *, "" + + ! Test 3: Creating flux calculator + print *, "3. Testing flux calculator..." + print *, "-------------------------------" + + print *, "Creating Rusanov flux calculator..." + rusanov = rusanov_flux() ! 使用构造函数 + call rusanov%info() ! 必须调用info方法 + print *, "" + + ! Test 4: Registry integration + print *, "4. Testing registry..." + print *, "----------------------" + + call initialize_registry(verbose=.true.) + + ! Register components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("flux", "rusanov") + + ! Check registration + if (has_component("reconstructor", "eno")) then + print *, "[OK] ENO reconstructor registered successfully" + end if + + if (has_component("reconstructor", "weno3")) then + print *, "[OK] WENO3 reconstructor registered successfully" + end if + + if (has_component("flux", "rusanov")) then + print *, "[OK] Rusanov flux registered successfully" + end if + + print *, "" + call cleanup_registry() + + print *, "=== Factory pattern simple test completed successfully ===" + +end program test_factory_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_minimal_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_minimal_simple.f90 new file mode 100644 index 000000000..cc6753b5f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_minimal_simple.f90 @@ -0,0 +1,84 @@ +! tests/test_minimal_simple.f90 +program test_minimal_simple + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Simple Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_simple() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components (simple version) + print *, "5. Testing registry functionality" + print *, "---------------------------------" + print *, "Registry initialized: ", registry_is_initialized() + print *, "Number of components: ", registry_get_size() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Simple test completed successfully ===" + +end program test_minimal_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_simple_link.f90 b/example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_simple_link.f90 new file mode 100644 index 000000000..27b165b00 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_simple_link.f90 @@ -0,0 +1,108 @@ +! tests/test_minimal.f90 +program test_minimal + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i ! Declare loop variable here + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call initialize_registry(verbose=.true.) + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call component_registry%list_simple() + print *, "Registry size: ", component_registry%size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components + print *, "5. Testing available components" + print *, "--------------------------------" + + ! Use a separate block to avoid allocation issues + call test_available_components() + print *, "" + + ! Cleanup + call cleanup_registry() + + print *, "=== Minimal test completed successfully ===" + +contains + + ! Internal subroutine for testing available components + subroutine test_available_components() + character(len=:), allocatable :: names(:) + integer, allocatable :: orders(:) + integer :: j + + call get_available_components("reconstructor", names, orders) + + if (allocated(names)) then + print *, "Reconstructors:" + do j = 1, size(names) + print *, " - ", trim(names(j)) + if (allocated(orders)) then + print *, " Order: ", orders(j) + end if + end do + else + print *, "No reconstructors found" + end if + end subroutine test_available_components + +end program test_minimal \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_solver_framework.f90 b/example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_solver_framework.f90 new file mode 100644 index 000000000..6754323d0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02d/tests/test_solver_framework.f90 @@ -0,0 +1,91 @@ +! tests/test_solver_framework.f90 +program test_solver_framework + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use mesh_module, only: mesh_type + use solver_module, only: cfd_solver, solver_create, solver_run, solver_cleanup + use solver_module, only: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, & + SOLVER_COMPLETED, SOLVER_ERROR + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(cfd_solver) :: solver + + print *, "=== 求解器框架测试 ===" + print *, "" + + ! 测试1: 基本创建 + print *, "1. 测试求解器创建..." + print *, "----------------------" + + ! 创建配置 + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.0_real64 + config%dt = 0.01_real64 + + call config_print(config) + print *, "" + + ! 创建网格 + call mesh%init(xmin=0.0_real64, xmax=2.0_real64, ncells=20) + call mesh%print_info() + print *, "" + + ! 创建求解器 + solver = solver_create(config, mesh) + print *, "✓ 求解器创建成功" + print *, " 状态: ", solver%get_state() + print *, "" + + ! 测试2: 求解器初始化 + print *, "2. 测试求解器初始化..." + print *, "------------------------" + + call solver%initialize() + print *, "✓ 求解器初始化完成" + print *, " 状态: ", solver%get_state() + print *, " 错误信息: '", trim(solver%get_error()), "'" + print *, "" + + ! 测试3: 简单运行 + print *, "3. 测试求解器运行..." + print *, "----------------------" + + call solver_run(solver, 0.05_real64) ! 运行到0.05秒 + print *, "✓ 求解器运行完成" + print *, " 最终状态: ", solver%get_state() + print *, "" + + ! 测试4: 清理 + print *, "4. 测试求解器清理..." + print *, "----------------------" + + call solver_cleanup(solver) + print *, "✓ 求解器清理完成" + print *, " 状态: ", solver%get_state() + print *, "" + + ! 测试5: 错误处理 + print *, "5. 测试错误处理..." + print *, "-------------------" + + ! 尝试重复初始化 + call solver%initialize() + print *, " 重复初始化状态: ", solver%get_state() + print *, " 错误信息: '", trim(solver%get_error()), "'" + + call solver_cleanup(solver) + print *, "" + + print *, "=== 框架测试总结 ===" + print *, "✓ 求解器创建/初始化/运行/清理流程验证完成" + print *, "✓ 状态管理正常工作" + print *, "✓ 错误处理机制就绪" + print *, "" + print *, "下一步: 添加实际数值计算功能" + +end program test_solver_framework \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02e/CMakeLists.txt new file mode 100644 index 000000000..ef66d5848 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/README.md b/example/1d-linear-convection/weno3/fortran/registry/02e/README.md new file mode 100644 index 000000000..c4889757b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/README.md @@ -0,0 +1,221 @@ +# Fortran CFD Project + +基于工厂模式和注册系统的CFD求解器框架。 + +## 项目结构 + +``` +fortran_cfd/ +├── CMakeLists.txt # 根CMake配置 +├── scripts/ # 构建脚本 +│ ├── build.py # Python构建脚本(推荐) +│ ├── build.bat # Batch包装脚本 +│ ├── build.ps1 # PowerShell包装脚本 +│ └── requirements.txt # Python依赖 +├── src/ # 源代码 +│ ├── CMakeLists.txt +│ ├── core/ # 核心模块(注册系统、工厂接口) +│ ├── infrastructure/ # 基础设施(配置、网格) +│ └── numerics/ # 数值方法 +├── tests/ # 测试 +│ ├── CMakeLists.txt +│ ├── test_minimal_simple.f90 # 简单测试 +│ └── test_factory.f90 # 工厂模式测试 +└── README.md # 项目说明(本文件) +``` + +## 快速开始 + +### 使用Python脚本(推荐) + +```bash +# 进入脚本目录 +cd scripts + +# 基本构建 +python build.py + +# 清理并构建 +python build.py --clean + +# 发布构建 +python build.py --build-type Release --clean + +# 并行构建(使用所有核心) +python build.py -j + +# 不运行测试 +python build.py --no-tests + +# 查看帮助 +python build.py --help +``` + +### 使用批处理脚本 + +```bash +cd scripts +.\build.bat +``` + +### 使用PowerShell脚本 + +```powershell +cd scripts +.\build.ps1 +``` + +## 手动构建 + +```bash +# 设置Intel环境 +cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && powershell' + +# 配置项目 +mkdir build +cd build +cmake -G "Visual Studio 17 2022" -A x64 -T fortran=ifx .. + +# 构建项目 +cmake --build . --config Debug + +# 运行测试 +Debug\test_simple.exe +Debug\test_factory.exe +``` + +## 功能特性 + +✅ 组件注册系统 +✅ 工厂模式 +✅ ENO/WENO重构器 +✅ Rusanov通量计算器 +✅ 可扩展架构 +✅ 自动化测试 + +## 依赖 + +- Intel oneAPI(包含ifx编译器) +- CMake 3.12+ +- Python 3.6+(仅用于构建脚本) + +## 源代码文件 + +### 核心模块 +- `src/core/registry.f90` - 组件注册系统 +- `src/core/factory_interfaces.f90` - 工厂接口 + +### 基础设施 +- `src/infrastructure/config.f90` - 配置管理 +- `src/infrastructure/mesh.f90` - 网格生成 + +### 数值方法 +- `src/numerics/reconstructor/base.f90` - 重构器基类 +- `src/numerics/reconstructor/eno.f90` - ENO重构器 +- `src/numerics/reconstructor/weno3.f90` - WENO-3重构器 +- `src/numerics/flux/base.f90` - 通量计算器基类 +- `src/numerics/flux/rusanov.f90` - Rusanov通量 + +### 测试 +- `tests/test_minimal_simple.f90` - 简单功能测试 +- `tests/test_factory.f90` - 工厂模式测试 + +## 构建脚本 + +### Python脚本 (build.py) +主构建脚本,支持: +- 自动设置Intel oneAPI环境 +- 参数化构建选项 +- 并行编译 +- 自动化测试 +- 彩色输出 + +### Batch脚本 (build.bat) +Windows批处理包装器,调用Python脚本。 + +### PowerShell脚本 (build.ps1) +PowerShell包装器,调用Python脚本。 + +## 示例输出 + +成功构建后,会看到类似输出: + +``` +============================================================ + Fortran CFD Project Builder +============================================================ + +[1/4] Setting up Intel Fortran compiler... +✓ Intel oneAPI environment configured + +[2/4] Preparing build directory... +✓ Cleaned build directory + +[3/4] Configuring project... + $ cmake -G Visual Studio 17 2022 -A x64 -T fortran=ifx -DCMAKE_BUILD_TYPE=Debug .. +✓ CMake configuration successful + +[4/4] Building project... + $ cmake --build . --config Debug +✓ Build completed in 12.3 seconds + +============================================================ + Running Tests +============================================================ + +[TEST] Simple functionality test... +✓ Simple test passed + +[TEST] Factory pattern test... +✓ Factory test passed + +============================================================ + Build Summary +============================================================ +✓ Build directory: D:\path\to\build +✓ Build type: Debug +✓ Total time: 15.2s +``` + +## 开发指南 + +### 添加新组件 + +1. 在相应模块中创建Fortran源文件 +2. 实现工厂函数 +3. 使用`register_component_with_factory`注册组件 +4. 添加测试 + +### 扩展架构 + +项目采用模块化设计: +1. **注册系统** - 管理所有可用的组件 +2. **工厂模式** - 动态创建组件实例 +3. **抽象接口** - 确保组件兼容性 +4. **配置驱动** - 通过配置文件控制行为 + +## 故障排除 + +### 常见问题 + +1. **Intel环境未找到** + - 检查Intel oneAPI安装路径 + - 手动运行`setvars.bat` + +2. **CMake配置失败** + - 确保使用正确的生成器:`Visual Studio 17 2022` + - 检查Fortran编译器是否可用 + +3. **构建失败** + - 检查依赖项是否完整 + - 查看详细的错误信息 + +### 调试建议 + +- 使用`--verbose`参数获取详细输出 +- 检查`build/CMakeCache.txt`了解配置详情 +- 查看编译器错误日志 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/scripts/build.bat b/example/1d-linear-convection/weno3/fortran/registry/02e/scripts/build.bat new file mode 100644 index 000000000..6fd6dc031 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/scripts/build.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Project Builder +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python build script with full Intel environment support... +echo. + +python build.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Build failed + pause + exit /b 1 +) + +echo. +echo [INFO] Build completed successfully! +echo. +echo [INFO] To run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/scripts/build.py b/example/1d-linear-convection/weno3/fortran/registry/02e/scripts/build.py new file mode 100644 index 000000000..3bf6d5375 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/scripts/build.py @@ -0,0 +1,629 @@ +#!/usr/bin/env python3 +""" +Fortran CFD Project Builder - 完整Python解决方案 +在Python内部处理Intel oneAPI环境配置 +""" + +import os +import sys +import subprocess +import shutil +import argparse +import time +import platform +import tempfile +from pathlib import Path + +class IntelEnvironment: + """Intel oneAPI环境管理器""" + + def __init__(self): + self.setvars_path = None + self.env_vars = {} + + def find_setvars(self): + """查找setvars.bat文件""" + possible_paths = [ + r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat", + r"C:\Program Files\Intel\oneAPI\setvars.bat", + r"C:\Program Files (x86)\Intel\oneAPI\compiler\latest\env\vars.bat", + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\setvars.bat"), + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\compiler\latest\env\vars.bat"), + ] + + for path in possible_paths: + if os.path.exists(path): + self.setvars_path = path + return True + + return False + + def setup_environment(self): + """设置Intel环境""" + if not self.find_setvars(): + return False + + try: + # 创建临时的批处理文件来捕获环境变量 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + f.write(f'@echo off\n') + f.write(f'call "{self.setvars_path}" >nul 2>&1\n') + f.write(f'set\n') # 输出所有环境变量 + temp_bat = f.name + + # 运行批处理文件并捕获输出 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + shell=True + ) + + # 解析环境变量 + for line in result.stdout.split('\n'): + line = line.strip() + if '=' in line: + key, value = line.split('=', 1) + self.env_vars[key.strip()] = value.strip() + + # 清理临时文件 + os.unlink(temp_bat) + + # 更新当前进程的环境变量 + os.environ.update(self.env_vars) + + return True + + except Exception as e: + print(f"设置Intel环境失败: {e}") + return False + + def get_compiler_info(self): + """获取编译器信息""" + info = {} + + # 检查ifx编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + env={**os.environ, **self.env_vars} if self.env_vars else os.environ + ) + + if result.returncode == 0: + for line in result.stdout.split('\n'): + if 'Version' in line or '版本' in line: + info['ifx_version'] = line.strip() + break + except: + pass + + # 检查环境变量 + info['ifx_root'] = self.env_vars.get('IFX_ROOT', '') + info['compiler_root'] = self.env_vars.get('ONEAPI_ROOT', '') + + return info + +class BuildSystem: + """构建系统主类""" + + def __init__(self): + self.project_root = Path(__file__).parent.parent + self.build_dir = self.project_root / "build" + self.intel_env = IntelEnvironment() + + # 设置控制台编码 + if sys.platform == "win32": + try: + import ctypes + # 设置控制台输出为UTF-8 + ctypes.windll.kernel32.SetConsoleOutputCP(65001) + except: + pass + + def print_header(self, text): + """打印标题""" + print(f"\n{'='*70}") + print(f" {text}") + print(f"{'='*70}\n") + + def print_step(self, step, total, message): + """打印步骤""" + print(f"[{step}/{total}] {message}...") + + def print_success(self, message): + """打印成功""" + print(f"\033[92m✓ {message}\033[0m") + + def print_error(self, message): + """打印错误""" + print(f"\033[91m✗ {message}\033[0m") + + def print_warning(self, message): + """打印警告""" + print(f"\033[93m! {message}\033[0m") + + def print_info(self, message): + """打印信息""" + print(f"\033[94mℹ {message}\033[0m") + + def check_prerequisites(self): + """检查前提条件""" + self.print_step(1, 6, "检查前提条件") + + # 检查Python版本 + python_version = sys.version.split()[0] + self.print_info(f"Python版本: {python_version}") + + # 检查平台 + self.print_info(f"平台: {platform.system()} {platform.release()}") + self.print_info(f"处理器核心数: {os.cpu_count()}") + + # 检查CMake + try: + result = subprocess.run( + ["cmake", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + version_line = result.stdout.split('\n')[0] + self.print_success(f"CMake: {version_line}") + else: + self.print_error("CMake未找到") + return False + except FileNotFoundError: + self.print_error("CMake未安装") + return False + + return True + + def setup_intel_environment(self, args): + """设置Intel环境""" + self.print_step(2, 6, "配置Intel oneAPI环境") + + if not self.intel_env.find_setvars(): + self.print_warning("未找到Intel oneAPI setvars.bat") + self.print_info("将尝试使用系统环境中的编译器") + + # 检查是否能直接访问编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + self.print_success("Intel编译器在系统PATH中找到") + return True + else: + self.print_warning("Intel编译器未在PATH中找到") + except: + self.print_warning("无法访问Intel编译器") + + return True # 继续,让CMake自己找编译器 + + # 设置环境 + if self.intel_env.setup_environment(): + compiler_info = self.intel_env.get_compiler_info() + + if compiler_info.get('ifx_version'): + self.print_success(f"Intel Fortran编译器: {compiler_info['ifx_version']}") + elif compiler_info.get('ifx_root'): + self.print_success(f"Intel编译器路径: {compiler_info['ifx_root']}") + else: + self.print_success("Intel oneAPI环境配置完成") + + return True + else: + self.print_warning("Intel环境配置失败,将继续使用系统环境") + return True + + def clean_build_directory(self, args): + """清理构建目录""" + if args.clean and self.build_dir.exists(): + self.print_info("清理构建目录...") + try: + shutil.rmtree(self.build_dir) + self.print_success("构建目录已清理") + except Exception as e: + self.print_error(f"清理失败: {e}") + if not args.force: + return False + return True + + def run_command(self, cmd, cwd=None, check=True, env=None): + """运行命令""" + if isinstance(cmd, list): + cmd_str = ' '.join(str(c) for c in cmd if c) + else: + cmd_str = str(cmd) + + print(f" \033[96m$\033[0m {cmd_str}") + + try: + # 合并环境变量 + exec_env = os.environ.copy() + if env: + exec_env.update(env) + if self.intel_env.env_vars: + exec_env.update(self.intel_env.env_vars) + + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=False, + env=exec_env + ) + + # 处理输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or '完成' in line or '生成' in line: + print(f" \033[92m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + if check and result.returncode != 0: + self.print_error(f"命令执行失败,退出码: {result.returncode}") + return False + + return True + + except Exception as e: + self.print_error(f"命令执行异常: {e}") + return False + + def configure_cmake(self, args): + """配置CMake""" + self.print_step(3, 6, "配置CMake项目") + + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + f"-DCMAKE_BUILD_TYPE={args.build_type}", + ] + + if args.compiler == "ifx": + cmake_cmd.extend(["-T", "fortran=ifx"]) + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + success = self.run_command(cmake_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("CMake配置完成") + else: + self.print_error("CMake配置失败") + + return success + + def build_project(self, args): + """构建项目""" + self.print_step(4, 6, "构建项目") + + build_cmd = [ + "cmake", + "--build", ".", + "--config", args.build_type, + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + success = self.run_command(build_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("项目构建完成") + else: + self.print_error("构建失败") + + return success + + def run_tests_with_environment(self, test_exe): + """运行单个测试,确保有Intel环境""" + try: + # 创建临时的批处理文件来运行测试 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'"{test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'"{test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + return result + + except Exception as e: + print(f"运行测试失败: {e}") + return None + + def run_tests(self, args): + """运行测试""" + self.print_step(5, 6, "运行测试") + + # 查找测试可执行文件 + test_dir = self.build_dir / "bin" / args.build_type + if not test_dir.exists(): + test_dir = self.build_dir / "bin" + if not test_dir.exists(): + test_dir = self.build_dir + + test_files = list(test_dir.glob("test_*.exe")) + + if not test_files: + self.print_warning("未找到测试程序") + return True + + all_passed = True + + for test_exe in sorted(test_files): + test_name = test_exe.stem + self.print_info(f"运行测试: {test_name}") + print(f" {'-'*50}") + + # 运行测试 + result = self.run_tests_with_environment(str(test_exe)) + + if result is None: + self.print_error(f" {test_name} 运行失败") + all_passed = False + continue + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + print(f" {line}") + + if result.returncode == 0: + self.print_success(f" {test_name} 通过") + else: + self.print_error(f" {test_name} 失败 (退出码: {result.returncode})") + all_passed = False + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + print() # 空行 + + return all_passed + + def create_test_runner(self, args): + """创建独立的测试运行器""" + self.print_step(6, 6, "创建测试运行器") + + runner_path = self.build_dir / "run_tests.bat" + + content = f'''@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Test Runner +echo ======================================== +echo. + +REM Setup Intel oneAPI environment +set "SETVARS_PATH={self.intel_env.setvars_path or ''}" +if exist "%SETVARS_PATH%" ( + call "%SETVARS_PATH%" >nul + echo [INFO] Intel environment configured +) else ( + echo [WARNING] Intel environment not found + echo [WARNING] Tests may fail without runtime libraries +) + +echo. + +REM Run all test executables +set "TEST_COUNT=0" +set "PASS_COUNT=0" + +for %%f in ("bin\\{args.build_type}\\test_*.exe") do ( + set /a TEST_COUNT+=1 + echo [TEST %%f] + echo {'-'*50} + + %%f + if errorlevel 1 ( + echo [FAILED] %%f + ) else ( + echo [PASSED] %%f + set /a PASS_COUNT+=1 + ) + echo. +) + +echo ======================================== +echo Tests: %PASS_COUNT%/%TEST_COUNT% passed +if %PASS_COUNT% equ %TEST_COUNT% ( + echo [SUCCESS] All tests passed! +) else ( + echo [FAILURE] Some tests failed +) +echo ======================================== + +pause +''' + + with open(runner_path, 'w', encoding='utf-8') as f: + f.write(content) + + self.print_success(f"测试运行器已创建: {runner_path}") + self.print_info(f"使用方法: cd build && run_tests.bat") + + return runner_path + + def generate_report(self, args, build_time, tests_passed): + """生成构建报告""" + self.print_header("构建完成") + + print(f"项目: {self.project_root.name}") + print(f"构建类型: {args.build_type}") + print(f"编译器: {args.compiler}") + print(f"并行作业: {args.jobs}") + print(f"总耗时: {build_time:.1f}秒") + print(f"测试结果: {'全部通过' if tests_passed else '有失败'}") + + # 显示生成的可执行文件 + bin_dir = self.build_dir / "bin" / args.build_type + if bin_dir.exists(): + print(f"\n生成的可执行文件:") + for exe in sorted(bin_dir.glob("*.exe")): + size_mb = exe.stat().st_size / (1024 * 1024) + print(f" • {exe.name} ({size_mb:.2f} MB)") + + # 显示测试运行器信息 + runner_path = self.build_dir / "run_tests.bat" + if runner_path.exists(): + print(f"\n独立测试运行器:") + print(f" • {runner_path.name}") + print(f" 在Intel oneAPI环境中运行所有测试") + + def run(self): + """运行构建系统""" + parser = argparse.ArgumentParser( + description="Fortran CFD项目构建工具", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认构建 + %(prog)s --clean # 清理后构建 + %(prog)s --build-type Release # Release构建 + %(prog)s --no-tests # 只构建,不运行测试 + %(prog)s -j8 --verbose # 8线程并行构建,详细输出 + """ + ) + + parser.add_argument("--build-type", choices=["Debug", "Release"], + default="Debug", help="构建类型") + parser.add_argument("--compiler", choices=["ifx", "ifort"], + default="ifx", help="Fortran编译器") + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-tests", action="store_true", + help="跳过测试") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + + args = parser.parse_args() + + # 开始构建 + start_time = time.time() + + self.print_header("Fortran CFD 项目构建系统") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 检查前提条件 + if not self.check_prerequisites(): + if not args.force: + return 1 + + # 2. 设置Intel环境 + if not self.setup_intel_environment(args): + if not args.force: + return 1 + + # 3. 清理目录 + if not self.clean_build_directory(args): + if not args.force: + return 1 + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 4. 配置CMake + if not self.configure_cmake(args): + if not args.force: + return 1 + + # 5. 构建项目 + if not self.build_project(args): + if not args.force: + return 1 + + # 6. 运行测试和创建测试运行器 + tests_passed = True + if not args.no_tests: + tests_passed = self.run_tests(args) + + # 创建测试运行器 + self.create_test_runner(args) # 传递 args 参数 + + # 7. 生成报告 + build_time = time.time() - start_time + self.generate_report(args, build_time, tests_passed) + + return 0 if tests_passed else 1 + + except KeyboardInterrupt: + self.print_error("\n构建被用户中断") + return 1 + except Exception as e: + self.print_error(f"构建过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + builder = BuildSystem() + return builder.run() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/scripts/requirements.txt b/example/1d-linear-convection/weno3/fortran/registry/02e/scripts/requirements.txt new file mode 100644 index 000000000..d21a12b45 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/scripts/requirements.txt @@ -0,0 +1,2 @@ +# 构建脚本的Python依赖(可选) +# 目前没有特殊依赖,保持空文件或添加未来可能需要的包 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02e/src/CMakeLists.txt new file mode 100644 index 000000000..d8cbd5f31 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/src/CMakeLists.txt @@ -0,0 +1,16 @@ +# ==================== src\CMakeLists.txt ==================== + +# src/CMakeLists.txt +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(base) +add_subdirectory(core) +add_subdirectory(infrastructure) +add_subdirectory(numerics) +add_subdirectory(manager) + +message(STATUS "源代码目录配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/src/base/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02e/src/base/CMakeLists.txt new file mode 100644 index 000000000..d537affd7 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/src/base/CMakeLists.txt @@ -0,0 +1,15 @@ +# src/base/CMakeLists.txt +message(STATUS "Configuring base module...") + +add_library(base STATIC + modules.f90 +) + +set_target_properties(base PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Base module configured") + +# src/core/CMakeLists.txt +message(STATUS "Configuring core module...") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/src/base/modules.f90 b/example/1d-linear-convection/weno3/fortran/registry/02e/src/base/modules.f90 new file mode 100644 index 000000000..99ef33372 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/src/base/modules.f90 @@ -0,0 +1,42 @@ +! src/base/modules.f90 +module base_modules + use, intrinsic :: iso_fortran_env, only: real64, int32 + + implicit none + + ! 公开所有基本类型 + public :: wp, ip, string_len, max_name_len + public :: cfd_config_base, component_info + + ! 工作精度类型 + integer, parameter :: wp = real64 + integer, parameter :: ip = int32 + + ! 字符串长度常量(使用数值常量) + integer, parameter :: string_len = 100 + integer, parameter :: max_name_len = 32 + + ! 基础配置类型 + type :: cfd_config_base + character(len=max_name_len) :: ic_type = "step" + character(len=max_name_len) :: recon_scheme = "eno" + character(len=max_name_len) :: flux_type = "rusanov" + integer(ip) :: rk_order = 1 + real(wp) :: wave_speed = 1.0_wp + real(wp) :: final_time = 0.625_wp + real(wp) :: dt = 0.025_wp + character(len=max_name_len) :: boundary_type = "periodic" + integer(ip) :: spatial_order = 2 + character(len=max_name_len) :: equation_type = "linear_advection" + character(len=max_name_len) :: problem_type = "linear_advection" + logical :: verbose = .true. + end type cfd_config_base + + ! 组件信息类型 + type :: component_info + character(len=max_name_len) :: category = "" + character(len=max_name_len) :: name = "" + integer(ip) :: order = 0 + end type component_info + +end module base_modules \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02e/src/core/CMakeLists.txt new file mode 100644 index 000000000..d8b8df064 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/src/core/CMakeLists.txt @@ -0,0 +1,14 @@ +# src/core/CMakeLists.txt +message(STATUS "Configuring core module...") + +add_library(core STATIC + registry.f90 +) + +target_link_libraries(core PRIVATE base) + +set_target_properties(core PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Core module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/src/core/factory_base.f90 b/example/1d-linear-convection/weno3/fortran/registry/02e/src/core/factory_base.f90 new file mode 100644 index 000000000..302418a15 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/src/core/factory_base.f90 @@ -0,0 +1,57 @@ +! src/core/factory_base.f90 +module factory_base_module + use base_modules, only: wp, ip + use registry_module, only: create_component, has_component + + implicit none + private + public :: wp, ip, factory_base, factory_create + + ! 工厂基类 + type :: factory_base + character(len=max_name_length) :: category = "" + contains + procedure :: create => factory_base_create + procedure :: get_available => factory_base_get_available + end type factory_base + + ! 便捷函数类型 + abstract interface + function factory_function_interface(category, name) result(instance) + import :: wp + character(len=*), intent(in) :: category, name + class(*), allocatable :: instance + end function factory_function_interface + end interface + +contains + + ! 创建工厂实例 + function factory_create(category) result(factory) + character(len=*), intent(in) :: category + type(factory_base) :: factory + factory%category = trim(category) + end function factory_create + + ! 工厂创建方法 + function factory_base_create(this, name) result(instance) + class(factory_base), intent(in) :: this + character(len=*), intent(in) :: name + class(*), allocatable :: instance + + instance = create_component(this%category, name) + end function factory_base_create + + ! 获取可用组件列表(简化版) + subroutine factory_base_get_available(this, names, count) + class(factory_base), intent(in) :: this + character(len=*), allocatable, intent(out) :: names(:) + integer(ip), intent(out) :: count + + ! 这里需要实现从注册表获取列表的逻辑 + ! 暂时返回空列表 + count = 0 + allocate(character(len=max_name_length) :: names(0)) + end subroutine factory_base_get_available + +end module factory_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/02e/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..a960167c3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/src/core/factory_interfaces.f90 @@ -0,0 +1,17 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, factory_procedure + + ! Factory procedure interface + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/02e/src/core/registry.f90 new file mode 100644 index 000000000..acc63edb4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/src/core/registry.f90 @@ -0,0 +1,203 @@ +! src/core/registry.f90 +module registry_module + use base_modules, only: wp, ip, max_name_len, component_info + + implicit none + private + + ! 明确公开所有需要的接口 + public :: wp, ip ! 类型参数 + public :: component_info ! 类型 + public :: registry_init, registry_cleanup ! 初始化/清理 + public :: register_component_simple ! 注册组件 + public :: has_component_simple ! 检查组件 + public :: list_components ! 列出组件 + public :: registry_is_initialized ! 检查初始化状态 ← 新增 + public :: registry_get_size ! 获取大小 ← 新增 + + ! 全局注册表 + type :: component_registry + type(component_info), allocatable :: components(:) + integer(ip) :: count = 0 + integer(ip) :: capacity = 100 + logical :: initialized = .false. + logical :: verbose = .true. + end type component_registry + + type(component_registry) :: registry + +contains + + ! ==================== 公共API ==================== + + subroutine registry_init(verbose) + logical, optional, intent(in) :: verbose + + if (registry%initialized) then + if (registry%verbose) then + print *, "[REGISTRY] Already initialized" + end if + return + end if + + if (present(verbose)) then + registry%verbose = verbose + end if + + allocate(registry%components(registry%capacity)) + registry%initialized = .true. + + if (registry%verbose) then + print *, "[REGISTRY] Initialized with capacity:", registry%capacity + end if + end subroutine registry_init + + subroutine registry_cleanup() + if (allocated(registry%components)) then + deallocate(registry%components) + end if + registry%initialized = .false. + registry%count = 0 + + if (registry%verbose) then + print *, "[REGISTRY] Cleaned up" + end if + end subroutine registry_cleanup + + subroutine register_component_simple(category, name, order) + character(len=*), intent(in) :: category, name + integer(ip), optional, intent(in) :: order + + integer(ip) :: i + type(component_info) :: info + + if (.not. registry%initialized) then + call registry_init() + end if + + ! 检查是否已存在 + do i = 1, registry%count + if (trim(registry%components(i)%category) == trim(category) .and. & + trim(registry%components(i)%name) == trim(name)) then + if (registry%verbose) then + print *, "[WARN] Overwriting component: ", trim(category), ".", trim(name) + end if + + ! 更新 + if (present(order)) then + registry%components(i)%order = order + else + registry%components(i)%order = 0 + end if + return + end if + end do + + ! 扩展数组 + if (registry%count >= registry%capacity) then + call expand_registry() + end if + + ! 添加新组件 + registry%count = registry%count + 1 + + info%category = trim(category) + info%name = trim(name) + info%order = 0 + if (present(order)) then + info%order = order + end if + + registry%components(registry%count) = info + + if (registry%verbose) then + print *, "[OK] Registered simple: ", trim(category), ".", trim(name) + end if + end subroutine register_component_simple + + logical function has_component_simple(category, name) + character(len=*), intent(in) :: category, name + + integer(ip) :: i + + has_component_simple = .false. + + if (.not. registry%initialized) return + + do i = 1, registry%count + if (trim(registry%components(i)%category) == trim(category) .and. & + trim(registry%components(i)%name) == trim(name)) then + has_component_simple = .true. + return + end if + end do + end function has_component_simple + + subroutine list_components(category) + character(len=*), optional, intent(in) :: category + + integer(ip) :: i, count + + if (.not. registry%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + if (registry%count == 0) then + print *, "[INFO] No components registered" + return + end if + + count = 0 + print *, "=== Registry Contents ===" + do i = 1, registry%count + if (.not. present(category) .or. & + trim(registry%components(i)%category) == trim(category)) then + call print_component_info(registry%components(i)) + count = count + 1 + end if + end do + + print *, "Total:", count, "components" + print *, "==========================" + end subroutine list_components + + ! ==================== 新增函数 ==================== + + logical function registry_is_initialized() + ! 检查注册表是否已初始化 + registry_is_initialized = registry%initialized + end function registry_is_initialized + + integer(ip) function registry_get_size() + ! 获取注册表中的组件数量 + registry_get_size = registry%count + end function registry_get_size + + ! ==================== 内部辅助函数 ==================== + + subroutine expand_registry() + type(component_info), allocatable :: temp(:) + + registry%capacity = registry%capacity * 2 + allocate(temp(registry%capacity)) + temp(1:registry%count) = registry%components(1:registry%count) + call move_alloc(temp, registry%components) + + if (registry%verbose) then + print *, "[INFO] Registry expanded to capacity:", registry%capacity + end if + end subroutine expand_registry + + subroutine print_component_info(info) + type(component_info), intent(in) :: info + + if (info%order > 0) then + print *, " [", trim(info%category), ".", trim(info%name), & + " (order:", info%order, ")]" + else + print *, " [", trim(info%category), ".", trim(info%name), "]" + end if + end subroutine print_component_info + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02e/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..c48396afb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/src/infrastructure/CMakeLists.txt @@ -0,0 +1,15 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "Configuring infrastructure module...") + +add_library(infrastructure STATIC + config.f90 + mesh.f90 +) + +target_link_libraries(infrastructure PRIVATE base) + +set_target_properties(infrastructure PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Infrastructure module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/02e/src/infrastructure/config.f90 new file mode 100644 index 000000000..719e14d25 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/src/infrastructure/config.f90 @@ -0,0 +1,64 @@ +! src/infrastructure/config.f90 +module config_module + use base_modules, only: wp, ip, max_name_len, cfd_config_base + + implicit none + public :: wp, ip, cfd_config, config_print, config_with_reconstruction + + ! 扩展配置类型 + type, extends(cfd_config_base) :: cfd_config + real(wp) :: left_boundary_value = 1.0_wp + real(wp) :: right_boundary_value = 2.0_wp + real(wp) :: domain_length = 2.0_wp + end type cfd_config + +contains + + subroutine config_print(cfg) + type(cfd_config), intent(in) :: cfg + + print *, "=== CFD Configuration ===" + print *, "Initial condition: ", trim(cfg%ic_type) + print *, "Reconstruction: ", trim(cfg%recon_scheme), " (order:", cfg%spatial_order, ")" + print *, "Flux type: ", trim(cfg%flux_type) + print *, "Time integration: RK", cfg%rk_order + print *, "Wave speed: ", cfg%wave_speed + print *, "Final time: ", cfg%final_time + print *, "Time step: ", cfg%dt + print *, "Boundary: ", trim(cfg%boundary_type) + print *, "===============================" + end subroutine config_print + + subroutine config_with_reconstruction(cfg, scheme, order) + type(cfd_config), intent(inout) :: cfg + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + integer :: i + + ! 转换为小写 + cfg%recon_scheme = scheme + do i = 1, len_trim(cfg%recon_scheme) + if (cfg%recon_scheme(i:i) >= 'A' .and. cfg%recon_scheme(i:i) <= 'Z') then + cfg%recon_scheme(i:i) = char(ichar(cfg%recon_scheme(i:i)) + 32) + end if + end do + + ! 设置阶数 + if (present(order)) then + cfg%spatial_order = order + else + if (index(cfg%recon_scheme, 'weno') > 0) then + cfg%spatial_order = 5 + else if (trim(cfg%recon_scheme) == 'eno') then + cfg%spatial_order = 3 + end if + end if + + if (cfg%verbose) then + print *, "[CONFIG] Reconstruction: ", trim(cfg%recon_scheme), & + " Order: ", cfg%spatial_order + end if + end subroutine config_with_reconstruction + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/src/infrastructure/domain.f90 b/example/1d-linear-convection/weno3/fortran/registry/02e/src/infrastructure/domain.f90 new file mode 100644 index 000000000..5a97c8a59 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/src/infrastructure/domain.f90 @@ -0,0 +1,80 @@ +! src/infrastructure/domain.f90 +module domain_module + use base_modules, only: wp, ip + use mesh_module, only: mesh_type + use config_module, only: cfd_config + + implicit none + private + public :: wp, ip, domain_type, domain_create, is_physical_cell + + type :: domain_type + type(cfd_config), pointer :: config => null() + type(mesh_type), pointer :: mesh => null() + integer(ip) :: nghosts = 0 + integer(ip) :: ist = 1 ! 1-based start index + integer(ip) :: ied = 1 ! 1-based end index (exclusive) + integer(ip) :: ntcells = 0 + contains + procedure :: print_info => domain_print_info + end type domain_type + +contains + + function domain_create(config, mesh) result(domain) + type(cfd_config), target, intent(in) :: config + type(mesh_type), target, intent(in) :: mesh + type(domain_type) :: domain + + domain%config => config + domain%mesh => mesh + + ! 计算ghost层数(参考Julia的_calc_nghosts) + domain%nghosts = calc_nghosts(config) + domain%ist = domain%nghosts + 1 + domain%ied = domain%ist + mesh%ncells - 1 + domain%ntcells = mesh%ncells + 2 * domain%nghosts + + if (config%verbose) then + print *, "[DOMAIN] Created with nghosts = ", domain%nghosts + print *, " Physical cells: ", domain%ist, " to ", domain%ied + print *, " Total cells: ", domain%ntcells + end if + end function domain_create + + function calc_nghosts(config) result(nghosts) + type(cfd_config), intent(in) :: config + integer(ip) :: nghosts + + character(len=max_name_length) :: scheme + + scheme = trim(config%recon_scheme) + + if (scheme == "eno") then + nghosts = config%spatial_order + else if (index(scheme, 'weno') > 0) then + nghosts = config%spatial_order / 2 + 1 + else + print *, "[ERROR] Unknown reconstruction scheme: ", trim(scheme) + nghosts = 2 ! 默认值 + end if + end function calc_nghosts + + logical function is_physical_cell(this, idx) + class(domain_type), intent(in) :: this + integer(ip), intent(in) :: idx + is_physical_cell = (idx >= this%ist .and. idx < this%ied) + end function is_physical_cell + + subroutine domain_print_info(this) + class(domain_type), intent(in) :: this + + print *, "=== Domain Information ===" + print *, "Ghost layers: ", this%nghosts + print *, "Physical cells: ", this%ist, " to ", this%ied - 1 + print *, "Total cells: ", this%ntcells + print *, "Mesh cells: ", this%mesh%ncells + print *, "==========================" + end subroutine domain_print_info + +end module domain_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/02e/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..f810f3a1b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/src/infrastructure/mesh.f90 @@ -0,0 +1,73 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use base_modules, only: wp, ip + + implicit none + public :: wp, ip, mesh_type, mesh_init, mesh_print_info + + ! 网格类型 + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer(ip) :: ncells = 40 + integer(ip) :: nnodes + integer(ip) :: nx + real(wp), allocatable :: x(:) ! 节点坐标 + real(wp), allocatable :: xcc(:) ! 单元中心坐标 + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer(ip), optional, intent(in) :: ncells + + integer(ip) :: i + + ! 设置参数 + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! 计算 + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! 分配内存 + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! 生成节点坐标 + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! 生成单元中心坐标 + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== Mesh Information ===" + print *, "Domain: [", this%xmin, ", ", this%xmax, "]" + print *, "Cells: ", this%ncells + print *, "Nodes: ", this%nnodes + print *, "dx: ", this%dx + print *, "L: ", this%L + print *, "========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/src/infrastructure/solution.f90 b/example/1d-linear-convection/weno3/fortran/registry/02e/src/infrastructure/solution.f90 new file mode 100644 index 000000000..5f9ed0878 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/src/infrastructure/solution.f90 @@ -0,0 +1,114 @@ +! src/infrastructure/solution.f90 +module solution_module + use base_modules, only: wp, ip + use domain_module, only: domain_type + + implicit none + private + public :: wp, ip, solution_type, solution_create, solution_reset + + type :: solution_type + type(domain_type), pointer :: domain => null() + real(wp), allocatable :: u(:) ! 当前解(包含ghost) + real(wp), allocatable :: un(:) ! 旧解 + real(wp), allocatable :: q_face_left(:) ! 左界面值 + real(wp), allocatable :: q_face_right(:)! 右界面值 + real(wp), allocatable :: flux(:) ! 通量 + real(wp), allocatable :: res(:) ! 残差 + contains + procedure :: initialize => solution_initialize + procedure :: update_old_field => solution_update_old_field + procedure :: print_info => solution_print_info + end type solution_type + +contains + + function solution_create(domain) result(solution) + type(domain_type), target, intent(in) :: domain + type(solution_type) :: solution + + integer(ip) :: ncells, nnodes, ntcells + + solution%domain => domain + + ncells = domain%mesh%ncells + nnodes = domain%mesh%nnodes + ntcells = domain%ntcells + + allocate(solution%u(ntcells), source=0.0_wp) + allocate(solution%un(ntcells), source=0.0_wp) + allocate(solution%q_face_left(nnodes), source=0.0_wp) + allocate(solution%q_face_right(nnodes), source=0.0_wp) + allocate(solution%flux(nnodes), source=0.0_wp) + allocate(solution%res(ncells), source=0.0_wp) + + if (domain%config%verbose) then + print *, "[SOLUTION] Created with arrays:" + print *, " u size: ", size(solution%u) + print *, " flux size: ", size(solution%flux) + print *, " res size: ", size(solution%res) + end if + end function solution_create + + subroutine solution_initialize(this, initial_values) + class(solution_type), intent(inout) :: this + real(wp), intent(in), optional :: initial_values(:) + + integer(ip) :: i, idx + type(domain_type), pointer :: domain + + domain => this%domain + + if (present(initial_values)) then + ! 应用初始值到物理区域 + do i = domain%ist, domain%ied - 1 + idx = i - domain%ist + 1 + if (idx <= size(initial_values)) then + this%u(i) = initial_values(idx) + end if + end do + else + ! 默认为0 + this%u = 0.0_wp + end if + + ! 同步旧场 + call this%update_old_field() + + if (domain%config%verbose) then + print *, "[SOLUTION] Initialized" + end if + end subroutine solution_initialize + + subroutine solution_update_old_field(this) + class(solution_type), intent(inout) :: this + this%un = this%u + end subroutine solution_update_old_field + + subroutine solution_reset(this) + class(solution_type), intent(inout) :: this + this%u = 0.0_wp + this%un = 0.0_wp + this%q_face_left = 0.0_wp + this%q_face_right = 0.0_wp + this%flux = 0.0_wp + this%res = 0.0_wp + end subroutine solution_reset + + subroutine solution_print_info(this) + class(solution_type), intent(in) :: this + + print *, "=== Solution Information ===" + print *, "u size: ", size(this%u) + print *, "un size: ", size(this%un) + print *, "q_face_left size: ", size(this%q_face_left) + print *, "q_face_right size: ", size(this%q_face_right) + print *, "flux size: ", size(this%flux) + print *, "res size: ", size(this%res) + if (allocated(this%u)) then + print *, "u range: ", minval(this%u), " to ", maxval(this%u) + end if + print *, "============================" + end subroutine solution_print_info + +end module solution_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/src/manager/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02e/src/manager/CMakeLists.txt new file mode 100644 index 000000000..00c8bf49b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/src/manager/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/manager/CMakeLists.txt +message(STATUS "配置管理器模块...") + +# 创建管理器库 +add_library(manager STATIC + component_manager.f90 + component_factory.f90 +) + +# 明确依赖关系:管理器依赖所有其他模块 +target_link_libraries(manager + PRIVATE + core + infrastructure + reconstructor + flux +) + +# 设置模块输出目录 +set_target_properties(manager PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "管理器模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/src/manager/component_factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/02e/src/manager/component_factory.f90 new file mode 100644 index 000000000..114fedea5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/src/manager/component_factory.f90 @@ -0,0 +1,127 @@ +! src/manager/component_factory.f90 +module component_factory_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use config_module, only: cfd_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + use eno_reconstructor_module, only: eno_reconstructor, create_eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor, create_weno3_reconstructor + use rusanov_flux_module, only: rusanov_flux, create_rusanov_flux + + implicit none + private + public :: wp, create_reconstructor, create_flux_calculator + + ! 错误代码 + integer, parameter :: CM_SUCCESS = 0 + integer, parameter :: CM_ERROR_UNKNOWN_SCHEME = 1 + integer, parameter :: CM_ERROR_UNKNOWN_FLUX = 2 + integer, parameter :: CM_ERROR_INVALID_ORDER = 3 + +contains + + ! ==================== 重构器创建 ==================== + + function create_reconstructor(config, status) result(recon) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(reconstructor_base), allocatable :: recon + + character(len=20) :: scheme + integer :: order, error_code + + scheme = trim(adjustl(config%recon_scheme)) + order = config%spatial_order + + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating reconstructor: ", scheme, " order=", order + end if + + select case(scheme) + case('eno') + allocate(eno_reconstructor :: recon) + select type(recon) + type is(eno_reconstructor) + recon = create_eno_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case('weno3') + allocate(weno3_reconstructor :: recon) + select type(recon) + type is(weno3_reconstructor) + recon = create_weno3_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case default + error_code = CM_ERROR_UNKNOWN_SCHEME + if (config%verbose) then + print *, "[ERROR] Unknown reconstructor scheme: ", scheme + print *, " Available: eno, weno3" + end if + end select + + ! 检查阶数有效性 + if (error_code == CM_SUCCESS) then + if (order < 1) then + error_code = CM_ERROR_INVALID_ORDER + if (config%verbose) then + print *, "[ERROR] Invalid spatial order: ", order + end if + end if + end if + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Reconstructor creation failed" + end if + end function create_reconstructor + + ! ==================== 通量计算器创建 ==================== + + function create_flux_calculator(config, status) result(flux) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(flux_calculator_base), allocatable :: flux + + character(len=20) :: flux_type + integer :: error_code + + flux_type = trim(adjustl(config%flux_type)) + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating flux calculator: ", flux_type + end if + + select case(flux_type) + case('rusanov') + allocate(rusanov_flux :: flux) + select type(flux) + type is(rusanov_flux) + flux = create_rusanov_flux() + flux%wave_speed_default = config%wave_speed + end select + + case default + error_code = CM_ERROR_UNKNOWN_FLUX + if (config%verbose) then + print *, "[ERROR] Unknown flux type: ", flux_type + print *, " Available: rusanov" + end if + end select + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Flux calculator creation failed" + end if + end function create_flux_calculator + +end module component_factory_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/src/manager/component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/02e/src/manager/component_manager.f90 new file mode 100644 index 000000000..9e095c257 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/src/manager/component_manager.f90 @@ -0,0 +1,75 @@ +! src/manager/component_manager.f90 +module component_manager_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use config_module, only: cfd_config + use component_factory_module, only: create_reconstructor, create_flux_calculator + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + + implicit none + private + public :: wp, component_manager_info, validate_config + public :: create_reconstructor, create_flux_calculator + +contains + + ! ==================== 配置验证 ==================== + + function validate_config(config) result(is_valid) + type(cfd_config), intent(in) :: config + logical :: is_valid + + integer :: status + class(reconstructor_base), allocatable :: test_recon + class(flux_calculator_base), allocatable :: test_flux + + is_valid = .false. + + ! 测试创建重构器 + test_recon = create_reconstructor(config, status) + if (status /= 0) then + if (config%verbose) then + print *, "[CONFIG VALIDATION] Invalid reconstructor configuration" + end if + return + end if + + ! 测试创建通量计算器 + test_flux = create_flux_calculator(config, status) + if (status /= 0) then + if (config%verbose) then + print *, "[CONFIG VALIDATION] Invalid flux configuration" + end if + return + end if + + ! 清理测试组件 + if (allocated(test_recon)) deallocate(test_recon) + if (allocated(test_flux)) deallocate(test_flux) + + is_valid = .true. + + if (config%verbose) then + print *, "[CONFIG VALIDATION] Configuration is valid" + end if + end function validate_config + + ! ==================== 信息显示 ==================== + + subroutine component_manager_info() + print *, "=== Component Manager ===" + print *, "Available reconstructors:" + print *, " - eno (orders: 1-7)" + print *, " - weno3 (order: 3)" + print *, "" + print *, "Available flux calculators:" + print *, " - rusanov" + print *, "" + print *, "Features:" + print *, " - Configuration validation" + print *, " - Component creation from config" + print *, " - Error handling with status codes" + print *, "=========================" + end subroutine component_manager_info + +end module component_manager_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..66874a7ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/CMakeLists.txt @@ -0,0 +1,7 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..3fde7746b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,28 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +# Start with just the base module +add_library(flux STATIC + base.f90 + rusanov.f90 +) + +#target_link_libraries(flux PRIVATE core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 明确设置不使用 /Qm64 选项 +if(CMAKE_Fortran_COMPILER_ID MATCHES "IntelLLVM|Intel") + set_target_properties(flux PROPERTIES + Fortran_FLAGS "${CMAKE_Fortran_FLAGS} /Qm64" # 明确设置,避免继承问题 + ) +endif() + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/flux/base.f90 new file mode 100644 index 000000000..7080a7ae4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/flux/base.f90 @@ -0,0 +1,30 @@ +!src/numerics/flux/base.f90 +module flux_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, flux_calculator_base + + ! Base flux calculator type + type :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure :: info => flux_info + procedure :: print_basic_info => flux_print_basic ! 添加辅助方法 + end type flux_calculator_base + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "Flux calculator information:" + print *, " Name: ", trim(this%name) + end subroutine flux_info + + subroutine flux_print_basic(this) + class(flux_calculator_base), intent(in) :: this + print *, " Name: ", trim(this%name) + end subroutine flux_print_basic + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/flux/rusanov.f90 b/example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/flux/rusanov.f90 new file mode 100644 index 000000000..daa9e3bb5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/flux/rusanov.f90 @@ -0,0 +1,41 @@ +! src/numerics/flux/rusanov.f90 +module rusanov_flux_module + use, intrinsic :: iso_fortran_env, only: real64 + use flux_base_module, only: flux_calculator_base + implicit none + + private + public :: real64, rusanov_flux, create_rusanov_flux + + type, extends(flux_calculator_base) :: rusanov_flux + real(real64) :: wave_speed_default = 1.0_real64 + contains + procedure :: info => rusanov_info + end type rusanov_flux + + ! 构造函数接口 + interface rusanov_flux + module procedure create_rusanov_flux + end interface + +contains + + ! 构造函数 + type(rusanov_flux) function create_rusanov_flux() result(this) + this%name = "Rusanov" + this%wave_speed_default = 1.0_real64 + end function create_rusanov_flux + + subroutine rusanov_info(this) + class(rusanov_flux), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Flux calculator information:" + call this%print_basic_info() + + ! 添加Rusanov特有信息 + print *, " Type: Rusanov flux" + print *, " Default wave speed: ", this%wave_speed_default + end subroutine rusanov_info + +end module rusanov_flux_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..5e4b938d5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,22 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "Configuring reconstructor module...") + +# Start with just the base module +add_library(reconstructor STATIC + base.f90 + eno.f90 + weno3.f90 +) + +target_link_libraries(reconstructor PRIVATE core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Reconstructor module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..53798d021 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/reconstructor/base.f90 @@ -0,0 +1,33 @@ +!src/numerics/reconstructor/base.f90 +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, reconstructor_base + + ! Base reconstructor type + type :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + contains + procedure :: info => reconstructor_info + procedure :: print_basic_info => reconstructor_print_basic ! 添加一个辅助方法 + end type reconstructor_base + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + print *, "Reconstructor information:" + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_info + + subroutine reconstructor_print_basic(this) + class(reconstructor_base), intent(in) :: this + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_print_basic + +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/reconstructor/eno.f90 b/example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/reconstructor/eno.f90 new file mode 100644 index 000000000..f973e8b32 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/reconstructor/eno.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/eno.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, eno_reconstructor, create_eno_reconstructor ! ← 添加这个 + + type, extends(reconstructor_base) :: eno_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => eno_info + end type eno_reconstructor + + ! 构造函数接口 + interface eno_reconstructor + module procedure create_eno_reconstructor + end interface + +contains + + ! 构造函数 + type(eno_reconstructor) function create_eno_reconstructor() result(this) + this%name = "ENO" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_eno_reconstructor + + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加ENO特有信息 + print *, " Type: ENO reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/reconstructor/weno3.f90 b/example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/reconstructor/weno3.f90 new file mode 100644 index 000000000..d5b7a7477 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/src/numerics/reconstructor/weno3.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/weno3.f90 +module weno3_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno3_reconstructor, create_weno3_reconstructor + + type, extends(reconstructor_base) :: weno3_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno3_info + end type weno3_reconstructor + + ! 构造函数接口 + interface weno3_reconstructor + module procedure create_weno3_reconstructor + end interface + +contains + + ! 构造函数 + type(weno3_reconstructor) function create_weno3_reconstructor() result(this) + this%name = "WENO3" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_weno3_reconstructor + + subroutine weno3_info(this) + class(weno3_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加WENO3特有信息 + print *, " Type: WENO-3 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno3_info + +end module weno3_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02e/tests/CMakeLists.txt new file mode 100644 index 000000000..7b949064d --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/tests/CMakeLists.txt @@ -0,0 +1,71 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +# +#message(STATUS "CMAKE_Fortran_MODULE_DIRECTORY=${CMAKE_Fortran_MODULE_DIRECTORY}") +# +add_executable(test_minimal_simple test_minimal_simple.f90) +target_link_libraries(test_minimal_simple + PRIVATE + core # 必须链接core库 + infrastructure +) + + +add_executable(test_simple_link test_simple_link.f90) +target_link_libraries(test_simple_link + PRIVATE + reconstructor + flux +) + + +add_executable(test_factory_simple test_factory_simple.f90) +target_link_libraries(test_factory_simple + PRIVATE + core + infrastructure + reconstructor + flux +) + +# 更新测试链接 +#add_executable(test_component_manager test_component_manager.f90) +# +#target_link_libraries(test_component_manager +# PRIVATE +# manager # ← 链接到新的管理器库 +# infrastructure +#) +# +#add_executable(test_architecture test_architecture.f90) +#target_link_libraries(test_architecture +# PRIVATE +# core +# infrastructure +# manager +#) + +#add_executable(test_solver_framework test_solver_framework.f90) +#target_link_libraries(test_solver_framework +# PRIVATE +# core +# infrastructure +# manager +#) +# +#add_executable(test_cfd_architecture test_cfd_architecture.f90) +#target_link_libraries(test_cfd_architecture +# PRIVATE +# base +# core +# infrastructure +#) + + +add_executable(test_basic_only test_basic_only.f90) +target_link_libraries(test_basic_only + PRIVATE + infrastructure + core +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_architecture.f90 b/example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_architecture.f90 new file mode 100644 index 000000000..1c40d4677 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_architecture.f90 @@ -0,0 +1,117 @@ +! tests/test_architecture.f90 +program test_architecture + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module + use config_module + use mesh_module + use component_manager_module + + implicit none + + print *, "=== CFD架构测试 ===" + print *, "" + + ! 测试1: 基本系统 + call test_basic_systems() + + ! 测试2: 组件注册 + call test_registry_integration() + + ! 测试3: 配置验证 + call test_config_validation() + + ! 测试4: 完整流程 + call test_full_workflow() + + print *, "" + print *, "=== 架构测试完成 ===" + +contains + + subroutine test_basic_systems() + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "1. 测试基本系统..." + print *, "-------------------" + + ! 测试配置 + call config_print(config) + print *, "✓ 配置创建成功" + + ! 测试网格 + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "✓ 网格创建成功" + print *, "" + end subroutine test_basic_systems + + subroutine test_registry_integration() + print *, "2. 测试注册系统集成..." + print *, "------------------------" + + call initialize_registry(verbose=.true.) + + ! 注册核心组件 + print *, "注册核心组件:" + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + ! 显示注册内容 + call component_registry%list_simple() + print *, "注册表大小: ", registry_get_size() + + call cleanup_registry() + print *, "✓ 注册系统测试完成" + print *, "" + end subroutine test_registry_integration + + subroutine test_config_validation() + type(cfd_config) :: config + logical :: is_valid + + print *, "3. 测试配置验证..." + print *, "-------------------" + + ! 测试有效配置 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + print *, "测试配置:" + print *, " - 重构器: ", trim(config%recon_scheme), " 阶数: ", config%spatial_order + print *, " - 通量: ", trim(config%flux_type) + print *, " - 波速: ", config%wave_speed + + ! 这里调用验证函数(需要先实现) + ! is_valid = validate_config(config) + print *, "✓ 配置验证接口准备就绪" + print *, "" + end subroutine test_config_validation + + subroutine test_full_workflow() + print *, "4. 测试完整流程..." + print *, "-------------------" + + print *, "步骤1: 初始化系统 ✓" + print *, "步骤2: 创建网格 ✓" + print *, "步骤3: 设置配置 ✓" + print *, "步骤4: 创建组件 (待实现)" + print *, "步骤5: 初始化解 (待实现)" + print *, "步骤6: 时间推进 (待实现)" + print *, "步骤7: 输出结果 (待实现)" + print *, "" + + print *, "✓ 流程框架定义完成" + end subroutine test_full_workflow + +end program test_architecture \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_basic_only.f90 b/example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_basic_only.f90 new file mode 100644 index 000000000..20901ddcc --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_basic_only.f90 @@ -0,0 +1,56 @@ +! tests/test_basic_only.f90 +program test_basic_only + ! 只测试最基本的功能,不依赖复杂模块 + use config_module, only: cfd_config, config_print, wp + use mesh_module, only: mesh_type + use registry_module, only: registry_init, registry_cleanup, & + register_component_simple, list_components + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "=== BASIC TEST - Minimal Functionality ===" + print *, "" + + ! 测试1: 配置 + print *, "1. Testing configuration..." + print *, "----------------------------" + call config_print(config) + print *, "" + + ! 测试2: 网格 + print *, "2. Testing mesh..." + print *, "------------------" + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=5) + print *, "Mesh initialized:" + print *, " Cells: ", mesh%ncells + print *, " Nodes: ", mesh%nnodes + print *, " dx: ", mesh%dx + print *, "" + + ! 测试3: 注册系统 + print *, "3. Testing registry..." + print *, "----------------------" + + call registry_init() + + ! 注册组件(使用简化版本) + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("flux", "rusanov") + + ! 列出组件 + call list_components() + print *, "" + + ! 清理 + call registry_cleanup() + + print *, "=== TEST PASSED ===" + print *, "✓ Configuration works" + print *, "✓ Mesh works" + print *, "✓ Registry works" + +end program test_basic_only \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_cfd_architecture.f90 b/example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_cfd_architecture.f90 new file mode 100644 index 000000000..1c40d4677 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_cfd_architecture.f90 @@ -0,0 +1,117 @@ +! tests/test_architecture.f90 +program test_architecture + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module + use config_module + use mesh_module + use component_manager_module + + implicit none + + print *, "=== CFD架构测试 ===" + print *, "" + + ! 测试1: 基本系统 + call test_basic_systems() + + ! 测试2: 组件注册 + call test_registry_integration() + + ! 测试3: 配置验证 + call test_config_validation() + + ! 测试4: 完整流程 + call test_full_workflow() + + print *, "" + print *, "=== 架构测试完成 ===" + +contains + + subroutine test_basic_systems() + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "1. 测试基本系统..." + print *, "-------------------" + + ! 测试配置 + call config_print(config) + print *, "✓ 配置创建成功" + + ! 测试网格 + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "✓ 网格创建成功" + print *, "" + end subroutine test_basic_systems + + subroutine test_registry_integration() + print *, "2. 测试注册系统集成..." + print *, "------------------------" + + call initialize_registry(verbose=.true.) + + ! 注册核心组件 + print *, "注册核心组件:" + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + ! 显示注册内容 + call component_registry%list_simple() + print *, "注册表大小: ", registry_get_size() + + call cleanup_registry() + print *, "✓ 注册系统测试完成" + print *, "" + end subroutine test_registry_integration + + subroutine test_config_validation() + type(cfd_config) :: config + logical :: is_valid + + print *, "3. 测试配置验证..." + print *, "-------------------" + + ! 测试有效配置 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + print *, "测试配置:" + print *, " - 重构器: ", trim(config%recon_scheme), " 阶数: ", config%spatial_order + print *, " - 通量: ", trim(config%flux_type) + print *, " - 波速: ", config%wave_speed + + ! 这里调用验证函数(需要先实现) + ! is_valid = validate_config(config) + print *, "✓ 配置验证接口准备就绪" + print *, "" + end subroutine test_config_validation + + subroutine test_full_workflow() + print *, "4. 测试完整流程..." + print *, "-------------------" + + print *, "步骤1: 初始化系统 ✓" + print *, "步骤2: 创建网格 ✓" + print *, "步骤3: 设置配置 ✓" + print *, "步骤4: 创建组件 (待实现)" + print *, "步骤5: 初始化解 (待实现)" + print *, "步骤6: 时间推进 (待实现)" + print *, "步骤7: 输出结果 (待实现)" + print *, "" + + print *, "✓ 流程框架定义完成" + end subroutine test_full_workflow + +end program test_architecture \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_component_manager.f90 new file mode 100644 index 000000000..f60c35056 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_component_manager.f90 @@ -0,0 +1,111 @@ +! tests/test_component_manager.f90 +program test_component_manager + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use component_manager_module, only: create_reconstructor, create_flux_calculator + use component_manager_module, only: component_manager_info, validate_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + implicit none + + type(cfd_config) :: config + class(reconstructor_base), allocatable :: recon + class(flux_calculator_base), allocatable :: flux + integer :: status + logical :: is_valid + + print *, "=== Component Manager Test ===" + print *, "" + + ! 显示组件管理器信息 + call component_manager_info() + print *, "" + + ! 测试1: 基本配置 + print *, "1. Testing basic ENO3 + Rusanov configuration..." + print *, "-----------------------------------------------" + + config%verbose = .true. + call config_print(config) + + ! 配置ENO3重构 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + call config_print(config) + print *, "" + + ! 验证配置 + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Configuration is valid" + else + print *, "[ERROR] Configuration is invalid" + end if + print *, "" + + ! 测试2: 创建组件 + print *, "2. Testing component creation..." + print *, "--------------------------------" + + ! 创建重构器(带状态检查) + recon = create_reconstructor(config, status) + if (status == 0) then + print *, "[OK] Reconstructor created successfully" + call recon%info() + else + print *, "[ERROR] Failed to create reconstructor, code:", status + end if + print *, "" + + ! 创建通量计算器 + flux = create_flux_calculator(config, status) + if (status == 0) then + print *, "[OK] Flux calculator created successfully" + call flux%info() + else + print *, "[ERROR] Failed to create flux calculator, code:", status + end if + print *, "" + + ! 测试3: WENO3重构测试 + print *, "3. Testing WENO3 configuration..." + print *, "---------------------------------" + + call config_with_reconstruction(config, "weno3", 3) + + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] WENO3 configuration is valid" + + ! 创建WENO3重构器 + recon = create_reconstructor(config) + call recon%info() + else + print *, "[ERROR] WENO3 configuration is invalid" + end if + print *, "" + + ! 测试4: 错误配置测试 + print *, "4. Testing invalid configuration..." + print *, "-----------------------------------" + + config%recon_scheme = "unknown_scheme" + config%flux_type = "unknown_flux" + + is_valid = validate_config(config) + if (.not. is_valid) then + print *, "[OK] Invalid configuration correctly rejected" + else + print *, "[ERROR] Invalid configuration should have been rejected" + end if + + ! 清理 + if (allocated(recon)) deallocate(recon) + if (allocated(flux)) deallocate(flux) + + print *, "" + print *, "=== Component manager test completed successfully ===" + +end program test_component_manager \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_factory_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_factory_simple.f90 new file mode 100644 index 000000000..db65da7cf --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_factory_simple.f90 @@ -0,0 +1,58 @@ +! tests/test_factory_simple.f90 (修复版) +program test_factory_simple + use base_modules, only: wp ! ← 添加这行 + use config_module, only: cfd_config, config_print + use mesh_module, only: mesh_type + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(eno_reconstructor) :: eno + type(weno3_reconstructor) :: weno3 + type(rusanov_flux) :: rusanov + + print *, "=== Factory Pattern Simple Test ===" + print *, "" + + ! Test 1: Basic systems + print *, "1. Testing basic systems..." + print *, "-----------------------------" + call config_print(config) + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 2: Creating reconstructors + print *, "2. Testing reconstructors..." + print *, "------------------------------" + + ! 创建并测试ENO重构器 + print *, "Creating ENO reconstructor..." + eno = eno_reconstructor() ! 使用构造函数 + call eno%info() ! 必须调用info方法 + + print *, "" + print *, "Creating WENO3 reconstructor..." + weno3 = weno3_reconstructor() ! 使用构造函数 + call weno3%info() ! 必须调用info方法 + print *, "" + + ! Test 3: Creating flux calculator + print *, "3. Testing flux calculator..." + print *, "-------------------------------" + + print *, "Creating Rusanov flux calculator..." + rusanov = rusanov_flux() ! 使用构造函数 + call rusanov%info() ! 必须调用info方法 + print *, "" + + print *, "=== Factory pattern simple test completed successfully ===" + +end program test_factory_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_minimal_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_minimal_simple.f90 new file mode 100644 index 000000000..ec03ccf89 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_minimal_simple.f90 @@ -0,0 +1,87 @@ +! tests/test_minimal_simple.f90 +program test_minimal_simple + use base_modules, only: wp ! ← 添加这行 + use registry_module + use config_module + use mesh_module + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Simple Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call registry_init() + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call list_components() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + + if (has_component_simple("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component_simple("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components (simple version) + print *, "5. Testing registry functionality" + print *, "---------------------------------" + print *, "Registry initialized: ", registry_is_initialized() + print *, "Number of components: ", registry_get_size() + print *, "" + + ! Cleanup + call registry_cleanup() + + print *, "=== Simple test completed successfully ===" + +end program test_minimal_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_simple_link.f90 b/example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_simple_link.f90 new file mode 100644 index 000000000..71cc614e8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_simple_link.f90 @@ -0,0 +1,78 @@ +! tests/test_simple_link.f90 +program test_simple_link + use base_modules, only: wp ! ← 添加这行 + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call registry_init() + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call list_components() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component_simple("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component_simple("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Cleanup + call registry_cleanup() + + print *, "=== Minimal test completed successfully ===" + +end program test_simple_link \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_solver_framework.f90 b/example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_solver_framework.f90 new file mode 100644 index 000000000..6754323d0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02e/tests/test_solver_framework.f90 @@ -0,0 +1,91 @@ +! tests/test_solver_framework.f90 +program test_solver_framework + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use mesh_module, only: mesh_type + use solver_module, only: cfd_solver, solver_create, solver_run, solver_cleanup + use solver_module, only: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, & + SOLVER_COMPLETED, SOLVER_ERROR + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(cfd_solver) :: solver + + print *, "=== 求解器框架测试 ===" + print *, "" + + ! 测试1: 基本创建 + print *, "1. 测试求解器创建..." + print *, "----------------------" + + ! 创建配置 + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.0_real64 + config%dt = 0.01_real64 + + call config_print(config) + print *, "" + + ! 创建网格 + call mesh%init(xmin=0.0_real64, xmax=2.0_real64, ncells=20) + call mesh%print_info() + print *, "" + + ! 创建求解器 + solver = solver_create(config, mesh) + print *, "✓ 求解器创建成功" + print *, " 状态: ", solver%get_state() + print *, "" + + ! 测试2: 求解器初始化 + print *, "2. 测试求解器初始化..." + print *, "------------------------" + + call solver%initialize() + print *, "✓ 求解器初始化完成" + print *, " 状态: ", solver%get_state() + print *, " 错误信息: '", trim(solver%get_error()), "'" + print *, "" + + ! 测试3: 简单运行 + print *, "3. 测试求解器运行..." + print *, "----------------------" + + call solver_run(solver, 0.05_real64) ! 运行到0.05秒 + print *, "✓ 求解器运行完成" + print *, " 最终状态: ", solver%get_state() + print *, "" + + ! 测试4: 清理 + print *, "4. 测试求解器清理..." + print *, "----------------------" + + call solver_cleanup(solver) + print *, "✓ 求解器清理完成" + print *, " 状态: ", solver%get_state() + print *, "" + + ! 测试5: 错误处理 + print *, "5. 测试错误处理..." + print *, "-------------------" + + ! 尝试重复初始化 + call solver%initialize() + print *, " 重复初始化状态: ", solver%get_state() + print *, " 错误信息: '", trim(solver%get_error()), "'" + + call solver_cleanup(solver) + print *, "" + + print *, "=== 框架测试总结 ===" + print *, "✓ 求解器创建/初始化/运行/清理流程验证完成" + print *, "✓ 状态管理正常工作" + print *, "✓ 错误处理机制就绪" + print *, "" + print *, "下一步: 添加实际数值计算功能" + +end program test_solver_framework \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02f/CMakeLists.txt new file mode 100644 index 000000000..ef66d5848 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/README.md b/example/1d-linear-convection/weno3/fortran/registry/02f/README.md new file mode 100644 index 000000000..c4889757b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/README.md @@ -0,0 +1,221 @@ +# Fortran CFD Project + +基于工厂模式和注册系统的CFD求解器框架。 + +## 项目结构 + +``` +fortran_cfd/ +├── CMakeLists.txt # 根CMake配置 +├── scripts/ # 构建脚本 +│ ├── build.py # Python构建脚本(推荐) +│ ├── build.bat # Batch包装脚本 +│ ├── build.ps1 # PowerShell包装脚本 +│ └── requirements.txt # Python依赖 +├── src/ # 源代码 +│ ├── CMakeLists.txt +│ ├── core/ # 核心模块(注册系统、工厂接口) +│ ├── infrastructure/ # 基础设施(配置、网格) +│ └── numerics/ # 数值方法 +├── tests/ # 测试 +│ ├── CMakeLists.txt +│ ├── test_minimal_simple.f90 # 简单测试 +│ └── test_factory.f90 # 工厂模式测试 +└── README.md # 项目说明(本文件) +``` + +## 快速开始 + +### 使用Python脚本(推荐) + +```bash +# 进入脚本目录 +cd scripts + +# 基本构建 +python build.py + +# 清理并构建 +python build.py --clean + +# 发布构建 +python build.py --build-type Release --clean + +# 并行构建(使用所有核心) +python build.py -j + +# 不运行测试 +python build.py --no-tests + +# 查看帮助 +python build.py --help +``` + +### 使用批处理脚本 + +```bash +cd scripts +.\build.bat +``` + +### 使用PowerShell脚本 + +```powershell +cd scripts +.\build.ps1 +``` + +## 手动构建 + +```bash +# 设置Intel环境 +cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && powershell' + +# 配置项目 +mkdir build +cd build +cmake -G "Visual Studio 17 2022" -A x64 -T fortran=ifx .. + +# 构建项目 +cmake --build . --config Debug + +# 运行测试 +Debug\test_simple.exe +Debug\test_factory.exe +``` + +## 功能特性 + +✅ 组件注册系统 +✅ 工厂模式 +✅ ENO/WENO重构器 +✅ Rusanov通量计算器 +✅ 可扩展架构 +✅ 自动化测试 + +## 依赖 + +- Intel oneAPI(包含ifx编译器) +- CMake 3.12+ +- Python 3.6+(仅用于构建脚本) + +## 源代码文件 + +### 核心模块 +- `src/core/registry.f90` - 组件注册系统 +- `src/core/factory_interfaces.f90` - 工厂接口 + +### 基础设施 +- `src/infrastructure/config.f90` - 配置管理 +- `src/infrastructure/mesh.f90` - 网格生成 + +### 数值方法 +- `src/numerics/reconstructor/base.f90` - 重构器基类 +- `src/numerics/reconstructor/eno.f90` - ENO重构器 +- `src/numerics/reconstructor/weno3.f90` - WENO-3重构器 +- `src/numerics/flux/base.f90` - 通量计算器基类 +- `src/numerics/flux/rusanov.f90` - Rusanov通量 + +### 测试 +- `tests/test_minimal_simple.f90` - 简单功能测试 +- `tests/test_factory.f90` - 工厂模式测试 + +## 构建脚本 + +### Python脚本 (build.py) +主构建脚本,支持: +- 自动设置Intel oneAPI环境 +- 参数化构建选项 +- 并行编译 +- 自动化测试 +- 彩色输出 + +### Batch脚本 (build.bat) +Windows批处理包装器,调用Python脚本。 + +### PowerShell脚本 (build.ps1) +PowerShell包装器,调用Python脚本。 + +## 示例输出 + +成功构建后,会看到类似输出: + +``` +============================================================ + Fortran CFD Project Builder +============================================================ + +[1/4] Setting up Intel Fortran compiler... +✓ Intel oneAPI environment configured + +[2/4] Preparing build directory... +✓ Cleaned build directory + +[3/4] Configuring project... + $ cmake -G Visual Studio 17 2022 -A x64 -T fortran=ifx -DCMAKE_BUILD_TYPE=Debug .. +✓ CMake configuration successful + +[4/4] Building project... + $ cmake --build . --config Debug +✓ Build completed in 12.3 seconds + +============================================================ + Running Tests +============================================================ + +[TEST] Simple functionality test... +✓ Simple test passed + +[TEST] Factory pattern test... +✓ Factory test passed + +============================================================ + Build Summary +============================================================ +✓ Build directory: D:\path\to\build +✓ Build type: Debug +✓ Total time: 15.2s +``` + +## 开发指南 + +### 添加新组件 + +1. 在相应模块中创建Fortran源文件 +2. 实现工厂函数 +3. 使用`register_component_with_factory`注册组件 +4. 添加测试 + +### 扩展架构 + +项目采用模块化设计: +1. **注册系统** - 管理所有可用的组件 +2. **工厂模式** - 动态创建组件实例 +3. **抽象接口** - 确保组件兼容性 +4. **配置驱动** - 通过配置文件控制行为 + +## 故障排除 + +### 常见问题 + +1. **Intel环境未找到** + - 检查Intel oneAPI安装路径 + - 手动运行`setvars.bat` + +2. **CMake配置失败** + - 确保使用正确的生成器:`Visual Studio 17 2022` + - 检查Fortran编译器是否可用 + +3. **构建失败** + - 检查依赖项是否完整 + - 查看详细的错误信息 + +### 调试建议 + +- 使用`--verbose`参数获取详细输出 +- 检查`build/CMakeCache.txt`了解配置详情 +- 查看编译器错误日志 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/scripts/build.bat b/example/1d-linear-convection/weno3/fortran/registry/02f/scripts/build.bat new file mode 100644 index 000000000..6fd6dc031 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/scripts/build.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Project Builder +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python build script with full Intel environment support... +echo. + +python build.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Build failed + pause + exit /b 1 +) + +echo. +echo [INFO] Build completed successfully! +echo. +echo [INFO] To run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/scripts/build.py b/example/1d-linear-convection/weno3/fortran/registry/02f/scripts/build.py new file mode 100644 index 000000000..3bf6d5375 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/scripts/build.py @@ -0,0 +1,629 @@ +#!/usr/bin/env python3 +""" +Fortran CFD Project Builder - 完整Python解决方案 +在Python内部处理Intel oneAPI环境配置 +""" + +import os +import sys +import subprocess +import shutil +import argparse +import time +import platform +import tempfile +from pathlib import Path + +class IntelEnvironment: + """Intel oneAPI环境管理器""" + + def __init__(self): + self.setvars_path = None + self.env_vars = {} + + def find_setvars(self): + """查找setvars.bat文件""" + possible_paths = [ + r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat", + r"C:\Program Files\Intel\oneAPI\setvars.bat", + r"C:\Program Files (x86)\Intel\oneAPI\compiler\latest\env\vars.bat", + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\setvars.bat"), + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\compiler\latest\env\vars.bat"), + ] + + for path in possible_paths: + if os.path.exists(path): + self.setvars_path = path + return True + + return False + + def setup_environment(self): + """设置Intel环境""" + if not self.find_setvars(): + return False + + try: + # 创建临时的批处理文件来捕获环境变量 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + f.write(f'@echo off\n') + f.write(f'call "{self.setvars_path}" >nul 2>&1\n') + f.write(f'set\n') # 输出所有环境变量 + temp_bat = f.name + + # 运行批处理文件并捕获输出 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + shell=True + ) + + # 解析环境变量 + for line in result.stdout.split('\n'): + line = line.strip() + if '=' in line: + key, value = line.split('=', 1) + self.env_vars[key.strip()] = value.strip() + + # 清理临时文件 + os.unlink(temp_bat) + + # 更新当前进程的环境变量 + os.environ.update(self.env_vars) + + return True + + except Exception as e: + print(f"设置Intel环境失败: {e}") + return False + + def get_compiler_info(self): + """获取编译器信息""" + info = {} + + # 检查ifx编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + env={**os.environ, **self.env_vars} if self.env_vars else os.environ + ) + + if result.returncode == 0: + for line in result.stdout.split('\n'): + if 'Version' in line or '版本' in line: + info['ifx_version'] = line.strip() + break + except: + pass + + # 检查环境变量 + info['ifx_root'] = self.env_vars.get('IFX_ROOT', '') + info['compiler_root'] = self.env_vars.get('ONEAPI_ROOT', '') + + return info + +class BuildSystem: + """构建系统主类""" + + def __init__(self): + self.project_root = Path(__file__).parent.parent + self.build_dir = self.project_root / "build" + self.intel_env = IntelEnvironment() + + # 设置控制台编码 + if sys.platform == "win32": + try: + import ctypes + # 设置控制台输出为UTF-8 + ctypes.windll.kernel32.SetConsoleOutputCP(65001) + except: + pass + + def print_header(self, text): + """打印标题""" + print(f"\n{'='*70}") + print(f" {text}") + print(f"{'='*70}\n") + + def print_step(self, step, total, message): + """打印步骤""" + print(f"[{step}/{total}] {message}...") + + def print_success(self, message): + """打印成功""" + print(f"\033[92m✓ {message}\033[0m") + + def print_error(self, message): + """打印错误""" + print(f"\033[91m✗ {message}\033[0m") + + def print_warning(self, message): + """打印警告""" + print(f"\033[93m! {message}\033[0m") + + def print_info(self, message): + """打印信息""" + print(f"\033[94mℹ {message}\033[0m") + + def check_prerequisites(self): + """检查前提条件""" + self.print_step(1, 6, "检查前提条件") + + # 检查Python版本 + python_version = sys.version.split()[0] + self.print_info(f"Python版本: {python_version}") + + # 检查平台 + self.print_info(f"平台: {platform.system()} {platform.release()}") + self.print_info(f"处理器核心数: {os.cpu_count()}") + + # 检查CMake + try: + result = subprocess.run( + ["cmake", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + version_line = result.stdout.split('\n')[0] + self.print_success(f"CMake: {version_line}") + else: + self.print_error("CMake未找到") + return False + except FileNotFoundError: + self.print_error("CMake未安装") + return False + + return True + + def setup_intel_environment(self, args): + """设置Intel环境""" + self.print_step(2, 6, "配置Intel oneAPI环境") + + if not self.intel_env.find_setvars(): + self.print_warning("未找到Intel oneAPI setvars.bat") + self.print_info("将尝试使用系统环境中的编译器") + + # 检查是否能直接访问编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + self.print_success("Intel编译器在系统PATH中找到") + return True + else: + self.print_warning("Intel编译器未在PATH中找到") + except: + self.print_warning("无法访问Intel编译器") + + return True # 继续,让CMake自己找编译器 + + # 设置环境 + if self.intel_env.setup_environment(): + compiler_info = self.intel_env.get_compiler_info() + + if compiler_info.get('ifx_version'): + self.print_success(f"Intel Fortran编译器: {compiler_info['ifx_version']}") + elif compiler_info.get('ifx_root'): + self.print_success(f"Intel编译器路径: {compiler_info['ifx_root']}") + else: + self.print_success("Intel oneAPI环境配置完成") + + return True + else: + self.print_warning("Intel环境配置失败,将继续使用系统环境") + return True + + def clean_build_directory(self, args): + """清理构建目录""" + if args.clean and self.build_dir.exists(): + self.print_info("清理构建目录...") + try: + shutil.rmtree(self.build_dir) + self.print_success("构建目录已清理") + except Exception as e: + self.print_error(f"清理失败: {e}") + if not args.force: + return False + return True + + def run_command(self, cmd, cwd=None, check=True, env=None): + """运行命令""" + if isinstance(cmd, list): + cmd_str = ' '.join(str(c) for c in cmd if c) + else: + cmd_str = str(cmd) + + print(f" \033[96m$\033[0m {cmd_str}") + + try: + # 合并环境变量 + exec_env = os.environ.copy() + if env: + exec_env.update(env) + if self.intel_env.env_vars: + exec_env.update(self.intel_env.env_vars) + + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=False, + env=exec_env + ) + + # 处理输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or '完成' in line or '生成' in line: + print(f" \033[92m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + if check and result.returncode != 0: + self.print_error(f"命令执行失败,退出码: {result.returncode}") + return False + + return True + + except Exception as e: + self.print_error(f"命令执行异常: {e}") + return False + + def configure_cmake(self, args): + """配置CMake""" + self.print_step(3, 6, "配置CMake项目") + + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + f"-DCMAKE_BUILD_TYPE={args.build_type}", + ] + + if args.compiler == "ifx": + cmake_cmd.extend(["-T", "fortran=ifx"]) + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + success = self.run_command(cmake_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("CMake配置完成") + else: + self.print_error("CMake配置失败") + + return success + + def build_project(self, args): + """构建项目""" + self.print_step(4, 6, "构建项目") + + build_cmd = [ + "cmake", + "--build", ".", + "--config", args.build_type, + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + success = self.run_command(build_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("项目构建完成") + else: + self.print_error("构建失败") + + return success + + def run_tests_with_environment(self, test_exe): + """运行单个测试,确保有Intel环境""" + try: + # 创建临时的批处理文件来运行测试 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'"{test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'"{test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + return result + + except Exception as e: + print(f"运行测试失败: {e}") + return None + + def run_tests(self, args): + """运行测试""" + self.print_step(5, 6, "运行测试") + + # 查找测试可执行文件 + test_dir = self.build_dir / "bin" / args.build_type + if not test_dir.exists(): + test_dir = self.build_dir / "bin" + if not test_dir.exists(): + test_dir = self.build_dir + + test_files = list(test_dir.glob("test_*.exe")) + + if not test_files: + self.print_warning("未找到测试程序") + return True + + all_passed = True + + for test_exe in sorted(test_files): + test_name = test_exe.stem + self.print_info(f"运行测试: {test_name}") + print(f" {'-'*50}") + + # 运行测试 + result = self.run_tests_with_environment(str(test_exe)) + + if result is None: + self.print_error(f" {test_name} 运行失败") + all_passed = False + continue + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + print(f" {line}") + + if result.returncode == 0: + self.print_success(f" {test_name} 通过") + else: + self.print_error(f" {test_name} 失败 (退出码: {result.returncode})") + all_passed = False + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + print() # 空行 + + return all_passed + + def create_test_runner(self, args): + """创建独立的测试运行器""" + self.print_step(6, 6, "创建测试运行器") + + runner_path = self.build_dir / "run_tests.bat" + + content = f'''@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Test Runner +echo ======================================== +echo. + +REM Setup Intel oneAPI environment +set "SETVARS_PATH={self.intel_env.setvars_path or ''}" +if exist "%SETVARS_PATH%" ( + call "%SETVARS_PATH%" >nul + echo [INFO] Intel environment configured +) else ( + echo [WARNING] Intel environment not found + echo [WARNING] Tests may fail without runtime libraries +) + +echo. + +REM Run all test executables +set "TEST_COUNT=0" +set "PASS_COUNT=0" + +for %%f in ("bin\\{args.build_type}\\test_*.exe") do ( + set /a TEST_COUNT+=1 + echo [TEST %%f] + echo {'-'*50} + + %%f + if errorlevel 1 ( + echo [FAILED] %%f + ) else ( + echo [PASSED] %%f + set /a PASS_COUNT+=1 + ) + echo. +) + +echo ======================================== +echo Tests: %PASS_COUNT%/%TEST_COUNT% passed +if %PASS_COUNT% equ %TEST_COUNT% ( + echo [SUCCESS] All tests passed! +) else ( + echo [FAILURE] Some tests failed +) +echo ======================================== + +pause +''' + + with open(runner_path, 'w', encoding='utf-8') as f: + f.write(content) + + self.print_success(f"测试运行器已创建: {runner_path}") + self.print_info(f"使用方法: cd build && run_tests.bat") + + return runner_path + + def generate_report(self, args, build_time, tests_passed): + """生成构建报告""" + self.print_header("构建完成") + + print(f"项目: {self.project_root.name}") + print(f"构建类型: {args.build_type}") + print(f"编译器: {args.compiler}") + print(f"并行作业: {args.jobs}") + print(f"总耗时: {build_time:.1f}秒") + print(f"测试结果: {'全部通过' if tests_passed else '有失败'}") + + # 显示生成的可执行文件 + bin_dir = self.build_dir / "bin" / args.build_type + if bin_dir.exists(): + print(f"\n生成的可执行文件:") + for exe in sorted(bin_dir.glob("*.exe")): + size_mb = exe.stat().st_size / (1024 * 1024) + print(f" • {exe.name} ({size_mb:.2f} MB)") + + # 显示测试运行器信息 + runner_path = self.build_dir / "run_tests.bat" + if runner_path.exists(): + print(f"\n独立测试运行器:") + print(f" • {runner_path.name}") + print(f" 在Intel oneAPI环境中运行所有测试") + + def run(self): + """运行构建系统""" + parser = argparse.ArgumentParser( + description="Fortran CFD项目构建工具", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认构建 + %(prog)s --clean # 清理后构建 + %(prog)s --build-type Release # Release构建 + %(prog)s --no-tests # 只构建,不运行测试 + %(prog)s -j8 --verbose # 8线程并行构建,详细输出 + """ + ) + + parser.add_argument("--build-type", choices=["Debug", "Release"], + default="Debug", help="构建类型") + parser.add_argument("--compiler", choices=["ifx", "ifort"], + default="ifx", help="Fortran编译器") + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-tests", action="store_true", + help="跳过测试") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + + args = parser.parse_args() + + # 开始构建 + start_time = time.time() + + self.print_header("Fortran CFD 项目构建系统") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 检查前提条件 + if not self.check_prerequisites(): + if not args.force: + return 1 + + # 2. 设置Intel环境 + if not self.setup_intel_environment(args): + if not args.force: + return 1 + + # 3. 清理目录 + if not self.clean_build_directory(args): + if not args.force: + return 1 + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 4. 配置CMake + if not self.configure_cmake(args): + if not args.force: + return 1 + + # 5. 构建项目 + if not self.build_project(args): + if not args.force: + return 1 + + # 6. 运行测试和创建测试运行器 + tests_passed = True + if not args.no_tests: + tests_passed = self.run_tests(args) + + # 创建测试运行器 + self.create_test_runner(args) # 传递 args 参数 + + # 7. 生成报告 + build_time = time.time() - start_time + self.generate_report(args, build_time, tests_passed) + + return 0 if tests_passed else 1 + + except KeyboardInterrupt: + self.print_error("\n构建被用户中断") + return 1 + except Exception as e: + self.print_error(f"构建过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + builder = BuildSystem() + return builder.run() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/scripts/requirements.txt b/example/1d-linear-convection/weno3/fortran/registry/02f/scripts/requirements.txt new file mode 100644 index 000000000..d21a12b45 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/scripts/requirements.txt @@ -0,0 +1,2 @@ +# 构建脚本的Python依赖(可选) +# 目前没有特殊依赖,保持空文件或添加未来可能需要的包 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02f/src/CMakeLists.txt new file mode 100644 index 000000000..d8cbd5f31 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/src/CMakeLists.txt @@ -0,0 +1,16 @@ +# ==================== src\CMakeLists.txt ==================== + +# src/CMakeLists.txt +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(base) +add_subdirectory(core) +add_subdirectory(infrastructure) +add_subdirectory(numerics) +add_subdirectory(manager) + +message(STATUS "源代码目录配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/src/base/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02f/src/base/CMakeLists.txt new file mode 100644 index 000000000..d537affd7 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/src/base/CMakeLists.txt @@ -0,0 +1,15 @@ +# src/base/CMakeLists.txt +message(STATUS "Configuring base module...") + +add_library(base STATIC + modules.f90 +) + +set_target_properties(base PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Base module configured") + +# src/core/CMakeLists.txt +message(STATUS "Configuring core module...") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/src/base/modules.f90 b/example/1d-linear-convection/weno3/fortran/registry/02f/src/base/modules.f90 new file mode 100644 index 000000000..99ef33372 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/src/base/modules.f90 @@ -0,0 +1,42 @@ +! src/base/modules.f90 +module base_modules + use, intrinsic :: iso_fortran_env, only: real64, int32 + + implicit none + + ! 公开所有基本类型 + public :: wp, ip, string_len, max_name_len + public :: cfd_config_base, component_info + + ! 工作精度类型 + integer, parameter :: wp = real64 + integer, parameter :: ip = int32 + + ! 字符串长度常量(使用数值常量) + integer, parameter :: string_len = 100 + integer, parameter :: max_name_len = 32 + + ! 基础配置类型 + type :: cfd_config_base + character(len=max_name_len) :: ic_type = "step" + character(len=max_name_len) :: recon_scheme = "eno" + character(len=max_name_len) :: flux_type = "rusanov" + integer(ip) :: rk_order = 1 + real(wp) :: wave_speed = 1.0_wp + real(wp) :: final_time = 0.625_wp + real(wp) :: dt = 0.025_wp + character(len=max_name_len) :: boundary_type = "periodic" + integer(ip) :: spatial_order = 2 + character(len=max_name_len) :: equation_type = "linear_advection" + character(len=max_name_len) :: problem_type = "linear_advection" + logical :: verbose = .true. + end type cfd_config_base + + ! 组件信息类型 + type :: component_info + character(len=max_name_len) :: category = "" + character(len=max_name_len) :: name = "" + integer(ip) :: order = 0 + end type component_info + +end module base_modules \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02f/src/core/CMakeLists.txt new file mode 100644 index 000000000..d8b8df064 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/src/core/CMakeLists.txt @@ -0,0 +1,14 @@ +# src/core/CMakeLists.txt +message(STATUS "Configuring core module...") + +add_library(core STATIC + registry.f90 +) + +target_link_libraries(core PRIVATE base) + +set_target_properties(core PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Core module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/src/core/factory_base.f90 b/example/1d-linear-convection/weno3/fortran/registry/02f/src/core/factory_base.f90 new file mode 100644 index 000000000..302418a15 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/src/core/factory_base.f90 @@ -0,0 +1,57 @@ +! src/core/factory_base.f90 +module factory_base_module + use base_modules, only: wp, ip + use registry_module, only: create_component, has_component + + implicit none + private + public :: wp, ip, factory_base, factory_create + + ! 工厂基类 + type :: factory_base + character(len=max_name_length) :: category = "" + contains + procedure :: create => factory_base_create + procedure :: get_available => factory_base_get_available + end type factory_base + + ! 便捷函数类型 + abstract interface + function factory_function_interface(category, name) result(instance) + import :: wp + character(len=*), intent(in) :: category, name + class(*), allocatable :: instance + end function factory_function_interface + end interface + +contains + + ! 创建工厂实例 + function factory_create(category) result(factory) + character(len=*), intent(in) :: category + type(factory_base) :: factory + factory%category = trim(category) + end function factory_create + + ! 工厂创建方法 + function factory_base_create(this, name) result(instance) + class(factory_base), intent(in) :: this + character(len=*), intent(in) :: name + class(*), allocatable :: instance + + instance = create_component(this%category, name) + end function factory_base_create + + ! 获取可用组件列表(简化版) + subroutine factory_base_get_available(this, names, count) + class(factory_base), intent(in) :: this + character(len=*), allocatable, intent(out) :: names(:) + integer(ip), intent(out) :: count + + ! 这里需要实现从注册表获取列表的逻辑 + ! 暂时返回空列表 + count = 0 + allocate(character(len=max_name_length) :: names(0)) + end subroutine factory_base_get_available + +end module factory_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/02f/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..a960167c3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/src/core/factory_interfaces.f90 @@ -0,0 +1,17 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, factory_procedure + + ! Factory procedure interface + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/02f/src/core/registry.f90 new file mode 100644 index 000000000..acc63edb4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/src/core/registry.f90 @@ -0,0 +1,203 @@ +! src/core/registry.f90 +module registry_module + use base_modules, only: wp, ip, max_name_len, component_info + + implicit none + private + + ! 明确公开所有需要的接口 + public :: wp, ip ! 类型参数 + public :: component_info ! 类型 + public :: registry_init, registry_cleanup ! 初始化/清理 + public :: register_component_simple ! 注册组件 + public :: has_component_simple ! 检查组件 + public :: list_components ! 列出组件 + public :: registry_is_initialized ! 检查初始化状态 ← 新增 + public :: registry_get_size ! 获取大小 ← 新增 + + ! 全局注册表 + type :: component_registry + type(component_info), allocatable :: components(:) + integer(ip) :: count = 0 + integer(ip) :: capacity = 100 + logical :: initialized = .false. + logical :: verbose = .true. + end type component_registry + + type(component_registry) :: registry + +contains + + ! ==================== 公共API ==================== + + subroutine registry_init(verbose) + logical, optional, intent(in) :: verbose + + if (registry%initialized) then + if (registry%verbose) then + print *, "[REGISTRY] Already initialized" + end if + return + end if + + if (present(verbose)) then + registry%verbose = verbose + end if + + allocate(registry%components(registry%capacity)) + registry%initialized = .true. + + if (registry%verbose) then + print *, "[REGISTRY] Initialized with capacity:", registry%capacity + end if + end subroutine registry_init + + subroutine registry_cleanup() + if (allocated(registry%components)) then + deallocate(registry%components) + end if + registry%initialized = .false. + registry%count = 0 + + if (registry%verbose) then + print *, "[REGISTRY] Cleaned up" + end if + end subroutine registry_cleanup + + subroutine register_component_simple(category, name, order) + character(len=*), intent(in) :: category, name + integer(ip), optional, intent(in) :: order + + integer(ip) :: i + type(component_info) :: info + + if (.not. registry%initialized) then + call registry_init() + end if + + ! 检查是否已存在 + do i = 1, registry%count + if (trim(registry%components(i)%category) == trim(category) .and. & + trim(registry%components(i)%name) == trim(name)) then + if (registry%verbose) then + print *, "[WARN] Overwriting component: ", trim(category), ".", trim(name) + end if + + ! 更新 + if (present(order)) then + registry%components(i)%order = order + else + registry%components(i)%order = 0 + end if + return + end if + end do + + ! 扩展数组 + if (registry%count >= registry%capacity) then + call expand_registry() + end if + + ! 添加新组件 + registry%count = registry%count + 1 + + info%category = trim(category) + info%name = trim(name) + info%order = 0 + if (present(order)) then + info%order = order + end if + + registry%components(registry%count) = info + + if (registry%verbose) then + print *, "[OK] Registered simple: ", trim(category), ".", trim(name) + end if + end subroutine register_component_simple + + logical function has_component_simple(category, name) + character(len=*), intent(in) :: category, name + + integer(ip) :: i + + has_component_simple = .false. + + if (.not. registry%initialized) return + + do i = 1, registry%count + if (trim(registry%components(i)%category) == trim(category) .and. & + trim(registry%components(i)%name) == trim(name)) then + has_component_simple = .true. + return + end if + end do + end function has_component_simple + + subroutine list_components(category) + character(len=*), optional, intent(in) :: category + + integer(ip) :: i, count + + if (.not. registry%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + if (registry%count == 0) then + print *, "[INFO] No components registered" + return + end if + + count = 0 + print *, "=== Registry Contents ===" + do i = 1, registry%count + if (.not. present(category) .or. & + trim(registry%components(i)%category) == trim(category)) then + call print_component_info(registry%components(i)) + count = count + 1 + end if + end do + + print *, "Total:", count, "components" + print *, "==========================" + end subroutine list_components + + ! ==================== 新增函数 ==================== + + logical function registry_is_initialized() + ! 检查注册表是否已初始化 + registry_is_initialized = registry%initialized + end function registry_is_initialized + + integer(ip) function registry_get_size() + ! 获取注册表中的组件数量 + registry_get_size = registry%count + end function registry_get_size + + ! ==================== 内部辅助函数 ==================== + + subroutine expand_registry() + type(component_info), allocatable :: temp(:) + + registry%capacity = registry%capacity * 2 + allocate(temp(registry%capacity)) + temp(1:registry%count) = registry%components(1:registry%count) + call move_alloc(temp, registry%components) + + if (registry%verbose) then + print *, "[INFO] Registry expanded to capacity:", registry%capacity + end if + end subroutine expand_registry + + subroutine print_component_info(info) + type(component_info), intent(in) :: info + + if (info%order > 0) then + print *, " [", trim(info%category), ".", trim(info%name), & + " (order:", info%order, ")]" + else + print *, " [", trim(info%category), ".", trim(info%name), "]" + end if + end subroutine print_component_info + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02f/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..70cbbd2f3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/src/infrastructure/CMakeLists.txt @@ -0,0 +1,17 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "Configuring infrastructure module...") + +add_library(infrastructure STATIC + config.f90 + mesh.f90 + domain.f90 # 新增 + solution.f90 # 新增 +) + +target_link_libraries(infrastructure PRIVATE base) + +set_target_properties(infrastructure PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Infrastructure module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/02f/src/infrastructure/config.f90 new file mode 100644 index 000000000..719e14d25 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/src/infrastructure/config.f90 @@ -0,0 +1,64 @@ +! src/infrastructure/config.f90 +module config_module + use base_modules, only: wp, ip, max_name_len, cfd_config_base + + implicit none + public :: wp, ip, cfd_config, config_print, config_with_reconstruction + + ! 扩展配置类型 + type, extends(cfd_config_base) :: cfd_config + real(wp) :: left_boundary_value = 1.0_wp + real(wp) :: right_boundary_value = 2.0_wp + real(wp) :: domain_length = 2.0_wp + end type cfd_config + +contains + + subroutine config_print(cfg) + type(cfd_config), intent(in) :: cfg + + print *, "=== CFD Configuration ===" + print *, "Initial condition: ", trim(cfg%ic_type) + print *, "Reconstruction: ", trim(cfg%recon_scheme), " (order:", cfg%spatial_order, ")" + print *, "Flux type: ", trim(cfg%flux_type) + print *, "Time integration: RK", cfg%rk_order + print *, "Wave speed: ", cfg%wave_speed + print *, "Final time: ", cfg%final_time + print *, "Time step: ", cfg%dt + print *, "Boundary: ", trim(cfg%boundary_type) + print *, "===============================" + end subroutine config_print + + subroutine config_with_reconstruction(cfg, scheme, order) + type(cfd_config), intent(inout) :: cfg + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + integer :: i + + ! 转换为小写 + cfg%recon_scheme = scheme + do i = 1, len_trim(cfg%recon_scheme) + if (cfg%recon_scheme(i:i) >= 'A' .and. cfg%recon_scheme(i:i) <= 'Z') then + cfg%recon_scheme(i:i) = char(ichar(cfg%recon_scheme(i:i)) + 32) + end if + end do + + ! 设置阶数 + if (present(order)) then + cfg%spatial_order = order + else + if (index(cfg%recon_scheme, 'weno') > 0) then + cfg%spatial_order = 5 + else if (trim(cfg%recon_scheme) == 'eno') then + cfg%spatial_order = 3 + end if + end if + + if (cfg%verbose) then + print *, "[CONFIG] Reconstruction: ", trim(cfg%recon_scheme), & + " Order: ", cfg%spatial_order + end if + end subroutine config_with_reconstruction + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/src/infrastructure/domain.f90 b/example/1d-linear-convection/weno3/fortran/registry/02f/src/infrastructure/domain.f90 new file mode 100644 index 000000000..c3662f039 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/src/infrastructure/domain.f90 @@ -0,0 +1,102 @@ +! src/infrastructure/domain.f90 +module domain_module + use base_modules, only: wp, ip, max_name_len + use config_module, only: cfd_config + use mesh_module, only: mesh_type + + implicit none + private + public :: wp, ip, domain_type, domain_create, is_physical_cell + + type :: domain_type + type(cfd_config), pointer :: config => null() + type(mesh_type), pointer :: mesh => null() + integer(ip) :: nghosts = 0 + integer(ip) :: ist = 1 ! 物理区域起始索引(1-based) + integer(ip) :: ied = 1 ! 物理区域结束索引(exclusive) + integer(ip) :: ntcells = 0 ! 总单元数(含ghost) + contains + procedure :: print_info => domain_print_info + procedure :: get_physical_indices => domain_get_physical_indices + end type domain_type + +contains + + function domain_create(config, mesh) result(domain) + type(cfd_config), target, intent(in) :: config + type(mesh_type), target, intent(in) :: mesh + type(domain_type) :: domain + + domain%config => config + domain%mesh => mesh + + ! 计算ghost层数(参考Julia的_calc_nghosts) + domain%nghosts = calc_nghosts(config) + domain%ist = domain%nghosts + 1 + domain%ied = domain%ist + mesh%ncells + domain%ntcells = mesh%ncells + 2 * domain%nghosts + + if (config%verbose) then + print *, "[DOMAIN] Created:" + print *, " Ghost layers: ", domain%nghosts + print *, " Physical cells: ", domain%ist, " to ", domain%ied - 1 + print *, " Total cells: ", domain%ntcells + end if + end function domain_create + + function calc_nghosts(config) result(nghosts) + type(cfd_config), intent(in) :: config + integer(ip) :: nghosts + + character(len=max_name_len) :: scheme + + scheme = config%recon_scheme + + if (scheme == "eno") then + nghosts = config%spatial_order + else if (index(scheme, "weno") > 0) then + nghosts = config%spatial_order / 2 + 1 + else + print *, "[WARNING] Unknown scheme, using default nghosts=2" + nghosts = 2 + end if + + if (nghosts <= 0) then + print *, "[ERROR] Invalid nghosts: ", nghosts + nghosts = 2 + end if + end function calc_nghosts + + logical function is_physical_cell(this, idx) + class(domain_type), intent(in) :: this + integer(ip), intent(in) :: idx + is_physical_cell = (idx >= this%ist .and. idx < this%ied) + end function is_physical_cell + + function domain_get_physical_indices(this) result(indices) + class(domain_type), intent(in) :: this + integer(ip), allocatable :: indices(:) + integer(ip) :: i, count + + count = this%ied - this%ist + allocate(indices(count)) + + do i = 1, count + indices(i) = this%ist + i - 1 + end do + end function domain_get_physical_indices + + subroutine domain_print_info(this) + class(domain_type), intent(in) :: this + + print *, "=== Domain Information ===" + print *, "Configuration: ", trim(this%config%recon_scheme), & + " order ", this%config%spatial_order + print *, "Ghost layers: ", this%nghosts + print *, "Physical cells: ", this%ist, " to ", this%ied - 1 + print *, "Total cells: ", this%ntcells + print *, "Mesh cells: ", this%mesh%ncells + print *, "==========================" + end subroutine domain_print_info + +end module domain_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/02f/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..f810f3a1b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/src/infrastructure/mesh.f90 @@ -0,0 +1,73 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use base_modules, only: wp, ip + + implicit none + public :: wp, ip, mesh_type, mesh_init, mesh_print_info + + ! 网格类型 + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer(ip) :: ncells = 40 + integer(ip) :: nnodes + integer(ip) :: nx + real(wp), allocatable :: x(:) ! 节点坐标 + real(wp), allocatable :: xcc(:) ! 单元中心坐标 + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer(ip), optional, intent(in) :: ncells + + integer(ip) :: i + + ! 设置参数 + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! 计算 + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! 分配内存 + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! 生成节点坐标 + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! 生成单元中心坐标 + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== Mesh Information ===" + print *, "Domain: [", this%xmin, ", ", this%xmax, "]" + print *, "Cells: ", this%ncells + print *, "Nodes: ", this%nnodes + print *, "dx: ", this%dx + print *, "L: ", this%L + print *, "========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/src/infrastructure/solution.f90 b/example/1d-linear-convection/weno3/fortran/registry/02f/src/infrastructure/solution.f90 new file mode 100644 index 000000000..ce88fd8a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/src/infrastructure/solution.f90 @@ -0,0 +1,131 @@ +! src/infrastructure/solution.f90 +module solution_module + use base_modules, only: wp, ip + use domain_module, only: domain_type + + implicit none + private + public :: wp, ip, solution_type, solution_create, solution_reset + + type :: solution_type + type(domain_type), pointer :: domain => null() + real(wp), allocatable :: u(:) ! 当前解(含ghost) + real(wp), allocatable :: un(:) ! 旧解 + real(wp), allocatable :: q_face_left(:) ! 左界面值 + real(wp), allocatable :: q_face_right(:)! 右界面值 + real(wp), allocatable :: flux(:) ! 通量 + real(wp), allocatable :: res(:) ! 残差 + contains + procedure :: initialize => solution_initialize + procedure :: update_old_field => solution_update_old_field + procedure :: print_info => solution_print_info + procedure :: reset => solution_reset_instance + end type solution_type + +contains + + function solution_create(domain) result(solution) + type(domain_type), target, intent(in) :: domain + type(solution_type) :: solution + + integer(ip) :: ncells, nnodes, ntcells + + solution%domain => domain + + ncells = domain%mesh%ncells + nnodes = domain%mesh%nnodes + ntcells = domain%ntcells + + ! 分配数组(与Julia solution.jl一致) + allocate(solution%u(ntcells), source=0.0_wp) + allocate(solution%un(ntcells), source=0.0_wp) + allocate(solution%q_face_left(nnodes), source=0.0_wp) + allocate(solution%q_face_right(nnodes), source=0.0_wp) + allocate(solution%flux(nnodes), source=0.0_wp) + allocate(solution%res(ncells), source=0.0_wp) + + if (domain%config%verbose) then + print *, "[SOLUTION] Created:" + print *, " u size: ", size(solution%u), " (with ghosts)" + print *, " flux size: ", size(solution%flux) + print *, " res size: ", size(solution%res) + end if + end function solution_create + + subroutine solution_initialize(this, initial_values) + class(solution_type), intent(inout) :: this + real(wp), intent(in), optional :: initial_values(:) + + integer(ip) :: i, idx + type(domain_type), pointer :: domain + + domain => this%domain + + if (present(initial_values)) then + ! 应用初始值到物理区域 + do i = domain%ist, domain%ied - 1 + idx = i - domain%ist + 1 + if (idx <= size(initial_values)) then + this%u(i) = initial_values(idx) + end if + end do + else + ! 默认为0 + this%u = 0.0_wp + end if + + ! 同步旧场(与Julia的update_old_field一致) + call this%update_old_field() + + if (domain%config%verbose) then + print *, "[SOLUTION] Initialized" + print *, " u range: ", minval(this%u), " to ", maxval(this%u) + end if + end subroutine solution_initialize + + subroutine solution_update_old_field(this) + class(solution_type), intent(inout) :: this + this%un = this%u ! 与Julia的 un .= u 一致 + end subroutine solution_update_old_field + + subroutine solution_reset_instance(this) + class(solution_type), intent(inout) :: this + call solution_reset(this) + end subroutine solution_reset_instance + + subroutine solution_reset(solution) + type(solution_type), intent(inout) :: solution + + if (allocated(solution%u)) solution%u = 0.0_wp + if (allocated(solution%un)) solution%un = 0.0_wp + if (allocated(solution%q_face_left)) solution%q_face_left = 0.0_wp + if (allocated(solution%q_face_right)) solution%q_face_right = 0.0_wp + if (allocated(solution%flux)) solution%flux = 0.0_wp + if (allocated(solution%res)) solution%res = 0.0_wp + + if (associated(solution%domain) .and. solution%domain%config%verbose) then + print *, "[SOLUTION] Reset" + end if + end subroutine solution_reset + + subroutine solution_print_info(this) + class(solution_type), intent(in) :: this + + print *, "=== Solution Information ===" + print *, "Arrays:" + print *, " u: ", size(this%u), " elements" + print *, " un: ", size(this%un), " elements" + print *, " q_face_left: ", size(this%q_face_left), " elements" + print *, " q_face_right: ", size(this%q_face_right), " elements" + print *, " flux: ", size(this%flux), " elements" + print *, " res: ", size(this%res), " elements" + + if (allocated(this%u)) then + print *, "Values:" + print *, " u min/max: ", minval(this%u), maxval(this%u) + print *, " un min/max: ", minval(this%un), maxval(this%un) + end if + print *, "============================" + end subroutine solution_print_info + +end module solution_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/src/manager/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02f/src/manager/CMakeLists.txt new file mode 100644 index 000000000..00c8bf49b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/src/manager/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/manager/CMakeLists.txt +message(STATUS "配置管理器模块...") + +# 创建管理器库 +add_library(manager STATIC + component_manager.f90 + component_factory.f90 +) + +# 明确依赖关系:管理器依赖所有其他模块 +target_link_libraries(manager + PRIVATE + core + infrastructure + reconstructor + flux +) + +# 设置模块输出目录 +set_target_properties(manager PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "管理器模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/src/manager/component_factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/02f/src/manager/component_factory.f90 new file mode 100644 index 000000000..114fedea5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/src/manager/component_factory.f90 @@ -0,0 +1,127 @@ +! src/manager/component_factory.f90 +module component_factory_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use config_module, only: cfd_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + use eno_reconstructor_module, only: eno_reconstructor, create_eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor, create_weno3_reconstructor + use rusanov_flux_module, only: rusanov_flux, create_rusanov_flux + + implicit none + private + public :: wp, create_reconstructor, create_flux_calculator + + ! 错误代码 + integer, parameter :: CM_SUCCESS = 0 + integer, parameter :: CM_ERROR_UNKNOWN_SCHEME = 1 + integer, parameter :: CM_ERROR_UNKNOWN_FLUX = 2 + integer, parameter :: CM_ERROR_INVALID_ORDER = 3 + +contains + + ! ==================== 重构器创建 ==================== + + function create_reconstructor(config, status) result(recon) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(reconstructor_base), allocatable :: recon + + character(len=20) :: scheme + integer :: order, error_code + + scheme = trim(adjustl(config%recon_scheme)) + order = config%spatial_order + + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating reconstructor: ", scheme, " order=", order + end if + + select case(scheme) + case('eno') + allocate(eno_reconstructor :: recon) + select type(recon) + type is(eno_reconstructor) + recon = create_eno_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case('weno3') + allocate(weno3_reconstructor :: recon) + select type(recon) + type is(weno3_reconstructor) + recon = create_weno3_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case default + error_code = CM_ERROR_UNKNOWN_SCHEME + if (config%verbose) then + print *, "[ERROR] Unknown reconstructor scheme: ", scheme + print *, " Available: eno, weno3" + end if + end select + + ! 检查阶数有效性 + if (error_code == CM_SUCCESS) then + if (order < 1) then + error_code = CM_ERROR_INVALID_ORDER + if (config%verbose) then + print *, "[ERROR] Invalid spatial order: ", order + end if + end if + end if + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Reconstructor creation failed" + end if + end function create_reconstructor + + ! ==================== 通量计算器创建 ==================== + + function create_flux_calculator(config, status) result(flux) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(flux_calculator_base), allocatable :: flux + + character(len=20) :: flux_type + integer :: error_code + + flux_type = trim(adjustl(config%flux_type)) + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating flux calculator: ", flux_type + end if + + select case(flux_type) + case('rusanov') + allocate(rusanov_flux :: flux) + select type(flux) + type is(rusanov_flux) + flux = create_rusanov_flux() + flux%wave_speed_default = config%wave_speed + end select + + case default + error_code = CM_ERROR_UNKNOWN_FLUX + if (config%verbose) then + print *, "[ERROR] Unknown flux type: ", flux_type + print *, " Available: rusanov" + end if + end select + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Flux calculator creation failed" + end if + end function create_flux_calculator + +end module component_factory_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/src/manager/component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/02f/src/manager/component_manager.f90 new file mode 100644 index 000000000..9e095c257 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/src/manager/component_manager.f90 @@ -0,0 +1,75 @@ +! src/manager/component_manager.f90 +module component_manager_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use config_module, only: cfd_config + use component_factory_module, only: create_reconstructor, create_flux_calculator + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + + implicit none + private + public :: wp, component_manager_info, validate_config + public :: create_reconstructor, create_flux_calculator + +contains + + ! ==================== 配置验证 ==================== + + function validate_config(config) result(is_valid) + type(cfd_config), intent(in) :: config + logical :: is_valid + + integer :: status + class(reconstructor_base), allocatable :: test_recon + class(flux_calculator_base), allocatable :: test_flux + + is_valid = .false. + + ! 测试创建重构器 + test_recon = create_reconstructor(config, status) + if (status /= 0) then + if (config%verbose) then + print *, "[CONFIG VALIDATION] Invalid reconstructor configuration" + end if + return + end if + + ! 测试创建通量计算器 + test_flux = create_flux_calculator(config, status) + if (status /= 0) then + if (config%verbose) then + print *, "[CONFIG VALIDATION] Invalid flux configuration" + end if + return + end if + + ! 清理测试组件 + if (allocated(test_recon)) deallocate(test_recon) + if (allocated(test_flux)) deallocate(test_flux) + + is_valid = .true. + + if (config%verbose) then + print *, "[CONFIG VALIDATION] Configuration is valid" + end if + end function validate_config + + ! ==================== 信息显示 ==================== + + subroutine component_manager_info() + print *, "=== Component Manager ===" + print *, "Available reconstructors:" + print *, " - eno (orders: 1-7)" + print *, " - weno3 (order: 3)" + print *, "" + print *, "Available flux calculators:" + print *, " - rusanov" + print *, "" + print *, "Features:" + print *, " - Configuration validation" + print *, " - Component creation from config" + print *, " - Error handling with status codes" + print *, "=========================" + end subroutine component_manager_info + +end module component_manager_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..66874a7ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/CMakeLists.txt @@ -0,0 +1,7 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..3fde7746b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,28 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +# Start with just the base module +add_library(flux STATIC + base.f90 + rusanov.f90 +) + +#target_link_libraries(flux PRIVATE core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 明确设置不使用 /Qm64 选项 +if(CMAKE_Fortran_COMPILER_ID MATCHES "IntelLLVM|Intel") + set_target_properties(flux PROPERTIES + Fortran_FLAGS "${CMAKE_Fortran_FLAGS} /Qm64" # 明确设置,避免继承问题 + ) +endif() + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/flux/base.f90 new file mode 100644 index 000000000..7080a7ae4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/flux/base.f90 @@ -0,0 +1,30 @@ +!src/numerics/flux/base.f90 +module flux_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, flux_calculator_base + + ! Base flux calculator type + type :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure :: info => flux_info + procedure :: print_basic_info => flux_print_basic ! 添加辅助方法 + end type flux_calculator_base + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "Flux calculator information:" + print *, " Name: ", trim(this%name) + end subroutine flux_info + + subroutine flux_print_basic(this) + class(flux_calculator_base), intent(in) :: this + print *, " Name: ", trim(this%name) + end subroutine flux_print_basic + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/flux/rusanov.f90 b/example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/flux/rusanov.f90 new file mode 100644 index 000000000..daa9e3bb5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/flux/rusanov.f90 @@ -0,0 +1,41 @@ +! src/numerics/flux/rusanov.f90 +module rusanov_flux_module + use, intrinsic :: iso_fortran_env, only: real64 + use flux_base_module, only: flux_calculator_base + implicit none + + private + public :: real64, rusanov_flux, create_rusanov_flux + + type, extends(flux_calculator_base) :: rusanov_flux + real(real64) :: wave_speed_default = 1.0_real64 + contains + procedure :: info => rusanov_info + end type rusanov_flux + + ! 构造函数接口 + interface rusanov_flux + module procedure create_rusanov_flux + end interface + +contains + + ! 构造函数 + type(rusanov_flux) function create_rusanov_flux() result(this) + this%name = "Rusanov" + this%wave_speed_default = 1.0_real64 + end function create_rusanov_flux + + subroutine rusanov_info(this) + class(rusanov_flux), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Flux calculator information:" + call this%print_basic_info() + + ! 添加Rusanov特有信息 + print *, " Type: Rusanov flux" + print *, " Default wave speed: ", this%wave_speed_default + end subroutine rusanov_info + +end module rusanov_flux_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..5e4b938d5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,22 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "Configuring reconstructor module...") + +# Start with just the base module +add_library(reconstructor STATIC + base.f90 + eno.f90 + weno3.f90 +) + +target_link_libraries(reconstructor PRIVATE core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Reconstructor module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..53798d021 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/reconstructor/base.f90 @@ -0,0 +1,33 @@ +!src/numerics/reconstructor/base.f90 +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, reconstructor_base + + ! Base reconstructor type + type :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + contains + procedure :: info => reconstructor_info + procedure :: print_basic_info => reconstructor_print_basic ! 添加一个辅助方法 + end type reconstructor_base + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + print *, "Reconstructor information:" + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_info + + subroutine reconstructor_print_basic(this) + class(reconstructor_base), intent(in) :: this + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_print_basic + +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/reconstructor/eno.f90 b/example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/reconstructor/eno.f90 new file mode 100644 index 000000000..f973e8b32 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/reconstructor/eno.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/eno.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, eno_reconstructor, create_eno_reconstructor ! ← 添加这个 + + type, extends(reconstructor_base) :: eno_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => eno_info + end type eno_reconstructor + + ! 构造函数接口 + interface eno_reconstructor + module procedure create_eno_reconstructor + end interface + +contains + + ! 构造函数 + type(eno_reconstructor) function create_eno_reconstructor() result(this) + this%name = "ENO" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_eno_reconstructor + + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加ENO特有信息 + print *, " Type: ENO reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/reconstructor/weno3.f90 b/example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/reconstructor/weno3.f90 new file mode 100644 index 000000000..d5b7a7477 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/src/numerics/reconstructor/weno3.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/weno3.f90 +module weno3_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno3_reconstructor, create_weno3_reconstructor + + type, extends(reconstructor_base) :: weno3_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno3_info + end type weno3_reconstructor + + ! 构造函数接口 + interface weno3_reconstructor + module procedure create_weno3_reconstructor + end interface + +contains + + ! 构造函数 + type(weno3_reconstructor) function create_weno3_reconstructor() result(this) + this%name = "WENO3" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_weno3_reconstructor + + subroutine weno3_info(this) + class(weno3_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加WENO3特有信息 + print *, " Type: WENO-3 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno3_info + +end module weno3_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02f/tests/CMakeLists.txt new file mode 100644 index 000000000..d23f7fe4a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/tests/CMakeLists.txt @@ -0,0 +1,78 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +# +#message(STATUS "CMAKE_Fortran_MODULE_DIRECTORY=${CMAKE_Fortran_MODULE_DIRECTORY}") +# +add_executable(test_minimal_simple test_minimal_simple.f90) +target_link_libraries(test_minimal_simple + PRIVATE + core # 必须链接core库 + infrastructure +) + + +add_executable(test_simple_link test_simple_link.f90) +target_link_libraries(test_simple_link + PRIVATE + reconstructor + flux +) + + +add_executable(test_factory_simple test_factory_simple.f90) +target_link_libraries(test_factory_simple + PRIVATE + core + infrastructure + reconstructor + flux +) + + +add_executable(test_component_manager test_component_manager.f90) + +target_link_libraries(test_component_manager + PRIVATE + manager # ← 链接到新的管理器库 + infrastructure +) +# +#add_executable(test_architecture test_architecture.f90) +#target_link_libraries(test_architecture +# PRIVATE +# core +# infrastructure +# manager +#) + +#add_executable(test_solver_framework test_solver_framework.f90) +#target_link_libraries(test_solver_framework +# PRIVATE +# core +# infrastructure +# manager +#) +# +#add_executable(test_cfd_architecture test_cfd_architecture.f90) +#target_link_libraries(test_cfd_architecture +# PRIVATE +# base +# core +# infrastructure +#) + + +add_executable(test_basic_only test_basic_only.f90) +target_link_libraries(test_basic_only + PRIVATE + infrastructure + core +) + +add_executable(test_domain_solution test_domain_solution.f90) +target_link_libraries(test_domain_solution + PRIVATE + infrastructure + core +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_architecture.f90 b/example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_architecture.f90 new file mode 100644 index 000000000..1c40d4677 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_architecture.f90 @@ -0,0 +1,117 @@ +! tests/test_architecture.f90 +program test_architecture + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module + use config_module + use mesh_module + use component_manager_module + + implicit none + + print *, "=== CFD架构测试 ===" + print *, "" + + ! 测试1: 基本系统 + call test_basic_systems() + + ! 测试2: 组件注册 + call test_registry_integration() + + ! 测试3: 配置验证 + call test_config_validation() + + ! 测试4: 完整流程 + call test_full_workflow() + + print *, "" + print *, "=== 架构测试完成 ===" + +contains + + subroutine test_basic_systems() + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "1. 测试基本系统..." + print *, "-------------------" + + ! 测试配置 + call config_print(config) + print *, "✓ 配置创建成功" + + ! 测试网格 + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "✓ 网格创建成功" + print *, "" + end subroutine test_basic_systems + + subroutine test_registry_integration() + print *, "2. 测试注册系统集成..." + print *, "------------------------" + + call initialize_registry(verbose=.true.) + + ! 注册核心组件 + print *, "注册核心组件:" + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + ! 显示注册内容 + call component_registry%list_simple() + print *, "注册表大小: ", registry_get_size() + + call cleanup_registry() + print *, "✓ 注册系统测试完成" + print *, "" + end subroutine test_registry_integration + + subroutine test_config_validation() + type(cfd_config) :: config + logical :: is_valid + + print *, "3. 测试配置验证..." + print *, "-------------------" + + ! 测试有效配置 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + print *, "测试配置:" + print *, " - 重构器: ", trim(config%recon_scheme), " 阶数: ", config%spatial_order + print *, " - 通量: ", trim(config%flux_type) + print *, " - 波速: ", config%wave_speed + + ! 这里调用验证函数(需要先实现) + ! is_valid = validate_config(config) + print *, "✓ 配置验证接口准备就绪" + print *, "" + end subroutine test_config_validation + + subroutine test_full_workflow() + print *, "4. 测试完整流程..." + print *, "-------------------" + + print *, "步骤1: 初始化系统 ✓" + print *, "步骤2: 创建网格 ✓" + print *, "步骤3: 设置配置 ✓" + print *, "步骤4: 创建组件 (待实现)" + print *, "步骤5: 初始化解 (待实现)" + print *, "步骤6: 时间推进 (待实现)" + print *, "步骤7: 输出结果 (待实现)" + print *, "" + + print *, "✓ 流程框架定义完成" + end subroutine test_full_workflow + +end program test_architecture \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_basic_only.f90 b/example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_basic_only.f90 new file mode 100644 index 000000000..20901ddcc --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_basic_only.f90 @@ -0,0 +1,56 @@ +! tests/test_basic_only.f90 +program test_basic_only + ! 只测试最基本的功能,不依赖复杂模块 + use config_module, only: cfd_config, config_print, wp + use mesh_module, only: mesh_type + use registry_module, only: registry_init, registry_cleanup, & + register_component_simple, list_components + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "=== BASIC TEST - Minimal Functionality ===" + print *, "" + + ! 测试1: 配置 + print *, "1. Testing configuration..." + print *, "----------------------------" + call config_print(config) + print *, "" + + ! 测试2: 网格 + print *, "2. Testing mesh..." + print *, "------------------" + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=5) + print *, "Mesh initialized:" + print *, " Cells: ", mesh%ncells + print *, " Nodes: ", mesh%nnodes + print *, " dx: ", mesh%dx + print *, "" + + ! 测试3: 注册系统 + print *, "3. Testing registry..." + print *, "----------------------" + + call registry_init() + + ! 注册组件(使用简化版本) + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("flux", "rusanov") + + ! 列出组件 + call list_components() + print *, "" + + ! 清理 + call registry_cleanup() + + print *, "=== TEST PASSED ===" + print *, "✓ Configuration works" + print *, "✓ Mesh works" + print *, "✓ Registry works" + +end program test_basic_only \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_cfd_architecture.f90 b/example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_cfd_architecture.f90 new file mode 100644 index 000000000..1c40d4677 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_cfd_architecture.f90 @@ -0,0 +1,117 @@ +! tests/test_architecture.f90 +program test_architecture + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module + use config_module + use mesh_module + use component_manager_module + + implicit none + + print *, "=== CFD架构测试 ===" + print *, "" + + ! 测试1: 基本系统 + call test_basic_systems() + + ! 测试2: 组件注册 + call test_registry_integration() + + ! 测试3: 配置验证 + call test_config_validation() + + ! 测试4: 完整流程 + call test_full_workflow() + + print *, "" + print *, "=== 架构测试完成 ===" + +contains + + subroutine test_basic_systems() + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "1. 测试基本系统..." + print *, "-------------------" + + ! 测试配置 + call config_print(config) + print *, "✓ 配置创建成功" + + ! 测试网格 + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "✓ 网格创建成功" + print *, "" + end subroutine test_basic_systems + + subroutine test_registry_integration() + print *, "2. 测试注册系统集成..." + print *, "------------------------" + + call initialize_registry(verbose=.true.) + + ! 注册核心组件 + print *, "注册核心组件:" + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + ! 显示注册内容 + call component_registry%list_simple() + print *, "注册表大小: ", registry_get_size() + + call cleanup_registry() + print *, "✓ 注册系统测试完成" + print *, "" + end subroutine test_registry_integration + + subroutine test_config_validation() + type(cfd_config) :: config + logical :: is_valid + + print *, "3. 测试配置验证..." + print *, "-------------------" + + ! 测试有效配置 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + print *, "测试配置:" + print *, " - 重构器: ", trim(config%recon_scheme), " 阶数: ", config%spatial_order + print *, " - 通量: ", trim(config%flux_type) + print *, " - 波速: ", config%wave_speed + + ! 这里调用验证函数(需要先实现) + ! is_valid = validate_config(config) + print *, "✓ 配置验证接口准备就绪" + print *, "" + end subroutine test_config_validation + + subroutine test_full_workflow() + print *, "4. 测试完整流程..." + print *, "-------------------" + + print *, "步骤1: 初始化系统 ✓" + print *, "步骤2: 创建网格 ✓" + print *, "步骤3: 设置配置 ✓" + print *, "步骤4: 创建组件 (待实现)" + print *, "步骤5: 初始化解 (待实现)" + print *, "步骤6: 时间推进 (待实现)" + print *, "步骤7: 输出结果 (待实现)" + print *, "" + + print *, "✓ 流程框架定义完成" + end subroutine test_full_workflow + +end program test_architecture \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_component_manager.f90 new file mode 100644 index 000000000..f60c35056 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_component_manager.f90 @@ -0,0 +1,111 @@ +! tests/test_component_manager.f90 +program test_component_manager + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use component_manager_module, only: create_reconstructor, create_flux_calculator + use component_manager_module, only: component_manager_info, validate_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + implicit none + + type(cfd_config) :: config + class(reconstructor_base), allocatable :: recon + class(flux_calculator_base), allocatable :: flux + integer :: status + logical :: is_valid + + print *, "=== Component Manager Test ===" + print *, "" + + ! 显示组件管理器信息 + call component_manager_info() + print *, "" + + ! 测试1: 基本配置 + print *, "1. Testing basic ENO3 + Rusanov configuration..." + print *, "-----------------------------------------------" + + config%verbose = .true. + call config_print(config) + + ! 配置ENO3重构 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + call config_print(config) + print *, "" + + ! 验证配置 + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Configuration is valid" + else + print *, "[ERROR] Configuration is invalid" + end if + print *, "" + + ! 测试2: 创建组件 + print *, "2. Testing component creation..." + print *, "--------------------------------" + + ! 创建重构器(带状态检查) + recon = create_reconstructor(config, status) + if (status == 0) then + print *, "[OK] Reconstructor created successfully" + call recon%info() + else + print *, "[ERROR] Failed to create reconstructor, code:", status + end if + print *, "" + + ! 创建通量计算器 + flux = create_flux_calculator(config, status) + if (status == 0) then + print *, "[OK] Flux calculator created successfully" + call flux%info() + else + print *, "[ERROR] Failed to create flux calculator, code:", status + end if + print *, "" + + ! 测试3: WENO3重构测试 + print *, "3. Testing WENO3 configuration..." + print *, "---------------------------------" + + call config_with_reconstruction(config, "weno3", 3) + + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] WENO3 configuration is valid" + + ! 创建WENO3重构器 + recon = create_reconstructor(config) + call recon%info() + else + print *, "[ERROR] WENO3 configuration is invalid" + end if + print *, "" + + ! 测试4: 错误配置测试 + print *, "4. Testing invalid configuration..." + print *, "-----------------------------------" + + config%recon_scheme = "unknown_scheme" + config%flux_type = "unknown_flux" + + is_valid = validate_config(config) + if (.not. is_valid) then + print *, "[OK] Invalid configuration correctly rejected" + else + print *, "[ERROR] Invalid configuration should have been rejected" + end if + + ! 清理 + if (allocated(recon)) deallocate(recon) + if (allocated(flux)) deallocate(flux) + + print *, "" + print *, "=== Component manager test completed successfully ===" + +end program test_component_manager \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_domain_solution.f90 b/example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_domain_solution.f90 new file mode 100644 index 000000000..ff659bac2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_domain_solution.f90 @@ -0,0 +1,102 @@ +! tests/test_domain_solution.f90 +program test_domain_solution + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create, solution_reset + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + real(wp), allocatable :: initial_values(:) + integer :: i + + print *, "=== Domain and Solution Test ===" + print *, "" + + ! 测试1: 不同重构方案的ghost层计算 + print *, "1. Testing ghost layer calculation..." + print *, "--------------------------------------" + + ! ENO3 + call config_with_reconstruction(config, "eno", 3) + config%verbose = .false. + call mesh%init(ncells=10) + domain = domain_create(config, mesh) + print *, "ENO3: nghosts = ", domain%nghosts, " (expected: 3)" + + ! WENO3 + call config_with_reconstruction(config, "weno3", 3) + domain = domain_create(config, mesh) + print *, "WENO3: nghosts = ", domain%nghosts, " (expected: 2)" + + ! WENO5 + call config_with_reconstruction(config, "weno", 5) + domain = domain_create(config, mesh) + print *, "WENO5: nghosts = ", domain%nghosts, " (expected: 3)" + print *, "" + + ! 测试2: Solution数组 + print *, "2. Testing solution arrays..." + print *, "------------------------------" + + call config_with_reconstruction(config, "eno", 3) + config%verbose = .true. + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=10) + + domain = domain_create(config, mesh) + call domain%print_info() + print *, "" + + solution = solution_create(domain) + call solution%print_info() + print *, "" + + ! 测试3: 初始化和更新 + print *, "3. Testing initialization and update..." + print *, "----------------------------------------" + + allocate(initial_values(mesh%ncells)) + do i = 1, mesh%ncells + initial_values(i) = sin(2.0_wp * 3.14159265358979_wp * mesh%xcc(i) / mesh%L) + end do + + call solution%initialize(initial_values) + print *, "After initialization:" + print *, " u range: ", minval(solution%u), " to ", maxval(solution%u) + print *, " un range: ", minval(solution%un), " to ", maxval(solution%un) + + ! 修改当前解,测试更新 + solution%u = solution%u * 2.0_wp + call solution%update_old_field() + print *, "After update: max|u - un| = ", maxval(abs(solution%u - solution%un)) + print *, "" + + ! 测试4: 重置 + print *, "4. Testing reset..." + print *, "-------------------" + + call solution_reset(solution) + print *, "After reset:" + print *, " u max: ", maxval(abs(solution%u)) + print *, " un max: ", maxval(abs(solution%un)) + print *, " flux max: ", maxval(abs(solution%flux)) + print *, "" + + deallocate(initial_values) + + print *, "=== Test Summary ===" + print *, "✓ Ghost layer calculation works" + print *, "✓ Domain creation works" + print *, "✓ Solution arrays work" + print *, "✓ Initialization works" + print *, "✓ Field update works" + print *, "✓ Reset works" + print *, "" + print *, "Ready for next step: Implementing Physics modules" + +end program test_domain_solution \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_factory_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_factory_simple.f90 new file mode 100644 index 000000000..db65da7cf --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_factory_simple.f90 @@ -0,0 +1,58 @@ +! tests/test_factory_simple.f90 (修复版) +program test_factory_simple + use base_modules, only: wp ! ← 添加这行 + use config_module, only: cfd_config, config_print + use mesh_module, only: mesh_type + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(eno_reconstructor) :: eno + type(weno3_reconstructor) :: weno3 + type(rusanov_flux) :: rusanov + + print *, "=== Factory Pattern Simple Test ===" + print *, "" + + ! Test 1: Basic systems + print *, "1. Testing basic systems..." + print *, "-----------------------------" + call config_print(config) + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 2: Creating reconstructors + print *, "2. Testing reconstructors..." + print *, "------------------------------" + + ! 创建并测试ENO重构器 + print *, "Creating ENO reconstructor..." + eno = eno_reconstructor() ! 使用构造函数 + call eno%info() ! 必须调用info方法 + + print *, "" + print *, "Creating WENO3 reconstructor..." + weno3 = weno3_reconstructor() ! 使用构造函数 + call weno3%info() ! 必须调用info方法 + print *, "" + + ! Test 3: Creating flux calculator + print *, "3. Testing flux calculator..." + print *, "-------------------------------" + + print *, "Creating Rusanov flux calculator..." + rusanov = rusanov_flux() ! 使用构造函数 + call rusanov%info() ! 必须调用info方法 + print *, "" + + print *, "=== Factory pattern simple test completed successfully ===" + +end program test_factory_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_minimal_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_minimal_simple.f90 new file mode 100644 index 000000000..ec03ccf89 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_minimal_simple.f90 @@ -0,0 +1,87 @@ +! tests/test_minimal_simple.f90 +program test_minimal_simple + use base_modules, only: wp ! ← 添加这行 + use registry_module + use config_module + use mesh_module + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Simple Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call registry_init() + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call list_components() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + + if (has_component_simple("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component_simple("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components (simple version) + print *, "5. Testing registry functionality" + print *, "---------------------------------" + print *, "Registry initialized: ", registry_is_initialized() + print *, "Number of components: ", registry_get_size() + print *, "" + + ! Cleanup + call registry_cleanup() + + print *, "=== Simple test completed successfully ===" + +end program test_minimal_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_physics_equations.f90 b/example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_physics_equations.f90 new file mode 100644 index 000000000..379cb8620 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_physics_equations.f90 @@ -0,0 +1,82 @@ +! tests/test_physics_equations.f90 +program test_physics_equations + use precision, only: dp + use linear_convection_equation, only: linear_convection_flux, linear_convection_speed + use linear_convection_problem, only: linear_convection_prob, create_linear_convection_problem + implicit none + + real(dp) :: u, f, a + type(linear_convection_prob) :: prob + real(dp), allocatable :: x(:), u_ic(:) + integer :: i, nx = 10 + + write(*,*) "=== 测试物理方程模块 ===" + + ! 测试线性对流方程 + write(*,*) "1. 测试线性对流方程..." + u = 2.5_dp + f = linear_convection_flux(u) + a = linear_convection_speed() + + write(*,*) " u = ", u + write(*,*) " F(u) = a*u = ", f + write(*,*) " a = ", a + + if (abs(f - 2.5_dp) < 1e-10_dp) then + write(*,*) " ✓ 通量计算正确" + else + write(*,*) " ✗ 通量计算错误" + end if + + ! 测试线性对流问题 + write(*,*) "2. 测试线性对流问题..." + prob = create_linear_convection_problem(wave_speed=2.0_dp, & + ic_type="step", & + boundary_type="periodic") + + write(*,*) " 波速: ", prob%wave_speed + write(*,*) " 初始条件类型: ", trim(prob%ic_type) + write(*,*) " 边界类型: ", trim(prob%boundary_type) + + ! 测试通量方法 + u = 1.5_dp + f = prob%flux(u) + a = prob%speed() + + write(*,*) " 问题通量 F(u) = ", f, " (期望: 3.0)" + write(*,*) " 问题波速 a = ", a, " (期望: 2.0)" + + if (abs(f - 3.0_dp) < 1e-10_dp) then + write(*,*) " ✓ 问题通量计算正确" + else + write(*,*) " ✗ 问题通量计算错误" + end if + + ! 测试初始条件 + write(*,*) "3. 测试初始条件..." + allocate(x(nx), u_ic(nx)) + do i = 1, nx + x(i) = 0.0_dp + (i-1) * 0.2_dp + end do + + call prob%initial_condition(x, u_ic, nx) + + write(*,*) " x 范围: ", x(1), " 到 ", x(nx) + write(*,*) " u_ic 范围: ", minval(u_ic), " 到 ", maxval(u_ic) + + ! 检查阶跃函数 + if (prob%ic_type == "step") then + if (abs(u_ic(1) - 1.0_dp) < 1e-10_dp .and. & + abs(u_ic(6) - 2.0_dp) < 1e-10_dp) then + write(*,*) " ✓ 阶跃初始条件正确" + else + write(*,*) " ✗ 阶跃初始条件错误" + end if + end if + + deallocate(x, u_ic) + + write(*,*) "=== 物理方程模块测试完成 ===" + write(*,*) "下一步: 实现初始条件工厂" + +end program test_physics_equations \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_simple_link.f90 b/example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_simple_link.f90 new file mode 100644 index 000000000..71cc614e8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02f/tests/test_simple_link.f90 @@ -0,0 +1,78 @@ +! tests/test_simple_link.f90 +program test_simple_link + use base_modules, only: wp ! ← 添加这行 + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call registry_init() + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call list_components() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component_simple("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component_simple("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Cleanup + call registry_cleanup() + + print *, "=== Minimal test completed successfully ===" + +end program test_simple_link \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02g/CMakeLists.txt new file mode 100644 index 000000000..ef66d5848 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/README.md b/example/1d-linear-convection/weno3/fortran/registry/02g/README.md new file mode 100644 index 000000000..c4889757b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/README.md @@ -0,0 +1,221 @@ +# Fortran CFD Project + +基于工厂模式和注册系统的CFD求解器框架。 + +## 项目结构 + +``` +fortran_cfd/ +├── CMakeLists.txt # 根CMake配置 +├── scripts/ # 构建脚本 +│ ├── build.py # Python构建脚本(推荐) +│ ├── build.bat # Batch包装脚本 +│ ├── build.ps1 # PowerShell包装脚本 +│ └── requirements.txt # Python依赖 +├── src/ # 源代码 +│ ├── CMakeLists.txt +│ ├── core/ # 核心模块(注册系统、工厂接口) +│ ├── infrastructure/ # 基础设施(配置、网格) +│ └── numerics/ # 数值方法 +├── tests/ # 测试 +│ ├── CMakeLists.txt +│ ├── test_minimal_simple.f90 # 简单测试 +│ └── test_factory.f90 # 工厂模式测试 +└── README.md # 项目说明(本文件) +``` + +## 快速开始 + +### 使用Python脚本(推荐) + +```bash +# 进入脚本目录 +cd scripts + +# 基本构建 +python build.py + +# 清理并构建 +python build.py --clean + +# 发布构建 +python build.py --build-type Release --clean + +# 并行构建(使用所有核心) +python build.py -j + +# 不运行测试 +python build.py --no-tests + +# 查看帮助 +python build.py --help +``` + +### 使用批处理脚本 + +```bash +cd scripts +.\build.bat +``` + +### 使用PowerShell脚本 + +```powershell +cd scripts +.\build.ps1 +``` + +## 手动构建 + +```bash +# 设置Intel环境 +cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && powershell' + +# 配置项目 +mkdir build +cd build +cmake -G "Visual Studio 17 2022" -A x64 -T fortran=ifx .. + +# 构建项目 +cmake --build . --config Debug + +# 运行测试 +Debug\test_simple.exe +Debug\test_factory.exe +``` + +## 功能特性 + +✅ 组件注册系统 +✅ 工厂模式 +✅ ENO/WENO重构器 +✅ Rusanov通量计算器 +✅ 可扩展架构 +✅ 自动化测试 + +## 依赖 + +- Intel oneAPI(包含ifx编译器) +- CMake 3.12+ +- Python 3.6+(仅用于构建脚本) + +## 源代码文件 + +### 核心模块 +- `src/core/registry.f90` - 组件注册系统 +- `src/core/factory_interfaces.f90` - 工厂接口 + +### 基础设施 +- `src/infrastructure/config.f90` - 配置管理 +- `src/infrastructure/mesh.f90` - 网格生成 + +### 数值方法 +- `src/numerics/reconstructor/base.f90` - 重构器基类 +- `src/numerics/reconstructor/eno.f90` - ENO重构器 +- `src/numerics/reconstructor/weno3.f90` - WENO-3重构器 +- `src/numerics/flux/base.f90` - 通量计算器基类 +- `src/numerics/flux/rusanov.f90` - Rusanov通量 + +### 测试 +- `tests/test_minimal_simple.f90` - 简单功能测试 +- `tests/test_factory.f90` - 工厂模式测试 + +## 构建脚本 + +### Python脚本 (build.py) +主构建脚本,支持: +- 自动设置Intel oneAPI环境 +- 参数化构建选项 +- 并行编译 +- 自动化测试 +- 彩色输出 + +### Batch脚本 (build.bat) +Windows批处理包装器,调用Python脚本。 + +### PowerShell脚本 (build.ps1) +PowerShell包装器,调用Python脚本。 + +## 示例输出 + +成功构建后,会看到类似输出: + +``` +============================================================ + Fortran CFD Project Builder +============================================================ + +[1/4] Setting up Intel Fortran compiler... +✓ Intel oneAPI environment configured + +[2/4] Preparing build directory... +✓ Cleaned build directory + +[3/4] Configuring project... + $ cmake -G Visual Studio 17 2022 -A x64 -T fortran=ifx -DCMAKE_BUILD_TYPE=Debug .. +✓ CMake configuration successful + +[4/4] Building project... + $ cmake --build . --config Debug +✓ Build completed in 12.3 seconds + +============================================================ + Running Tests +============================================================ + +[TEST] Simple functionality test... +✓ Simple test passed + +[TEST] Factory pattern test... +✓ Factory test passed + +============================================================ + Build Summary +============================================================ +✓ Build directory: D:\path\to\build +✓ Build type: Debug +✓ Total time: 15.2s +``` + +## 开发指南 + +### 添加新组件 + +1. 在相应模块中创建Fortran源文件 +2. 实现工厂函数 +3. 使用`register_component_with_factory`注册组件 +4. 添加测试 + +### 扩展架构 + +项目采用模块化设计: +1. **注册系统** - 管理所有可用的组件 +2. **工厂模式** - 动态创建组件实例 +3. **抽象接口** - 确保组件兼容性 +4. **配置驱动** - 通过配置文件控制行为 + +## 故障排除 + +### 常见问题 + +1. **Intel环境未找到** + - 检查Intel oneAPI安装路径 + - 手动运行`setvars.bat` + +2. **CMake配置失败** + - 确保使用正确的生成器:`Visual Studio 17 2022` + - 检查Fortran编译器是否可用 + +3. **构建失败** + - 检查依赖项是否完整 + - 查看详细的错误信息 + +### 调试建议 + +- 使用`--verbose`参数获取详细输出 +- 检查`build/CMakeCache.txt`了解配置详情 +- 查看编译器错误日志 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/scripts/build.bat b/example/1d-linear-convection/weno3/fortran/registry/02g/scripts/build.bat new file mode 100644 index 000000000..6fd6dc031 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/scripts/build.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Project Builder +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python build script with full Intel environment support... +echo. + +python build.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Build failed + pause + exit /b 1 +) + +echo. +echo [INFO] Build completed successfully! +echo. +echo [INFO] To run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/scripts/build.py b/example/1d-linear-convection/weno3/fortran/registry/02g/scripts/build.py new file mode 100644 index 000000000..3bf6d5375 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/scripts/build.py @@ -0,0 +1,629 @@ +#!/usr/bin/env python3 +""" +Fortran CFD Project Builder - 完整Python解决方案 +在Python内部处理Intel oneAPI环境配置 +""" + +import os +import sys +import subprocess +import shutil +import argparse +import time +import platform +import tempfile +from pathlib import Path + +class IntelEnvironment: + """Intel oneAPI环境管理器""" + + def __init__(self): + self.setvars_path = None + self.env_vars = {} + + def find_setvars(self): + """查找setvars.bat文件""" + possible_paths = [ + r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat", + r"C:\Program Files\Intel\oneAPI\setvars.bat", + r"C:\Program Files (x86)\Intel\oneAPI\compiler\latest\env\vars.bat", + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\setvars.bat"), + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\compiler\latest\env\vars.bat"), + ] + + for path in possible_paths: + if os.path.exists(path): + self.setvars_path = path + return True + + return False + + def setup_environment(self): + """设置Intel环境""" + if not self.find_setvars(): + return False + + try: + # 创建临时的批处理文件来捕获环境变量 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + f.write(f'@echo off\n') + f.write(f'call "{self.setvars_path}" >nul 2>&1\n') + f.write(f'set\n') # 输出所有环境变量 + temp_bat = f.name + + # 运行批处理文件并捕获输出 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + shell=True + ) + + # 解析环境变量 + for line in result.stdout.split('\n'): + line = line.strip() + if '=' in line: + key, value = line.split('=', 1) + self.env_vars[key.strip()] = value.strip() + + # 清理临时文件 + os.unlink(temp_bat) + + # 更新当前进程的环境变量 + os.environ.update(self.env_vars) + + return True + + except Exception as e: + print(f"设置Intel环境失败: {e}") + return False + + def get_compiler_info(self): + """获取编译器信息""" + info = {} + + # 检查ifx编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + env={**os.environ, **self.env_vars} if self.env_vars else os.environ + ) + + if result.returncode == 0: + for line in result.stdout.split('\n'): + if 'Version' in line or '版本' in line: + info['ifx_version'] = line.strip() + break + except: + pass + + # 检查环境变量 + info['ifx_root'] = self.env_vars.get('IFX_ROOT', '') + info['compiler_root'] = self.env_vars.get('ONEAPI_ROOT', '') + + return info + +class BuildSystem: + """构建系统主类""" + + def __init__(self): + self.project_root = Path(__file__).parent.parent + self.build_dir = self.project_root / "build" + self.intel_env = IntelEnvironment() + + # 设置控制台编码 + if sys.platform == "win32": + try: + import ctypes + # 设置控制台输出为UTF-8 + ctypes.windll.kernel32.SetConsoleOutputCP(65001) + except: + pass + + def print_header(self, text): + """打印标题""" + print(f"\n{'='*70}") + print(f" {text}") + print(f"{'='*70}\n") + + def print_step(self, step, total, message): + """打印步骤""" + print(f"[{step}/{total}] {message}...") + + def print_success(self, message): + """打印成功""" + print(f"\033[92m✓ {message}\033[0m") + + def print_error(self, message): + """打印错误""" + print(f"\033[91m✗ {message}\033[0m") + + def print_warning(self, message): + """打印警告""" + print(f"\033[93m! {message}\033[0m") + + def print_info(self, message): + """打印信息""" + print(f"\033[94mℹ {message}\033[0m") + + def check_prerequisites(self): + """检查前提条件""" + self.print_step(1, 6, "检查前提条件") + + # 检查Python版本 + python_version = sys.version.split()[0] + self.print_info(f"Python版本: {python_version}") + + # 检查平台 + self.print_info(f"平台: {platform.system()} {platform.release()}") + self.print_info(f"处理器核心数: {os.cpu_count()}") + + # 检查CMake + try: + result = subprocess.run( + ["cmake", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + version_line = result.stdout.split('\n')[0] + self.print_success(f"CMake: {version_line}") + else: + self.print_error("CMake未找到") + return False + except FileNotFoundError: + self.print_error("CMake未安装") + return False + + return True + + def setup_intel_environment(self, args): + """设置Intel环境""" + self.print_step(2, 6, "配置Intel oneAPI环境") + + if not self.intel_env.find_setvars(): + self.print_warning("未找到Intel oneAPI setvars.bat") + self.print_info("将尝试使用系统环境中的编译器") + + # 检查是否能直接访问编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + self.print_success("Intel编译器在系统PATH中找到") + return True + else: + self.print_warning("Intel编译器未在PATH中找到") + except: + self.print_warning("无法访问Intel编译器") + + return True # 继续,让CMake自己找编译器 + + # 设置环境 + if self.intel_env.setup_environment(): + compiler_info = self.intel_env.get_compiler_info() + + if compiler_info.get('ifx_version'): + self.print_success(f"Intel Fortran编译器: {compiler_info['ifx_version']}") + elif compiler_info.get('ifx_root'): + self.print_success(f"Intel编译器路径: {compiler_info['ifx_root']}") + else: + self.print_success("Intel oneAPI环境配置完成") + + return True + else: + self.print_warning("Intel环境配置失败,将继续使用系统环境") + return True + + def clean_build_directory(self, args): + """清理构建目录""" + if args.clean and self.build_dir.exists(): + self.print_info("清理构建目录...") + try: + shutil.rmtree(self.build_dir) + self.print_success("构建目录已清理") + except Exception as e: + self.print_error(f"清理失败: {e}") + if not args.force: + return False + return True + + def run_command(self, cmd, cwd=None, check=True, env=None): + """运行命令""" + if isinstance(cmd, list): + cmd_str = ' '.join(str(c) for c in cmd if c) + else: + cmd_str = str(cmd) + + print(f" \033[96m$\033[0m {cmd_str}") + + try: + # 合并环境变量 + exec_env = os.environ.copy() + if env: + exec_env.update(env) + if self.intel_env.env_vars: + exec_env.update(self.intel_env.env_vars) + + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=False, + env=exec_env + ) + + # 处理输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or '完成' in line or '生成' in line: + print(f" \033[92m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + if check and result.returncode != 0: + self.print_error(f"命令执行失败,退出码: {result.returncode}") + return False + + return True + + except Exception as e: + self.print_error(f"命令执行异常: {e}") + return False + + def configure_cmake(self, args): + """配置CMake""" + self.print_step(3, 6, "配置CMake项目") + + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + f"-DCMAKE_BUILD_TYPE={args.build_type}", + ] + + if args.compiler == "ifx": + cmake_cmd.extend(["-T", "fortran=ifx"]) + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + success = self.run_command(cmake_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("CMake配置完成") + else: + self.print_error("CMake配置失败") + + return success + + def build_project(self, args): + """构建项目""" + self.print_step(4, 6, "构建项目") + + build_cmd = [ + "cmake", + "--build", ".", + "--config", args.build_type, + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + success = self.run_command(build_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("项目构建完成") + else: + self.print_error("构建失败") + + return success + + def run_tests_with_environment(self, test_exe): + """运行单个测试,确保有Intel环境""" + try: + # 创建临时的批处理文件来运行测试 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'"{test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'"{test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + return result + + except Exception as e: + print(f"运行测试失败: {e}") + return None + + def run_tests(self, args): + """运行测试""" + self.print_step(5, 6, "运行测试") + + # 查找测试可执行文件 + test_dir = self.build_dir / "bin" / args.build_type + if not test_dir.exists(): + test_dir = self.build_dir / "bin" + if not test_dir.exists(): + test_dir = self.build_dir + + test_files = list(test_dir.glob("test_*.exe")) + + if not test_files: + self.print_warning("未找到测试程序") + return True + + all_passed = True + + for test_exe in sorted(test_files): + test_name = test_exe.stem + self.print_info(f"运行测试: {test_name}") + print(f" {'-'*50}") + + # 运行测试 + result = self.run_tests_with_environment(str(test_exe)) + + if result is None: + self.print_error(f" {test_name} 运行失败") + all_passed = False + continue + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + print(f" {line}") + + if result.returncode == 0: + self.print_success(f" {test_name} 通过") + else: + self.print_error(f" {test_name} 失败 (退出码: {result.returncode})") + all_passed = False + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + print() # 空行 + + return all_passed + + def create_test_runner(self, args): + """创建独立的测试运行器""" + self.print_step(6, 6, "创建测试运行器") + + runner_path = self.build_dir / "run_tests.bat" + + content = f'''@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Test Runner +echo ======================================== +echo. + +REM Setup Intel oneAPI environment +set "SETVARS_PATH={self.intel_env.setvars_path or ''}" +if exist "%SETVARS_PATH%" ( + call "%SETVARS_PATH%" >nul + echo [INFO] Intel environment configured +) else ( + echo [WARNING] Intel environment not found + echo [WARNING] Tests may fail without runtime libraries +) + +echo. + +REM Run all test executables +set "TEST_COUNT=0" +set "PASS_COUNT=0" + +for %%f in ("bin\\{args.build_type}\\test_*.exe") do ( + set /a TEST_COUNT+=1 + echo [TEST %%f] + echo {'-'*50} + + %%f + if errorlevel 1 ( + echo [FAILED] %%f + ) else ( + echo [PASSED] %%f + set /a PASS_COUNT+=1 + ) + echo. +) + +echo ======================================== +echo Tests: %PASS_COUNT%/%TEST_COUNT% passed +if %PASS_COUNT% equ %TEST_COUNT% ( + echo [SUCCESS] All tests passed! +) else ( + echo [FAILURE] Some tests failed +) +echo ======================================== + +pause +''' + + with open(runner_path, 'w', encoding='utf-8') as f: + f.write(content) + + self.print_success(f"测试运行器已创建: {runner_path}") + self.print_info(f"使用方法: cd build && run_tests.bat") + + return runner_path + + def generate_report(self, args, build_time, tests_passed): + """生成构建报告""" + self.print_header("构建完成") + + print(f"项目: {self.project_root.name}") + print(f"构建类型: {args.build_type}") + print(f"编译器: {args.compiler}") + print(f"并行作业: {args.jobs}") + print(f"总耗时: {build_time:.1f}秒") + print(f"测试结果: {'全部通过' if tests_passed else '有失败'}") + + # 显示生成的可执行文件 + bin_dir = self.build_dir / "bin" / args.build_type + if bin_dir.exists(): + print(f"\n生成的可执行文件:") + for exe in sorted(bin_dir.glob("*.exe")): + size_mb = exe.stat().st_size / (1024 * 1024) + print(f" • {exe.name} ({size_mb:.2f} MB)") + + # 显示测试运行器信息 + runner_path = self.build_dir / "run_tests.bat" + if runner_path.exists(): + print(f"\n独立测试运行器:") + print(f" • {runner_path.name}") + print(f" 在Intel oneAPI环境中运行所有测试") + + def run(self): + """运行构建系统""" + parser = argparse.ArgumentParser( + description="Fortran CFD项目构建工具", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认构建 + %(prog)s --clean # 清理后构建 + %(prog)s --build-type Release # Release构建 + %(prog)s --no-tests # 只构建,不运行测试 + %(prog)s -j8 --verbose # 8线程并行构建,详细输出 + """ + ) + + parser.add_argument("--build-type", choices=["Debug", "Release"], + default="Debug", help="构建类型") + parser.add_argument("--compiler", choices=["ifx", "ifort"], + default="ifx", help="Fortran编译器") + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-tests", action="store_true", + help="跳过测试") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + + args = parser.parse_args() + + # 开始构建 + start_time = time.time() + + self.print_header("Fortran CFD 项目构建系统") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 检查前提条件 + if not self.check_prerequisites(): + if not args.force: + return 1 + + # 2. 设置Intel环境 + if not self.setup_intel_environment(args): + if not args.force: + return 1 + + # 3. 清理目录 + if not self.clean_build_directory(args): + if not args.force: + return 1 + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 4. 配置CMake + if not self.configure_cmake(args): + if not args.force: + return 1 + + # 5. 构建项目 + if not self.build_project(args): + if not args.force: + return 1 + + # 6. 运行测试和创建测试运行器 + tests_passed = True + if not args.no_tests: + tests_passed = self.run_tests(args) + + # 创建测试运行器 + self.create_test_runner(args) # 传递 args 参数 + + # 7. 生成报告 + build_time = time.time() - start_time + self.generate_report(args, build_time, tests_passed) + + return 0 if tests_passed else 1 + + except KeyboardInterrupt: + self.print_error("\n构建被用户中断") + return 1 + except Exception as e: + self.print_error(f"构建过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + builder = BuildSystem() + return builder.run() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/scripts/requirements.txt b/example/1d-linear-convection/weno3/fortran/registry/02g/scripts/requirements.txt new file mode 100644 index 000000000..d21a12b45 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/scripts/requirements.txt @@ -0,0 +1,2 @@ +# 构建脚本的Python依赖(可选) +# 目前没有特殊依赖,保持空文件或添加未来可能需要的包 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/scripts/run_step1.bat b/example/1d-linear-convection/weno3/fortran/registry/02g/scripts/run_step1.bat new file mode 100644 index 000000000..0b6b1f17e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/scripts/run_step1.bat @@ -0,0 +1,39 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Step 1: Physics Modules Test +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python step1 script with full Intel environment support... +echo. + +python run_step1.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Step 1 failed + pause + exit /b 1 +) + +echo. +echo [INFO] Step 1 completed successfully! +echo. +echo [INFO] Next step: Update config to include physics settings +echo [INFO] Run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/scripts/run_step1.py b/example/1d-linear-convection/weno3/fortran/registry/02g/scripts/run_step1.py new file mode 100644 index 000000000..5e087a690 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/scripts/run_step1.py @@ -0,0 +1,319 @@ +#!/usr/bin/env python3 +""" +Step 1: Physics Modules Test +扩展build.py,专门用于测试物理模块 +""" + +import os +import sys +import subprocess +import time +from pathlib import Path + +# 添加当前目录到路径,以便导入build.py的类 +sys.path.insert(0, str(Path(__file__).parent)) + +try: + from build import IntelEnvironment, BuildSystem +except ImportError: + print("Error: Cannot import build.py. Make sure build.py is in the same directory.") + sys.exit(1) + +class Step1System(BuildSystem): + """Step 1 测试系统,继承自BuildSystem""" + + def __init__(self): + super().__init__() + self.test_name = "test_physics_minimal" + self.test_exe = None + + def find_test_executable(self): + """查找测试可执行文件""" + possible_paths = [ + self.build_dir / "bin" / "Debug" / f"{self.test_name}.exe", + self.build_dir / "Debug" / f"{self.test_name}.exe", + self.build_dir / f"{self.test_name}.exe", + self.build_dir / "bin" / f"{self.test_name}.exe", + ] + + for path in possible_paths: + if path.exists(): + self.test_exe = path + self.print_success(f"Found test executable: {path}") + return True + + # 如果没有找到,尝试搜索 + self.print_warning(f"Could not find {self.test_name}.exe") + self.print_info("Searching for test executables...") + + try: + result = subprocess.run( + ["dir", str(self.build_dir), "/s", "/b", "*.exe"], + capture_output=True, + text=True, + encoding='utf-8', + shell=True + ) + + if result.returncode == 0: + test_files = [line.strip() for line in result.stdout.split('\n') + if line and 'test_' in line.lower()] + + if test_files: + self.print_info("Found test files:") + for test_file in test_files: + self.print_info(f" {test_file}") + return False + except: + pass + + return False + + def run_test_with_intel_env(self): + """在Intel环境下运行测试""" + if not self.test_exe: + self.print_error("No test executable found") + return False + + self.print_step(1, 2, f"Running test: {self.test_exe.name}") + + try: + # 创建临时的批处理文件来运行测试(包含Intel环境) + import tempfile + + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + # 设置Intel环境 + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'echo [INFO] Intel environment configured\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'echo [WARNING] Intel environment not found\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + self.print_info(f"Command: {temp_bat}") + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower() or 'fail' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or 'pass' in line.lower() or '✓' in line: + print(f" \033[92m{line}\033[0m") + elif '=' in line or '---' in line: + print(f" \033[96m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + self.print_warning("Test stderr output:") + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + self.print_step(2, 2, "Test execution completed") + + if result.returncode == 0: + self.print_success("Test passed") + return True + else: + self.print_error(f"Test failed (exit code: {result.returncode})") + return False + + except Exception as e: + self.print_error(f"Failed to run test: {e}") + return False + + def build_project_if_needed(self, args): + """如果需要,构建项目""" + if args.no_build: + self.print_info("Skipping build (--no-build flag)") + return True + + self.print_step(1, 3, "Building project") + + # 调用父类的构建方法 + build_args = argparse.Namespace() + build_args.clean = args.clean + build_args.build_type = "Debug" + build_args.compiler = "ifx" + build_args.no_tests = True # 不运行所有测试 + build_args.jobs = os.cpu_count() + build_args.verbose = args.verbose + build_args.force = args.force + + # 清理构建目录 + if args.clean and self.build_dir.exists(): + self.print_info("Cleaning build directory...") + import shutil + try: + shutil.rmtree(self.build_dir) + self.print_success("Build directory cleaned") + except Exception as e: + self.print_error(f"Clean failed: {e}") + if not args.force: + return False + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 配置CMake + self.print_step(2, 3, "Configuring CMake") + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + "-DCMAKE_BUILD_TYPE=Debug", + "-T", "fortran=ifx", + ] + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + if not self.run_command(cmake_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + # 构建项目 + self.print_step(3, 3, "Building project") + build_cmd = [ + "cmake", + "--build", ".", + "--config", "Debug", + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + if not self.run_command(build_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + self.print_success("Build completed") + return True + + def run(self): + """运行Step 1测试""" + parser = argparse.ArgumentParser( + description="Step 1: Physics Modules Test", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认运行 + %(prog)s --clean # 清理后构建并测试 + %(prog)s --no-build # 只运行测试,不重新构建 + %(prog)s --verbose # 详细输出 + %(prog)s -j4 # 使用4个并行作业构建 + """ + ) + + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-build", action="store_true", + help="不重新构建,直接运行测试") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + + args = parser.parse_args() + + # 开始测试 + start_time = time.time() + + self.print_header("Step 1: Physics Modules Implementation Test") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 设置Intel环境 + self.print_step(1, 4, "Setting up Intel oneAPI environment") + if not self.setup_intel_environment(args): + if not args.force: + self.print_error("Intel environment setup failed") + return 1 + self.print_warning("Intel environment setup failed, continuing...") + + # 2. 构建项目(如果需要) + self.print_step(2, 4, "Building project if needed") + if not self.build_project_if_needed(args): + if not args.force: + return 1 + + # 3. 查找测试可执行文件 + self.print_step(3, 4, "Finding test executable") + if not self.find_test_executable(): + if not args.force: + return 1 + self.print_warning("Test executable not found, but continuing due to --force") + return 0 + + # 4. 运行测试 + self.print_step(4, 4, "Running physics module test") + test_passed = self.run_test_with_intel_env() + + # 生成报告 + test_time = time.time() - start_time + self.print_header("Step 1 Complete") + + print(f"测试: {'通过 ✓' if test_passed else '失败 ✗'}") + print(f"测试程序: {self.test_exe.name if self.test_exe else '未找到'}") + print(f"总耗时: {test_time:.1f}秒") + + if test_passed: + print(f"\n下一步: 更新配置以包含物理设置") + print(f"建议: 修改config.f90,添加physics相关字段") + return 0 + else: + if args.force: + self.print_warning("测试失败,但由于--force标志继续执行") + return 0 + return 1 + + except KeyboardInterrupt: + self.print_error("\n测试被用户中断") + return 1 + except Exception as e: + self.print_error(f"测试过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + system = Step1System() + return system.run() + +if __name__ == "__main__": + # 需要导入argparse + import argparse + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02g/src/CMakeLists.txt new file mode 100644 index 000000000..8a76bdb52 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/src/CMakeLists.txt @@ -0,0 +1,17 @@ +# ==================== src\CMakeLists.txt ==================== + +# src/CMakeLists.txt +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(base) +add_subdirectory(core) +add_subdirectory(infrastructure) +add_subdirectory(numerics) +add_subdirectory(physics) # ← 新增物理模块目录 +add_subdirectory(manager) + +message(STATUS "源代码目录配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/src/base/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02g/src/base/CMakeLists.txt new file mode 100644 index 000000000..74f4aa65f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/src/base/CMakeLists.txt @@ -0,0 +1,16 @@ +# src/base/CMakeLists.txt +message(STATUS "Configuring base module...") + +add_library(base STATIC + modules.f90 + precision.f90 # 新增 +) + +set_target_properties(base PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Base module configured") + +# src/core/CMakeLists.txt +message(STATUS "Configuring core module...") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/src/base/modules.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/src/base/modules.f90 new file mode 100644 index 000000000..43aaee241 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/src/base/modules.f90 @@ -0,0 +1,36 @@ +! src/base/modules.f90 +module base_modules + use, intrinsic :: iso_fortran_env, only: real64, int32 + implicit none + + public :: wp, ip, max_name_len, string_len, cfd_config_base, component_info + + integer, parameter :: wp = real64 + integer, parameter :: ip = int32 + integer, parameter :: string_len = 100 + integer, parameter :: max_name_len = 32 + + ! 基础配置类型 + type :: cfd_config_base + character(len=max_name_len) :: ic_type = "step" + character(len=max_name_len) :: recon_scheme = "eno" + character(len=max_name_len) :: flux_type = "rusanov" + integer(ip) :: rk_order = 1 + real(wp) :: wave_speed = 1.0_wp + real(wp) :: final_time = 0.625_wp + real(wp) :: dt = 0.025_wp + character(len=max_name_len) :: boundary_type = "periodic" + integer(ip) :: spatial_order = 2 + character(len=max_name_len) :: equation_type = "linear_advection" + character(len=max_name_len) :: problem_type = "linear_advection" + logical :: verbose = .true. + end type cfd_config_base + + ! 组件信息类型 + type :: component_info + character(len=max_name_len) :: category = "" + character(len=max_name_len) :: name = "" + integer(ip) :: order = 0 + end type component_info + +end module base_modules \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/src/base/precision.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/src/base/precision.f90 new file mode 100644 index 000000000..4ac5fd7ef --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/src/base/precision.f90 @@ -0,0 +1,9 @@ +! src/base/precision.f90(简单版本) +module precision_module + use base_modules, only: wp, ip + implicit none + + ! 重新导出,确保兼容 + public :: wp, ip + +end module precision_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02g/src/core/CMakeLists.txt new file mode 100644 index 000000000..d8b8df064 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/src/core/CMakeLists.txt @@ -0,0 +1,14 @@ +# src/core/CMakeLists.txt +message(STATUS "Configuring core module...") + +add_library(core STATIC + registry.f90 +) + +target_link_libraries(core PRIVATE base) + +set_target_properties(core PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Core module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/src/core/factory_base.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/src/core/factory_base.f90 new file mode 100644 index 000000000..302418a15 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/src/core/factory_base.f90 @@ -0,0 +1,57 @@ +! src/core/factory_base.f90 +module factory_base_module + use base_modules, only: wp, ip + use registry_module, only: create_component, has_component + + implicit none + private + public :: wp, ip, factory_base, factory_create + + ! 工厂基类 + type :: factory_base + character(len=max_name_length) :: category = "" + contains + procedure :: create => factory_base_create + procedure :: get_available => factory_base_get_available + end type factory_base + + ! 便捷函数类型 + abstract interface + function factory_function_interface(category, name) result(instance) + import :: wp + character(len=*), intent(in) :: category, name + class(*), allocatable :: instance + end function factory_function_interface + end interface + +contains + + ! 创建工厂实例 + function factory_create(category) result(factory) + character(len=*), intent(in) :: category + type(factory_base) :: factory + factory%category = trim(category) + end function factory_create + + ! 工厂创建方法 + function factory_base_create(this, name) result(instance) + class(factory_base), intent(in) :: this + character(len=*), intent(in) :: name + class(*), allocatable :: instance + + instance = create_component(this%category, name) + end function factory_base_create + + ! 获取可用组件列表(简化版) + subroutine factory_base_get_available(this, names, count) + class(factory_base), intent(in) :: this + character(len=*), allocatable, intent(out) :: names(:) + integer(ip), intent(out) :: count + + ! 这里需要实现从注册表获取列表的逻辑 + ! 暂时返回空列表 + count = 0 + allocate(character(len=max_name_length) :: names(0)) + end subroutine factory_base_get_available + +end module factory_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..a960167c3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/src/core/factory_interfaces.f90 @@ -0,0 +1,17 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, factory_procedure + + ! Factory procedure interface + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/src/core/registry.f90 new file mode 100644 index 000000000..acc63edb4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/src/core/registry.f90 @@ -0,0 +1,203 @@ +! src/core/registry.f90 +module registry_module + use base_modules, only: wp, ip, max_name_len, component_info + + implicit none + private + + ! 明确公开所有需要的接口 + public :: wp, ip ! 类型参数 + public :: component_info ! 类型 + public :: registry_init, registry_cleanup ! 初始化/清理 + public :: register_component_simple ! 注册组件 + public :: has_component_simple ! 检查组件 + public :: list_components ! 列出组件 + public :: registry_is_initialized ! 检查初始化状态 ← 新增 + public :: registry_get_size ! 获取大小 ← 新增 + + ! 全局注册表 + type :: component_registry + type(component_info), allocatable :: components(:) + integer(ip) :: count = 0 + integer(ip) :: capacity = 100 + logical :: initialized = .false. + logical :: verbose = .true. + end type component_registry + + type(component_registry) :: registry + +contains + + ! ==================== 公共API ==================== + + subroutine registry_init(verbose) + logical, optional, intent(in) :: verbose + + if (registry%initialized) then + if (registry%verbose) then + print *, "[REGISTRY] Already initialized" + end if + return + end if + + if (present(verbose)) then + registry%verbose = verbose + end if + + allocate(registry%components(registry%capacity)) + registry%initialized = .true. + + if (registry%verbose) then + print *, "[REGISTRY] Initialized with capacity:", registry%capacity + end if + end subroutine registry_init + + subroutine registry_cleanup() + if (allocated(registry%components)) then + deallocate(registry%components) + end if + registry%initialized = .false. + registry%count = 0 + + if (registry%verbose) then + print *, "[REGISTRY] Cleaned up" + end if + end subroutine registry_cleanup + + subroutine register_component_simple(category, name, order) + character(len=*), intent(in) :: category, name + integer(ip), optional, intent(in) :: order + + integer(ip) :: i + type(component_info) :: info + + if (.not. registry%initialized) then + call registry_init() + end if + + ! 检查是否已存在 + do i = 1, registry%count + if (trim(registry%components(i)%category) == trim(category) .and. & + trim(registry%components(i)%name) == trim(name)) then + if (registry%verbose) then + print *, "[WARN] Overwriting component: ", trim(category), ".", trim(name) + end if + + ! 更新 + if (present(order)) then + registry%components(i)%order = order + else + registry%components(i)%order = 0 + end if + return + end if + end do + + ! 扩展数组 + if (registry%count >= registry%capacity) then + call expand_registry() + end if + + ! 添加新组件 + registry%count = registry%count + 1 + + info%category = trim(category) + info%name = trim(name) + info%order = 0 + if (present(order)) then + info%order = order + end if + + registry%components(registry%count) = info + + if (registry%verbose) then + print *, "[OK] Registered simple: ", trim(category), ".", trim(name) + end if + end subroutine register_component_simple + + logical function has_component_simple(category, name) + character(len=*), intent(in) :: category, name + + integer(ip) :: i + + has_component_simple = .false. + + if (.not. registry%initialized) return + + do i = 1, registry%count + if (trim(registry%components(i)%category) == trim(category) .and. & + trim(registry%components(i)%name) == trim(name)) then + has_component_simple = .true. + return + end if + end do + end function has_component_simple + + subroutine list_components(category) + character(len=*), optional, intent(in) :: category + + integer(ip) :: i, count + + if (.not. registry%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + if (registry%count == 0) then + print *, "[INFO] No components registered" + return + end if + + count = 0 + print *, "=== Registry Contents ===" + do i = 1, registry%count + if (.not. present(category) .or. & + trim(registry%components(i)%category) == trim(category)) then + call print_component_info(registry%components(i)) + count = count + 1 + end if + end do + + print *, "Total:", count, "components" + print *, "==========================" + end subroutine list_components + + ! ==================== 新增函数 ==================== + + logical function registry_is_initialized() + ! 检查注册表是否已初始化 + registry_is_initialized = registry%initialized + end function registry_is_initialized + + integer(ip) function registry_get_size() + ! 获取注册表中的组件数量 + registry_get_size = registry%count + end function registry_get_size + + ! ==================== 内部辅助函数 ==================== + + subroutine expand_registry() + type(component_info), allocatable :: temp(:) + + registry%capacity = registry%capacity * 2 + allocate(temp(registry%capacity)) + temp(1:registry%count) = registry%components(1:registry%count) + call move_alloc(temp, registry%components) + + if (registry%verbose) then + print *, "[INFO] Registry expanded to capacity:", registry%capacity + end if + end subroutine expand_registry + + subroutine print_component_info(info) + type(component_info), intent(in) :: info + + if (info%order > 0) then + print *, " [", trim(info%category), ".", trim(info%name), & + " (order:", info%order, ")]" + else + print *, " [", trim(info%category), ".", trim(info%name), "]" + end if + end subroutine print_component_info + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02g/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..70cbbd2f3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/src/infrastructure/CMakeLists.txt @@ -0,0 +1,17 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "Configuring infrastructure module...") + +add_library(infrastructure STATIC + config.f90 + mesh.f90 + domain.f90 # 新增 + solution.f90 # 新增 +) + +target_link_libraries(infrastructure PRIVATE base) + +set_target_properties(infrastructure PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Infrastructure module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/src/infrastructure/config.f90 new file mode 100644 index 000000000..719e14d25 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/src/infrastructure/config.f90 @@ -0,0 +1,64 @@ +! src/infrastructure/config.f90 +module config_module + use base_modules, only: wp, ip, max_name_len, cfd_config_base + + implicit none + public :: wp, ip, cfd_config, config_print, config_with_reconstruction + + ! 扩展配置类型 + type, extends(cfd_config_base) :: cfd_config + real(wp) :: left_boundary_value = 1.0_wp + real(wp) :: right_boundary_value = 2.0_wp + real(wp) :: domain_length = 2.0_wp + end type cfd_config + +contains + + subroutine config_print(cfg) + type(cfd_config), intent(in) :: cfg + + print *, "=== CFD Configuration ===" + print *, "Initial condition: ", trim(cfg%ic_type) + print *, "Reconstruction: ", trim(cfg%recon_scheme), " (order:", cfg%spatial_order, ")" + print *, "Flux type: ", trim(cfg%flux_type) + print *, "Time integration: RK", cfg%rk_order + print *, "Wave speed: ", cfg%wave_speed + print *, "Final time: ", cfg%final_time + print *, "Time step: ", cfg%dt + print *, "Boundary: ", trim(cfg%boundary_type) + print *, "===============================" + end subroutine config_print + + subroutine config_with_reconstruction(cfg, scheme, order) + type(cfd_config), intent(inout) :: cfg + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + integer :: i + + ! 转换为小写 + cfg%recon_scheme = scheme + do i = 1, len_trim(cfg%recon_scheme) + if (cfg%recon_scheme(i:i) >= 'A' .and. cfg%recon_scheme(i:i) <= 'Z') then + cfg%recon_scheme(i:i) = char(ichar(cfg%recon_scheme(i:i)) + 32) + end if + end do + + ! 设置阶数 + if (present(order)) then + cfg%spatial_order = order + else + if (index(cfg%recon_scheme, 'weno') > 0) then + cfg%spatial_order = 5 + else if (trim(cfg%recon_scheme) == 'eno') then + cfg%spatial_order = 3 + end if + end if + + if (cfg%verbose) then + print *, "[CONFIG] Reconstruction: ", trim(cfg%recon_scheme), & + " Order: ", cfg%spatial_order + end if + end subroutine config_with_reconstruction + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/src/infrastructure/domain.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/src/infrastructure/domain.f90 new file mode 100644 index 000000000..c3662f039 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/src/infrastructure/domain.f90 @@ -0,0 +1,102 @@ +! src/infrastructure/domain.f90 +module domain_module + use base_modules, only: wp, ip, max_name_len + use config_module, only: cfd_config + use mesh_module, only: mesh_type + + implicit none + private + public :: wp, ip, domain_type, domain_create, is_physical_cell + + type :: domain_type + type(cfd_config), pointer :: config => null() + type(mesh_type), pointer :: mesh => null() + integer(ip) :: nghosts = 0 + integer(ip) :: ist = 1 ! 物理区域起始索引(1-based) + integer(ip) :: ied = 1 ! 物理区域结束索引(exclusive) + integer(ip) :: ntcells = 0 ! 总单元数(含ghost) + contains + procedure :: print_info => domain_print_info + procedure :: get_physical_indices => domain_get_physical_indices + end type domain_type + +contains + + function domain_create(config, mesh) result(domain) + type(cfd_config), target, intent(in) :: config + type(mesh_type), target, intent(in) :: mesh + type(domain_type) :: domain + + domain%config => config + domain%mesh => mesh + + ! 计算ghost层数(参考Julia的_calc_nghosts) + domain%nghosts = calc_nghosts(config) + domain%ist = domain%nghosts + 1 + domain%ied = domain%ist + mesh%ncells + domain%ntcells = mesh%ncells + 2 * domain%nghosts + + if (config%verbose) then + print *, "[DOMAIN] Created:" + print *, " Ghost layers: ", domain%nghosts + print *, " Physical cells: ", domain%ist, " to ", domain%ied - 1 + print *, " Total cells: ", domain%ntcells + end if + end function domain_create + + function calc_nghosts(config) result(nghosts) + type(cfd_config), intent(in) :: config + integer(ip) :: nghosts + + character(len=max_name_len) :: scheme + + scheme = config%recon_scheme + + if (scheme == "eno") then + nghosts = config%spatial_order + else if (index(scheme, "weno") > 0) then + nghosts = config%spatial_order / 2 + 1 + else + print *, "[WARNING] Unknown scheme, using default nghosts=2" + nghosts = 2 + end if + + if (nghosts <= 0) then + print *, "[ERROR] Invalid nghosts: ", nghosts + nghosts = 2 + end if + end function calc_nghosts + + logical function is_physical_cell(this, idx) + class(domain_type), intent(in) :: this + integer(ip), intent(in) :: idx + is_physical_cell = (idx >= this%ist .and. idx < this%ied) + end function is_physical_cell + + function domain_get_physical_indices(this) result(indices) + class(domain_type), intent(in) :: this + integer(ip), allocatable :: indices(:) + integer(ip) :: i, count + + count = this%ied - this%ist + allocate(indices(count)) + + do i = 1, count + indices(i) = this%ist + i - 1 + end do + end function domain_get_physical_indices + + subroutine domain_print_info(this) + class(domain_type), intent(in) :: this + + print *, "=== Domain Information ===" + print *, "Configuration: ", trim(this%config%recon_scheme), & + " order ", this%config%spatial_order + print *, "Ghost layers: ", this%nghosts + print *, "Physical cells: ", this%ist, " to ", this%ied - 1 + print *, "Total cells: ", this%ntcells + print *, "Mesh cells: ", this%mesh%ncells + print *, "==========================" + end subroutine domain_print_info + +end module domain_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..f810f3a1b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/src/infrastructure/mesh.f90 @@ -0,0 +1,73 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use base_modules, only: wp, ip + + implicit none + public :: wp, ip, mesh_type, mesh_init, mesh_print_info + + ! 网格类型 + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer(ip) :: ncells = 40 + integer(ip) :: nnodes + integer(ip) :: nx + real(wp), allocatable :: x(:) ! 节点坐标 + real(wp), allocatable :: xcc(:) ! 单元中心坐标 + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer(ip), optional, intent(in) :: ncells + + integer(ip) :: i + + ! 设置参数 + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! 计算 + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! 分配内存 + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! 生成节点坐标 + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! 生成单元中心坐标 + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== Mesh Information ===" + print *, "Domain: [", this%xmin, ", ", this%xmax, "]" + print *, "Cells: ", this%ncells + print *, "Nodes: ", this%nnodes + print *, "dx: ", this%dx + print *, "L: ", this%L + print *, "========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/src/infrastructure/solution.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/src/infrastructure/solution.f90 new file mode 100644 index 000000000..ce88fd8a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/src/infrastructure/solution.f90 @@ -0,0 +1,131 @@ +! src/infrastructure/solution.f90 +module solution_module + use base_modules, only: wp, ip + use domain_module, only: domain_type + + implicit none + private + public :: wp, ip, solution_type, solution_create, solution_reset + + type :: solution_type + type(domain_type), pointer :: domain => null() + real(wp), allocatable :: u(:) ! 当前解(含ghost) + real(wp), allocatable :: un(:) ! 旧解 + real(wp), allocatable :: q_face_left(:) ! 左界面值 + real(wp), allocatable :: q_face_right(:)! 右界面值 + real(wp), allocatable :: flux(:) ! 通量 + real(wp), allocatable :: res(:) ! 残差 + contains + procedure :: initialize => solution_initialize + procedure :: update_old_field => solution_update_old_field + procedure :: print_info => solution_print_info + procedure :: reset => solution_reset_instance + end type solution_type + +contains + + function solution_create(domain) result(solution) + type(domain_type), target, intent(in) :: domain + type(solution_type) :: solution + + integer(ip) :: ncells, nnodes, ntcells + + solution%domain => domain + + ncells = domain%mesh%ncells + nnodes = domain%mesh%nnodes + ntcells = domain%ntcells + + ! 分配数组(与Julia solution.jl一致) + allocate(solution%u(ntcells), source=0.0_wp) + allocate(solution%un(ntcells), source=0.0_wp) + allocate(solution%q_face_left(nnodes), source=0.0_wp) + allocate(solution%q_face_right(nnodes), source=0.0_wp) + allocate(solution%flux(nnodes), source=0.0_wp) + allocate(solution%res(ncells), source=0.0_wp) + + if (domain%config%verbose) then + print *, "[SOLUTION] Created:" + print *, " u size: ", size(solution%u), " (with ghosts)" + print *, " flux size: ", size(solution%flux) + print *, " res size: ", size(solution%res) + end if + end function solution_create + + subroutine solution_initialize(this, initial_values) + class(solution_type), intent(inout) :: this + real(wp), intent(in), optional :: initial_values(:) + + integer(ip) :: i, idx + type(domain_type), pointer :: domain + + domain => this%domain + + if (present(initial_values)) then + ! 应用初始值到物理区域 + do i = domain%ist, domain%ied - 1 + idx = i - domain%ist + 1 + if (idx <= size(initial_values)) then + this%u(i) = initial_values(idx) + end if + end do + else + ! 默认为0 + this%u = 0.0_wp + end if + + ! 同步旧场(与Julia的update_old_field一致) + call this%update_old_field() + + if (domain%config%verbose) then + print *, "[SOLUTION] Initialized" + print *, " u range: ", minval(this%u), " to ", maxval(this%u) + end if + end subroutine solution_initialize + + subroutine solution_update_old_field(this) + class(solution_type), intent(inout) :: this + this%un = this%u ! 与Julia的 un .= u 一致 + end subroutine solution_update_old_field + + subroutine solution_reset_instance(this) + class(solution_type), intent(inout) :: this + call solution_reset(this) + end subroutine solution_reset_instance + + subroutine solution_reset(solution) + type(solution_type), intent(inout) :: solution + + if (allocated(solution%u)) solution%u = 0.0_wp + if (allocated(solution%un)) solution%un = 0.0_wp + if (allocated(solution%q_face_left)) solution%q_face_left = 0.0_wp + if (allocated(solution%q_face_right)) solution%q_face_right = 0.0_wp + if (allocated(solution%flux)) solution%flux = 0.0_wp + if (allocated(solution%res)) solution%res = 0.0_wp + + if (associated(solution%domain) .and. solution%domain%config%verbose) then + print *, "[SOLUTION] Reset" + end if + end subroutine solution_reset + + subroutine solution_print_info(this) + class(solution_type), intent(in) :: this + + print *, "=== Solution Information ===" + print *, "Arrays:" + print *, " u: ", size(this%u), " elements" + print *, " un: ", size(this%un), " elements" + print *, " q_face_left: ", size(this%q_face_left), " elements" + print *, " q_face_right: ", size(this%q_face_right), " elements" + print *, " flux: ", size(this%flux), " elements" + print *, " res: ", size(this%res), " elements" + + if (allocated(this%u)) then + print *, "Values:" + print *, " u min/max: ", minval(this%u), maxval(this%u) + print *, " un min/max: ", minval(this%un), maxval(this%un) + end if + print *, "============================" + end subroutine solution_print_info + +end module solution_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/src/manager/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02g/src/manager/CMakeLists.txt new file mode 100644 index 000000000..00c8bf49b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/src/manager/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/manager/CMakeLists.txt +message(STATUS "配置管理器模块...") + +# 创建管理器库 +add_library(manager STATIC + component_manager.f90 + component_factory.f90 +) + +# 明确依赖关系:管理器依赖所有其他模块 +target_link_libraries(manager + PRIVATE + core + infrastructure + reconstructor + flux +) + +# 设置模块输出目录 +set_target_properties(manager PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "管理器模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/src/manager/component_factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/src/manager/component_factory.f90 new file mode 100644 index 000000000..114fedea5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/src/manager/component_factory.f90 @@ -0,0 +1,127 @@ +! src/manager/component_factory.f90 +module component_factory_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use config_module, only: cfd_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + use eno_reconstructor_module, only: eno_reconstructor, create_eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor, create_weno3_reconstructor + use rusanov_flux_module, only: rusanov_flux, create_rusanov_flux + + implicit none + private + public :: wp, create_reconstructor, create_flux_calculator + + ! 错误代码 + integer, parameter :: CM_SUCCESS = 0 + integer, parameter :: CM_ERROR_UNKNOWN_SCHEME = 1 + integer, parameter :: CM_ERROR_UNKNOWN_FLUX = 2 + integer, parameter :: CM_ERROR_INVALID_ORDER = 3 + +contains + + ! ==================== 重构器创建 ==================== + + function create_reconstructor(config, status) result(recon) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(reconstructor_base), allocatable :: recon + + character(len=20) :: scheme + integer :: order, error_code + + scheme = trim(adjustl(config%recon_scheme)) + order = config%spatial_order + + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating reconstructor: ", scheme, " order=", order + end if + + select case(scheme) + case('eno') + allocate(eno_reconstructor :: recon) + select type(recon) + type is(eno_reconstructor) + recon = create_eno_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case('weno3') + allocate(weno3_reconstructor :: recon) + select type(recon) + type is(weno3_reconstructor) + recon = create_weno3_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case default + error_code = CM_ERROR_UNKNOWN_SCHEME + if (config%verbose) then + print *, "[ERROR] Unknown reconstructor scheme: ", scheme + print *, " Available: eno, weno3" + end if + end select + + ! 检查阶数有效性 + if (error_code == CM_SUCCESS) then + if (order < 1) then + error_code = CM_ERROR_INVALID_ORDER + if (config%verbose) then + print *, "[ERROR] Invalid spatial order: ", order + end if + end if + end if + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Reconstructor creation failed" + end if + end function create_reconstructor + + ! ==================== 通量计算器创建 ==================== + + function create_flux_calculator(config, status) result(flux) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(flux_calculator_base), allocatable :: flux + + character(len=20) :: flux_type + integer :: error_code + + flux_type = trim(adjustl(config%flux_type)) + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating flux calculator: ", flux_type + end if + + select case(flux_type) + case('rusanov') + allocate(rusanov_flux :: flux) + select type(flux) + type is(rusanov_flux) + flux = create_rusanov_flux() + flux%wave_speed_default = config%wave_speed + end select + + case default + error_code = CM_ERROR_UNKNOWN_FLUX + if (config%verbose) then + print *, "[ERROR] Unknown flux type: ", flux_type + print *, " Available: rusanov" + end if + end select + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Flux calculator creation failed" + end if + end function create_flux_calculator + +end module component_factory_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/src/manager/component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/src/manager/component_manager.f90 new file mode 100644 index 000000000..9e095c257 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/src/manager/component_manager.f90 @@ -0,0 +1,75 @@ +! src/manager/component_manager.f90 +module component_manager_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use config_module, only: cfd_config + use component_factory_module, only: create_reconstructor, create_flux_calculator + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + + implicit none + private + public :: wp, component_manager_info, validate_config + public :: create_reconstructor, create_flux_calculator + +contains + + ! ==================== 配置验证 ==================== + + function validate_config(config) result(is_valid) + type(cfd_config), intent(in) :: config + logical :: is_valid + + integer :: status + class(reconstructor_base), allocatable :: test_recon + class(flux_calculator_base), allocatable :: test_flux + + is_valid = .false. + + ! 测试创建重构器 + test_recon = create_reconstructor(config, status) + if (status /= 0) then + if (config%verbose) then + print *, "[CONFIG VALIDATION] Invalid reconstructor configuration" + end if + return + end if + + ! 测试创建通量计算器 + test_flux = create_flux_calculator(config, status) + if (status /= 0) then + if (config%verbose) then + print *, "[CONFIG VALIDATION] Invalid flux configuration" + end if + return + end if + + ! 清理测试组件 + if (allocated(test_recon)) deallocate(test_recon) + if (allocated(test_flux)) deallocate(test_flux) + + is_valid = .true. + + if (config%verbose) then + print *, "[CONFIG VALIDATION] Configuration is valid" + end if + end function validate_config + + ! ==================== 信息显示 ==================== + + subroutine component_manager_info() + print *, "=== Component Manager ===" + print *, "Available reconstructors:" + print *, " - eno (orders: 1-7)" + print *, " - weno3 (order: 3)" + print *, "" + print *, "Available flux calculators:" + print *, " - rusanov" + print *, "" + print *, "Features:" + print *, " - Configuration validation" + print *, " - Component creation from config" + print *, " - Error handling with status codes" + print *, "=========================" + end subroutine component_manager_info + +end module component_manager_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..66874a7ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/CMakeLists.txt @@ -0,0 +1,7 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..3fde7746b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,28 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +# Start with just the base module +add_library(flux STATIC + base.f90 + rusanov.f90 +) + +#target_link_libraries(flux PRIVATE core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 明确设置不使用 /Qm64 选项 +if(CMAKE_Fortran_COMPILER_ID MATCHES "IntelLLVM|Intel") + set_target_properties(flux PROPERTIES + Fortran_FLAGS "${CMAKE_Fortran_FLAGS} /Qm64" # 明确设置,避免继承问题 + ) +endif() + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/flux/base.f90 new file mode 100644 index 000000000..7080a7ae4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/flux/base.f90 @@ -0,0 +1,30 @@ +!src/numerics/flux/base.f90 +module flux_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, flux_calculator_base + + ! Base flux calculator type + type :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure :: info => flux_info + procedure :: print_basic_info => flux_print_basic ! 添加辅助方法 + end type flux_calculator_base + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "Flux calculator information:" + print *, " Name: ", trim(this%name) + end subroutine flux_info + + subroutine flux_print_basic(this) + class(flux_calculator_base), intent(in) :: this + print *, " Name: ", trim(this%name) + end subroutine flux_print_basic + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/flux/rusanov.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/flux/rusanov.f90 new file mode 100644 index 000000000..daa9e3bb5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/flux/rusanov.f90 @@ -0,0 +1,41 @@ +! src/numerics/flux/rusanov.f90 +module rusanov_flux_module + use, intrinsic :: iso_fortran_env, only: real64 + use flux_base_module, only: flux_calculator_base + implicit none + + private + public :: real64, rusanov_flux, create_rusanov_flux + + type, extends(flux_calculator_base) :: rusanov_flux + real(real64) :: wave_speed_default = 1.0_real64 + contains + procedure :: info => rusanov_info + end type rusanov_flux + + ! 构造函数接口 + interface rusanov_flux + module procedure create_rusanov_flux + end interface + +contains + + ! 构造函数 + type(rusanov_flux) function create_rusanov_flux() result(this) + this%name = "Rusanov" + this%wave_speed_default = 1.0_real64 + end function create_rusanov_flux + + subroutine rusanov_info(this) + class(rusanov_flux), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Flux calculator information:" + call this%print_basic_info() + + ! 添加Rusanov特有信息 + print *, " Type: Rusanov flux" + print *, " Default wave speed: ", this%wave_speed_default + end subroutine rusanov_info + +end module rusanov_flux_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..5e4b938d5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,22 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "Configuring reconstructor module...") + +# Start with just the base module +add_library(reconstructor STATIC + base.f90 + eno.f90 + weno3.f90 +) + +target_link_libraries(reconstructor PRIVATE core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Reconstructor module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..53798d021 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/reconstructor/base.f90 @@ -0,0 +1,33 @@ +!src/numerics/reconstructor/base.f90 +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, reconstructor_base + + ! Base reconstructor type + type :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + contains + procedure :: info => reconstructor_info + procedure :: print_basic_info => reconstructor_print_basic ! 添加一个辅助方法 + end type reconstructor_base + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + print *, "Reconstructor information:" + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_info + + subroutine reconstructor_print_basic(this) + class(reconstructor_base), intent(in) :: this + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_print_basic + +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/reconstructor/eno.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/reconstructor/eno.f90 new file mode 100644 index 000000000..f973e8b32 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/reconstructor/eno.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/eno.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, eno_reconstructor, create_eno_reconstructor ! ← 添加这个 + + type, extends(reconstructor_base) :: eno_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => eno_info + end type eno_reconstructor + + ! 构造函数接口 + interface eno_reconstructor + module procedure create_eno_reconstructor + end interface + +contains + + ! 构造函数 + type(eno_reconstructor) function create_eno_reconstructor() result(this) + this%name = "ENO" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_eno_reconstructor + + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加ENO特有信息 + print *, " Type: ENO reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/reconstructor/weno3.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/reconstructor/weno3.f90 new file mode 100644 index 000000000..d5b7a7477 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/src/numerics/reconstructor/weno3.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/weno3.f90 +module weno3_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno3_reconstructor, create_weno3_reconstructor + + type, extends(reconstructor_base) :: weno3_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno3_info + end type weno3_reconstructor + + ! 构造函数接口 + interface weno3_reconstructor + module procedure create_weno3_reconstructor + end interface + +contains + + ! 构造函数 + type(weno3_reconstructor) function create_weno3_reconstructor() result(this) + this%name = "WENO3" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_weno3_reconstructor + + subroutine weno3_info(this) + class(weno3_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加WENO3特有信息 + print *, " Type: WENO-3 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno3_info + +end module weno3_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/src/physics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02g/src/physics/CMakeLists.txt new file mode 100644 index 000000000..cc4e233ab --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/src/physics/CMakeLists.txt @@ -0,0 +1,19 @@ +# src/physics/CMakeLists.txt +message(STATUS "配置物理模块...") + +# 创建物理模块库 +add_library(physics STATIC + physics_interface.f90 + equations/linear_convection.f90 + problems/linear_convection_problem.f90 +) + +# 链接依赖 +target_link_libraries(physics PRIVATE base) + +# 设置模块输出目录 +set_target_properties(physics PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "物理模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/src/physics/equations/linear_convection.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/src/physics/equations/linear_convection.f90 new file mode 100644 index 000000000..595a21107 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/src/physics/equations/linear_convection.f90 @@ -0,0 +1,83 @@ +! src/physics/equations/linear_convection.f90 +module linear_convection_equation + use precision_module, only: wp, ip + use physics_interface, only: physics_equation + implicit none + private + + public :: linear_convection_eq, create_linear_convection_eq + public :: linear_convection_flux, linear_convection_speed + + ! 具体方程类型 + type, extends(physics_equation) :: linear_convection_eq + real(wp) :: wave_speed = 1.0_wp + contains + procedure :: flux => lc_flux + procedure :: speed => lc_speed + end type linear_convection_eq + + ! 独立函数(供外部调用) + interface linear_convection_flux + module procedure :: lc_flux_func + end interface + + interface linear_convection_speed + module procedure :: lc_speed_func + end interface + +contains + + ! 构造函数 + function create_linear_convection_eq(wave_speed) result(eq) + real(wp), intent(in), optional :: wave_speed + type(linear_convection_eq) :: eq + + eq%name = "Linear Convection" + if (present(wave_speed)) then + eq%wave_speed = wave_speed + else + eq%wave_speed = 1.0_wp + end if + end function create_linear_convection_eq + + ! 方法实现 + pure function lc_flux(this, u) result(f) + class(linear_convection_eq), intent(in) :: this + real(wp), intent(in) :: u + real(wp) :: f + f = this%wave_speed * u + end function lc_flux + + pure function lc_speed(this) result(a) + class(linear_convection_eq), intent(in) :: this + real(wp) :: a + a = this%wave_speed + end function lc_speed + + ! 独立函数 + pure function lc_flux_func(u, wave_speed) result(f) + real(wp), intent(in) :: u + real(wp), intent(in), optional :: wave_speed + real(wp) :: f + real(wp) :: a + + if (present(wave_speed)) then + a = wave_speed + else + a = 1.0_wp + end if + f = a * u + end function lc_flux_func + + pure function lc_speed_func(wave_speed) result(a) + real(wp), intent(in), optional :: wave_speed + real(wp) :: a + + if (present(wave_speed)) then + a = wave_speed + else + a = 1.0_wp + end if + end function lc_speed_func + +end module linear_convection_equation \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/src/physics/physics_interface.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/src/physics/physics_interface.f90 new file mode 100644 index 000000000..55662e5c2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/src/physics/physics_interface.f90 @@ -0,0 +1,64 @@ +! src/physics/physics_interface.f90 +module physics_interface + use precision_module, only: wp, ip + implicit none + private + + ! 定义抽象基类型 + type, abstract :: physics_equation + character(len=:), allocatable :: name + contains + procedure(eq_flux_abs), deferred :: flux + procedure(eq_speed_abs), deferred :: speed + end type physics_equation + + type, abstract :: physics_problem + character(len=:), allocatable :: name + contains + procedure(prob_ic_abs), deferred :: initial_condition + procedure(prob_bc_abs), deferred :: boundary_condition + procedure(prob_exact_abs), deferred :: exact_solution + end type physics_problem + + ! 抽象接口定义 + abstract interface + pure function eq_flux_abs(this, u) result(f) + import :: physics_equation, wp + class(physics_equation), intent(in) :: this + real(wp), intent(in) :: u + real(wp) :: f + end function eq_flux_abs + + pure function eq_speed_abs(this) result(a) + import :: physics_equation, wp + class(physics_equation), intent(in) :: this + real(wp) :: a + end function eq_speed_abs + + subroutine prob_ic_abs(this, x, u) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(in) :: x(:) + real(wp), intent(out) :: u(:) + end subroutine prob_ic_abs + + subroutine prob_bc_abs(this, u, t) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(inout) :: u(:) + real(wp), intent(in), optional :: t + end subroutine prob_bc_abs + + function prob_exact_abs(this, x, t) result(u) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(in) :: x(:), t + real(wp), dimension(size(x)) :: u + end function prob_exact_abs + end interface + + ! 公开接口 + public :: physics_equation, physics_problem + public :: wp, ip + +end module physics_interface \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/src/physics/problems/linear_convection_problem.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/src/physics/problems/linear_convection_problem.f90 new file mode 100644 index 000000000..312107105 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/src/physics/problems/linear_convection_problem.f90 @@ -0,0 +1,116 @@ +! src/physics/problems/linear_convection_problem.f90 +module linear_convection_problem + use precision_module, only: wp, ip + use physics_interface, only: physics_problem + implicit none + private + + public :: linear_convection_prob, create_linear_convection_prob + + ! 具体问题类型 + type, extends(physics_problem) :: linear_convection_prob + real(wp) :: wave_speed = 1.0_wp + real(wp) :: domain_length = 2.0_wp + character(len=20) :: ic_type = "step" + character(len=20) :: boundary_type = "periodic" + contains + procedure :: initial_condition => lc_initial_condition + procedure :: boundary_condition => lc_boundary_condition + procedure :: exact_solution => lc_exact_solution + end type linear_convection_prob + +contains + + ! 构造函数 + function create_linear_convection_prob(wave_speed, domain_length, & + ic_type, boundary_type) result(prob) + real(wp), intent(in), optional :: wave_speed, domain_length + character(len=*), intent(in), optional :: ic_type, boundary_type + type(linear_convection_prob) :: prob + + prob%name = "Linear Convection Problem" + + if (present(wave_speed)) prob%wave_speed = wave_speed + if (present(domain_length)) prob%domain_length = domain_length + if (present(ic_type)) prob%ic_type = ic_type + if (present(boundary_type)) prob%boundary_type = boundary_type + end function create_linear_convection_prob + + ! 初始条件 + subroutine lc_initial_condition(this, x, u) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(in) :: x(:) + real(wp), intent(out) :: u(:) + + integer :: i + + select case (trim(this%ic_type)) + case ("step") + do i = 1, size(x) + if (x(i) >= 0.5_wp .and. x(i) <= 1.0_wp) then + u(i) = 2.0_wp + else + u(i) = 1.0_wp + end if + end do + + case ("sin", "sine") + do i = 1, size(x) + u(i) = sin(2.0_wp * 3.141592653589793_wp * x(i) / this%domain_length) + end do + + case ("gaussian") + do i = 1, size(x) + u(i) = exp(-((x(i) - 0.5_wp) / 0.1_wp)**2) + end do + + case default + ! 默认阶跃函数 + do i = 1, size(x) + if (x(i) >= 0.5_wp .and. x(i) <= 1.0_wp) then + u(i) = 2.0_wp + else + u(i) = 1.0_wp + end if + end do + end select + end subroutine lc_initial_condition + + ! 边界条件(虚拟实现,实际在boundary模块) + subroutine lc_boundary_condition(this, u, t) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(inout) :: u(:) + real(wp), intent(in), optional :: t + + ! 边界条件将在独立模块实现 + print *, "[PROBLEM] Boundary condition placeholder" + if (present(t)) then + print *, " Time = ", t + end if + end subroutine lc_boundary_condition + + ! 精确解(周期性平移) + function lc_exact_solution(this, x, t) result(u) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(in) :: x(:), t + real(wp), dimension(size(x)) :: u + real(wp), dimension(size(x)) :: x_shifted + integer :: i + + ! 周期性平移 + do i = 1, size(x) + x_shifted(i) = x(i) - this%wave_speed * t + ! 确保在 [0, domain_length) 范围内 + do while (x_shifted(i) < 0.0_wp) + x_shifted(i) = x_shifted(i) + this%domain_length + end do + do while (x_shifted(i) >= this%domain_length) + x_shifted(i) = x_shifted(i) - this%domain_length + end do + end do + + ! 重用初始条件函数 + call this%initial_condition(x_shifted, u) + end function lc_exact_solution + +end module linear_convection_problem \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02g/tests/CMakeLists.txt new file mode 100644 index 000000000..01e0a2bd2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/tests/CMakeLists.txt @@ -0,0 +1,85 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +# +#message(STATUS "CMAKE_Fortran_MODULE_DIRECTORY=${CMAKE_Fortran_MODULE_DIRECTORY}") +# +add_executable(test_minimal_simple test_minimal_simple.f90) +target_link_libraries(test_minimal_simple + PRIVATE + core # 必须链接core库 + infrastructure +) + + +add_executable(test_simple_link test_simple_link.f90) +target_link_libraries(test_simple_link + PRIVATE + reconstructor + flux +) + + +add_executable(test_factory_simple test_factory_simple.f90) +target_link_libraries(test_factory_simple + PRIVATE + core + infrastructure + reconstructor + flux +) + + +add_executable(test_component_manager test_component_manager.f90) + +target_link_libraries(test_component_manager + PRIVATE + manager # ← 链接到新的管理器库 + infrastructure +) +# +#add_executable(test_architecture test_architecture.f90) +#target_link_libraries(test_architecture +# PRIVATE +# core +# infrastructure +# manager +#) + +#add_executable(test_solver_framework test_solver_framework.f90) +#target_link_libraries(test_solver_framework +# PRIVATE +# core +# infrastructure +# manager +#) +# +#add_executable(test_cfd_architecture test_cfd_architecture.f90) +#target_link_libraries(test_cfd_architecture +# PRIVATE +# base +# core +# infrastructure +#) + + +add_executable(test_basic_only test_basic_only.f90) +target_link_libraries(test_basic_only + PRIVATE + infrastructure + core +) + +add_executable(test_physics_minimal test_physics_minimal.f90) +target_link_libraries(test_physics_minimal + PRIVATE + physics + base +) + +add_executable(test_domain_solution test_domain_solution.f90) +target_link_libraries(test_domain_solution + PRIVATE + infrastructure + core +) diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_architecture.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_architecture.f90 new file mode 100644 index 000000000..1c40d4677 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_architecture.f90 @@ -0,0 +1,117 @@ +! tests/test_architecture.f90 +program test_architecture + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module + use config_module + use mesh_module + use component_manager_module + + implicit none + + print *, "=== CFD架构测试 ===" + print *, "" + + ! 测试1: 基本系统 + call test_basic_systems() + + ! 测试2: 组件注册 + call test_registry_integration() + + ! 测试3: 配置验证 + call test_config_validation() + + ! 测试4: 完整流程 + call test_full_workflow() + + print *, "" + print *, "=== 架构测试完成 ===" + +contains + + subroutine test_basic_systems() + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "1. 测试基本系统..." + print *, "-------------------" + + ! 测试配置 + call config_print(config) + print *, "✓ 配置创建成功" + + ! 测试网格 + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "✓ 网格创建成功" + print *, "" + end subroutine test_basic_systems + + subroutine test_registry_integration() + print *, "2. 测试注册系统集成..." + print *, "------------------------" + + call initialize_registry(verbose=.true.) + + ! 注册核心组件 + print *, "注册核心组件:" + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + ! 显示注册内容 + call component_registry%list_simple() + print *, "注册表大小: ", registry_get_size() + + call cleanup_registry() + print *, "✓ 注册系统测试完成" + print *, "" + end subroutine test_registry_integration + + subroutine test_config_validation() + type(cfd_config) :: config + logical :: is_valid + + print *, "3. 测试配置验证..." + print *, "-------------------" + + ! 测试有效配置 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + print *, "测试配置:" + print *, " - 重构器: ", trim(config%recon_scheme), " 阶数: ", config%spatial_order + print *, " - 通量: ", trim(config%flux_type) + print *, " - 波速: ", config%wave_speed + + ! 这里调用验证函数(需要先实现) + ! is_valid = validate_config(config) + print *, "✓ 配置验证接口准备就绪" + print *, "" + end subroutine test_config_validation + + subroutine test_full_workflow() + print *, "4. 测试完整流程..." + print *, "-------------------" + + print *, "步骤1: 初始化系统 ✓" + print *, "步骤2: 创建网格 ✓" + print *, "步骤3: 设置配置 ✓" + print *, "步骤4: 创建组件 (待实现)" + print *, "步骤5: 初始化解 (待实现)" + print *, "步骤6: 时间推进 (待实现)" + print *, "步骤7: 输出结果 (待实现)" + print *, "" + + print *, "✓ 流程框架定义完成" + end subroutine test_full_workflow + +end program test_architecture \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_basic_only.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_basic_only.f90 new file mode 100644 index 000000000..20901ddcc --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_basic_only.f90 @@ -0,0 +1,56 @@ +! tests/test_basic_only.f90 +program test_basic_only + ! 只测试最基本的功能,不依赖复杂模块 + use config_module, only: cfd_config, config_print, wp + use mesh_module, only: mesh_type + use registry_module, only: registry_init, registry_cleanup, & + register_component_simple, list_components + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "=== BASIC TEST - Minimal Functionality ===" + print *, "" + + ! 测试1: 配置 + print *, "1. Testing configuration..." + print *, "----------------------------" + call config_print(config) + print *, "" + + ! 测试2: 网格 + print *, "2. Testing mesh..." + print *, "------------------" + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=5) + print *, "Mesh initialized:" + print *, " Cells: ", mesh%ncells + print *, " Nodes: ", mesh%nnodes + print *, " dx: ", mesh%dx + print *, "" + + ! 测试3: 注册系统 + print *, "3. Testing registry..." + print *, "----------------------" + + call registry_init() + + ! 注册组件(使用简化版本) + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("flux", "rusanov") + + ! 列出组件 + call list_components() + print *, "" + + ! 清理 + call registry_cleanup() + + print *, "=== TEST PASSED ===" + print *, "✓ Configuration works" + print *, "✓ Mesh works" + print *, "✓ Registry works" + +end program test_basic_only \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_cfd_architecture.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_cfd_architecture.f90 new file mode 100644 index 000000000..1c40d4677 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_cfd_architecture.f90 @@ -0,0 +1,117 @@ +! tests/test_architecture.f90 +program test_architecture + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module + use config_module + use mesh_module + use component_manager_module + + implicit none + + print *, "=== CFD架构测试 ===" + print *, "" + + ! 测试1: 基本系统 + call test_basic_systems() + + ! 测试2: 组件注册 + call test_registry_integration() + + ! 测试3: 配置验证 + call test_config_validation() + + ! 测试4: 完整流程 + call test_full_workflow() + + print *, "" + print *, "=== 架构测试完成 ===" + +contains + + subroutine test_basic_systems() + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "1. 测试基本系统..." + print *, "-------------------" + + ! 测试配置 + call config_print(config) + print *, "✓ 配置创建成功" + + ! 测试网格 + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "✓ 网格创建成功" + print *, "" + end subroutine test_basic_systems + + subroutine test_registry_integration() + print *, "2. 测试注册系统集成..." + print *, "------------------------" + + call initialize_registry(verbose=.true.) + + ! 注册核心组件 + print *, "注册核心组件:" + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + ! 显示注册内容 + call component_registry%list_simple() + print *, "注册表大小: ", registry_get_size() + + call cleanup_registry() + print *, "✓ 注册系统测试完成" + print *, "" + end subroutine test_registry_integration + + subroutine test_config_validation() + type(cfd_config) :: config + logical :: is_valid + + print *, "3. 测试配置验证..." + print *, "-------------------" + + ! 测试有效配置 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + print *, "测试配置:" + print *, " - 重构器: ", trim(config%recon_scheme), " 阶数: ", config%spatial_order + print *, " - 通量: ", trim(config%flux_type) + print *, " - 波速: ", config%wave_speed + + ! 这里调用验证函数(需要先实现) + ! is_valid = validate_config(config) + print *, "✓ 配置验证接口准备就绪" + print *, "" + end subroutine test_config_validation + + subroutine test_full_workflow() + print *, "4. 测试完整流程..." + print *, "-------------------" + + print *, "步骤1: 初始化系统 ✓" + print *, "步骤2: 创建网格 ✓" + print *, "步骤3: 设置配置 ✓" + print *, "步骤4: 创建组件 (待实现)" + print *, "步骤5: 初始化解 (待实现)" + print *, "步骤6: 时间推进 (待实现)" + print *, "步骤7: 输出结果 (待实现)" + print *, "" + + print *, "✓ 流程框架定义完成" + end subroutine test_full_workflow + +end program test_architecture \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_component_manager.f90 new file mode 100644 index 000000000..f60c35056 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_component_manager.f90 @@ -0,0 +1,111 @@ +! tests/test_component_manager.f90 +program test_component_manager + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use component_manager_module, only: create_reconstructor, create_flux_calculator + use component_manager_module, only: component_manager_info, validate_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + implicit none + + type(cfd_config) :: config + class(reconstructor_base), allocatable :: recon + class(flux_calculator_base), allocatable :: flux + integer :: status + logical :: is_valid + + print *, "=== Component Manager Test ===" + print *, "" + + ! 显示组件管理器信息 + call component_manager_info() + print *, "" + + ! 测试1: 基本配置 + print *, "1. Testing basic ENO3 + Rusanov configuration..." + print *, "-----------------------------------------------" + + config%verbose = .true. + call config_print(config) + + ! 配置ENO3重构 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + call config_print(config) + print *, "" + + ! 验证配置 + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Configuration is valid" + else + print *, "[ERROR] Configuration is invalid" + end if + print *, "" + + ! 测试2: 创建组件 + print *, "2. Testing component creation..." + print *, "--------------------------------" + + ! 创建重构器(带状态检查) + recon = create_reconstructor(config, status) + if (status == 0) then + print *, "[OK] Reconstructor created successfully" + call recon%info() + else + print *, "[ERROR] Failed to create reconstructor, code:", status + end if + print *, "" + + ! 创建通量计算器 + flux = create_flux_calculator(config, status) + if (status == 0) then + print *, "[OK] Flux calculator created successfully" + call flux%info() + else + print *, "[ERROR] Failed to create flux calculator, code:", status + end if + print *, "" + + ! 测试3: WENO3重构测试 + print *, "3. Testing WENO3 configuration..." + print *, "---------------------------------" + + call config_with_reconstruction(config, "weno3", 3) + + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] WENO3 configuration is valid" + + ! 创建WENO3重构器 + recon = create_reconstructor(config) + call recon%info() + else + print *, "[ERROR] WENO3 configuration is invalid" + end if + print *, "" + + ! 测试4: 错误配置测试 + print *, "4. Testing invalid configuration..." + print *, "-----------------------------------" + + config%recon_scheme = "unknown_scheme" + config%flux_type = "unknown_flux" + + is_valid = validate_config(config) + if (.not. is_valid) then + print *, "[OK] Invalid configuration correctly rejected" + else + print *, "[ERROR] Invalid configuration should have been rejected" + end if + + ! 清理 + if (allocated(recon)) deallocate(recon) + if (allocated(flux)) deallocate(flux) + + print *, "" + print *, "=== Component manager test completed successfully ===" + +end program test_component_manager \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_domain_solution.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_domain_solution.f90 new file mode 100644 index 000000000..ff659bac2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_domain_solution.f90 @@ -0,0 +1,102 @@ +! tests/test_domain_solution.f90 +program test_domain_solution + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create, solution_reset + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + real(wp), allocatable :: initial_values(:) + integer :: i + + print *, "=== Domain and Solution Test ===" + print *, "" + + ! 测试1: 不同重构方案的ghost层计算 + print *, "1. Testing ghost layer calculation..." + print *, "--------------------------------------" + + ! ENO3 + call config_with_reconstruction(config, "eno", 3) + config%verbose = .false. + call mesh%init(ncells=10) + domain = domain_create(config, mesh) + print *, "ENO3: nghosts = ", domain%nghosts, " (expected: 3)" + + ! WENO3 + call config_with_reconstruction(config, "weno3", 3) + domain = domain_create(config, mesh) + print *, "WENO3: nghosts = ", domain%nghosts, " (expected: 2)" + + ! WENO5 + call config_with_reconstruction(config, "weno", 5) + domain = domain_create(config, mesh) + print *, "WENO5: nghosts = ", domain%nghosts, " (expected: 3)" + print *, "" + + ! 测试2: Solution数组 + print *, "2. Testing solution arrays..." + print *, "------------------------------" + + call config_with_reconstruction(config, "eno", 3) + config%verbose = .true. + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=10) + + domain = domain_create(config, mesh) + call domain%print_info() + print *, "" + + solution = solution_create(domain) + call solution%print_info() + print *, "" + + ! 测试3: 初始化和更新 + print *, "3. Testing initialization and update..." + print *, "----------------------------------------" + + allocate(initial_values(mesh%ncells)) + do i = 1, mesh%ncells + initial_values(i) = sin(2.0_wp * 3.14159265358979_wp * mesh%xcc(i) / mesh%L) + end do + + call solution%initialize(initial_values) + print *, "After initialization:" + print *, " u range: ", minval(solution%u), " to ", maxval(solution%u) + print *, " un range: ", minval(solution%un), " to ", maxval(solution%un) + + ! 修改当前解,测试更新 + solution%u = solution%u * 2.0_wp + call solution%update_old_field() + print *, "After update: max|u - un| = ", maxval(abs(solution%u - solution%un)) + print *, "" + + ! 测试4: 重置 + print *, "4. Testing reset..." + print *, "-------------------" + + call solution_reset(solution) + print *, "After reset:" + print *, " u max: ", maxval(abs(solution%u)) + print *, " un max: ", maxval(abs(solution%un)) + print *, " flux max: ", maxval(abs(solution%flux)) + print *, "" + + deallocate(initial_values) + + print *, "=== Test Summary ===" + print *, "✓ Ghost layer calculation works" + print *, "✓ Domain creation works" + print *, "✓ Solution arrays work" + print *, "✓ Initialization works" + print *, "✓ Field update works" + print *, "✓ Reset works" + print *, "" + print *, "Ready for next step: Implementing Physics modules" + +end program test_domain_solution \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_factory_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_factory_simple.f90 new file mode 100644 index 000000000..db65da7cf --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_factory_simple.f90 @@ -0,0 +1,58 @@ +! tests/test_factory_simple.f90 (修复版) +program test_factory_simple + use base_modules, only: wp ! ← 添加这行 + use config_module, only: cfd_config, config_print + use mesh_module, only: mesh_type + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(eno_reconstructor) :: eno + type(weno3_reconstructor) :: weno3 + type(rusanov_flux) :: rusanov + + print *, "=== Factory Pattern Simple Test ===" + print *, "" + + ! Test 1: Basic systems + print *, "1. Testing basic systems..." + print *, "-----------------------------" + call config_print(config) + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 2: Creating reconstructors + print *, "2. Testing reconstructors..." + print *, "------------------------------" + + ! 创建并测试ENO重构器 + print *, "Creating ENO reconstructor..." + eno = eno_reconstructor() ! 使用构造函数 + call eno%info() ! 必须调用info方法 + + print *, "" + print *, "Creating WENO3 reconstructor..." + weno3 = weno3_reconstructor() ! 使用构造函数 + call weno3%info() ! 必须调用info方法 + print *, "" + + ! Test 3: Creating flux calculator + print *, "3. Testing flux calculator..." + print *, "-------------------------------" + + print *, "Creating Rusanov flux calculator..." + rusanov = rusanov_flux() ! 使用构造函数 + call rusanov%info() ! 必须调用info方法 + print *, "" + + print *, "=== Factory pattern simple test completed successfully ===" + +end program test_factory_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_minimal_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_minimal_simple.f90 new file mode 100644 index 000000000..ec03ccf89 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_minimal_simple.f90 @@ -0,0 +1,87 @@ +! tests/test_minimal_simple.f90 +program test_minimal_simple + use base_modules, only: wp ! ← 添加这行 + use registry_module + use config_module + use mesh_module + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Simple Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call registry_init() + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call list_components() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + + if (has_component_simple("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component_simple("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components (simple version) + print *, "5. Testing registry functionality" + print *, "---------------------------------" + print *, "Registry initialized: ", registry_is_initialized() + print *, "Number of components: ", registry_get_size() + print *, "" + + ! Cleanup + call registry_cleanup() + + print *, "=== Simple test completed successfully ===" + +end program test_minimal_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_physics_minimal.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_physics_minimal.f90 new file mode 100644 index 000000000..cf2a28f19 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_physics_minimal.f90 @@ -0,0 +1,91 @@ +! tests/test_physics_minimal.f90 +program test_physics_minimal + use precision_module, only: wp, ip + use linear_convection_equation, only: linear_convection_eq, create_linear_convection_eq + use linear_convection_problem, only: linear_convection_prob, create_linear_convection_prob + + implicit none + + type(linear_convection_eq) :: eq + type(linear_convection_prob) :: prob + real(wp) :: u, f, a + real(wp), allocatable :: x(:), u_ic(:), u_exact(:) + integer :: i, nx = 10 + + print *, "=== 最小物理模块测试 ===" + print *, "" + + ! 测试1: 方程功能 + print *, "1. 测试方程功能..." + print *, "-------------------" + + eq = create_linear_convection_eq(wave_speed=2.0_wp) + print *, "方程: ", eq%name + print *, "波速: ", eq%wave_speed + + u = 1.5_wp + f = eq%flux(u) + a = eq%speed() + + print *, "u = ", u + print *, "F(u) = ", f, " (期望: 3.0)" + print *, "波速 a = ", a, " (期望: 2.0)" + + if (abs(f - 3.0_wp) < 1e-10_wp .and. abs(a - 2.0_wp) < 1e-10_wp) then + print *, "✓ 方程功能正常" + else + print *, "✗ 方程功能异常" + end if + print *, "" + + ! 测试2: 问题功能 + print *, "2. 测试问题功能..." + print *, "-------------------" + + prob = create_linear_convection_prob(ic_type="step", domain_length=2.0_wp) + print *, "问题: ", prob%name + print *, "IC类型: ", trim(prob%ic_type) + print *, "域长度: ", prob%domain_length + + allocate(x(nx), u_ic(nx), u_exact(nx)) + do i = 1, nx + x(i) = 0.0_wp + (i-1) * 0.2_wp + end do + + ! 测试初始条件 + call prob%initial_condition(x, u_ic) + print *, "初始条件范围: ", minval(u_ic), " 到 ", maxval(u_ic) + + ! 测试精确解 + u_exact = prob%exact_solution(x, 0.0_wp) + print *, "t=0时精确解范围: ", minval(u_exact), " 到 ", maxval(u_exact) + + ! 检查阶跃函数 + if (abs(u_ic(1) - 1.0_wp) < 1e-10_wp .and. & + abs(u_ic(6) - 2.0_wp) < 1e-10_wp) then + print *, "✓ 阶跃初始条件正确" + else + print *, "✗ 阶跃初始条件错误" + end if + + ! 检查精确解与初始条件一致 + if (maxval(abs(u_ic - u_exact)) < 1e-10_wp) then + print *, "✓ t=0时精确解与初始条件一致" + else + print *, "✗ 精确解计算错误" + end if + print *, "" + + ! 测试3: 边界条件接口 + print *, "3. 测试边界条件接口..." + print *, "----------------------" + call prob%boundary_condition(u_ic, 0.0_wp) + print *, "✓ 边界条件接口正常" + print *, "" + + deallocate(x, u_ic, u_exact) + + print *, "=== 物理模块最小测试完成 ===" + print *, "下一步: 将物理模块集成到现有系统中" + +end program test_physics_minimal \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_simple_link.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_simple_link.f90 new file mode 100644 index 000000000..71cc614e8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_simple_link.f90 @@ -0,0 +1,78 @@ +! tests/test_simple_link.f90 +program test_simple_link + use base_modules, only: wp ! ← 添加这行 + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call registry_init() + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call list_components() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component_simple("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component_simple("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Cleanup + call registry_cleanup() + + print *, "=== Minimal test completed successfully ===" + +end program test_simple_link \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_solver_framework.f90 b/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_solver_framework.f90 new file mode 100644 index 000000000..6754323d0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02g/tests/test_solver_framework.f90 @@ -0,0 +1,91 @@ +! tests/test_solver_framework.f90 +program test_solver_framework + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use mesh_module, only: mesh_type + use solver_module, only: cfd_solver, solver_create, solver_run, solver_cleanup + use solver_module, only: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, & + SOLVER_COMPLETED, SOLVER_ERROR + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(cfd_solver) :: solver + + print *, "=== 求解器框架测试 ===" + print *, "" + + ! 测试1: 基本创建 + print *, "1. 测试求解器创建..." + print *, "----------------------" + + ! 创建配置 + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.0_real64 + config%dt = 0.01_real64 + + call config_print(config) + print *, "" + + ! 创建网格 + call mesh%init(xmin=0.0_real64, xmax=2.0_real64, ncells=20) + call mesh%print_info() + print *, "" + + ! 创建求解器 + solver = solver_create(config, mesh) + print *, "✓ 求解器创建成功" + print *, " 状态: ", solver%get_state() + print *, "" + + ! 测试2: 求解器初始化 + print *, "2. 测试求解器初始化..." + print *, "------------------------" + + call solver%initialize() + print *, "✓ 求解器初始化完成" + print *, " 状态: ", solver%get_state() + print *, " 错误信息: '", trim(solver%get_error()), "'" + print *, "" + + ! 测试3: 简单运行 + print *, "3. 测试求解器运行..." + print *, "----------------------" + + call solver_run(solver, 0.05_real64) ! 运行到0.05秒 + print *, "✓ 求解器运行完成" + print *, " 最终状态: ", solver%get_state() + print *, "" + + ! 测试4: 清理 + print *, "4. 测试求解器清理..." + print *, "----------------------" + + call solver_cleanup(solver) + print *, "✓ 求解器清理完成" + print *, " 状态: ", solver%get_state() + print *, "" + + ! 测试5: 错误处理 + print *, "5. 测试错误处理..." + print *, "-------------------" + + ! 尝试重复初始化 + call solver%initialize() + print *, " 重复初始化状态: ", solver%get_state() + print *, " 错误信息: '", trim(solver%get_error()), "'" + + call solver_cleanup(solver) + print *, "" + + print *, "=== 框架测试总结 ===" + print *, "✓ 求解器创建/初始化/运行/清理流程验证完成" + print *, "✓ 状态管理正常工作" + print *, "✓ 错误处理机制就绪" + print *, "" + print *, "下一步: 添加实际数值计算功能" + +end program test_solver_framework \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02h/CMakeLists.txt new file mode 100644 index 000000000..ef66d5848 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/README.md b/example/1d-linear-convection/weno3/fortran/registry/02h/README.md new file mode 100644 index 000000000..c4889757b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/README.md @@ -0,0 +1,221 @@ +# Fortran CFD Project + +基于工厂模式和注册系统的CFD求解器框架。 + +## 项目结构 + +``` +fortran_cfd/ +├── CMakeLists.txt # 根CMake配置 +├── scripts/ # 构建脚本 +│ ├── build.py # Python构建脚本(推荐) +│ ├── build.bat # Batch包装脚本 +│ ├── build.ps1 # PowerShell包装脚本 +│ └── requirements.txt # Python依赖 +├── src/ # 源代码 +│ ├── CMakeLists.txt +│ ├── core/ # 核心模块(注册系统、工厂接口) +│ ├── infrastructure/ # 基础设施(配置、网格) +│ └── numerics/ # 数值方法 +├── tests/ # 测试 +│ ├── CMakeLists.txt +│ ├── test_minimal_simple.f90 # 简单测试 +│ └── test_factory.f90 # 工厂模式测试 +└── README.md # 项目说明(本文件) +``` + +## 快速开始 + +### 使用Python脚本(推荐) + +```bash +# 进入脚本目录 +cd scripts + +# 基本构建 +python build.py + +# 清理并构建 +python build.py --clean + +# 发布构建 +python build.py --build-type Release --clean + +# 并行构建(使用所有核心) +python build.py -j + +# 不运行测试 +python build.py --no-tests + +# 查看帮助 +python build.py --help +``` + +### 使用批处理脚本 + +```bash +cd scripts +.\build.bat +``` + +### 使用PowerShell脚本 + +```powershell +cd scripts +.\build.ps1 +``` + +## 手动构建 + +```bash +# 设置Intel环境 +cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && powershell' + +# 配置项目 +mkdir build +cd build +cmake -G "Visual Studio 17 2022" -A x64 -T fortran=ifx .. + +# 构建项目 +cmake --build . --config Debug + +# 运行测试 +Debug\test_simple.exe +Debug\test_factory.exe +``` + +## 功能特性 + +✅ 组件注册系统 +✅ 工厂模式 +✅ ENO/WENO重构器 +✅ Rusanov通量计算器 +✅ 可扩展架构 +✅ 自动化测试 + +## 依赖 + +- Intel oneAPI(包含ifx编译器) +- CMake 3.12+ +- Python 3.6+(仅用于构建脚本) + +## 源代码文件 + +### 核心模块 +- `src/core/registry.f90` - 组件注册系统 +- `src/core/factory_interfaces.f90` - 工厂接口 + +### 基础设施 +- `src/infrastructure/config.f90` - 配置管理 +- `src/infrastructure/mesh.f90` - 网格生成 + +### 数值方法 +- `src/numerics/reconstructor/base.f90` - 重构器基类 +- `src/numerics/reconstructor/eno.f90` - ENO重构器 +- `src/numerics/reconstructor/weno3.f90` - WENO-3重构器 +- `src/numerics/flux/base.f90` - 通量计算器基类 +- `src/numerics/flux/rusanov.f90` - Rusanov通量 + +### 测试 +- `tests/test_minimal_simple.f90` - 简单功能测试 +- `tests/test_factory.f90` - 工厂模式测试 + +## 构建脚本 + +### Python脚本 (build.py) +主构建脚本,支持: +- 自动设置Intel oneAPI环境 +- 参数化构建选项 +- 并行编译 +- 自动化测试 +- 彩色输出 + +### Batch脚本 (build.bat) +Windows批处理包装器,调用Python脚本。 + +### PowerShell脚本 (build.ps1) +PowerShell包装器,调用Python脚本。 + +## 示例输出 + +成功构建后,会看到类似输出: + +``` +============================================================ + Fortran CFD Project Builder +============================================================ + +[1/4] Setting up Intel Fortran compiler... +✓ Intel oneAPI environment configured + +[2/4] Preparing build directory... +✓ Cleaned build directory + +[3/4] Configuring project... + $ cmake -G Visual Studio 17 2022 -A x64 -T fortran=ifx -DCMAKE_BUILD_TYPE=Debug .. +✓ CMake configuration successful + +[4/4] Building project... + $ cmake --build . --config Debug +✓ Build completed in 12.3 seconds + +============================================================ + Running Tests +============================================================ + +[TEST] Simple functionality test... +✓ Simple test passed + +[TEST] Factory pattern test... +✓ Factory test passed + +============================================================ + Build Summary +============================================================ +✓ Build directory: D:\path\to\build +✓ Build type: Debug +✓ Total time: 15.2s +``` + +## 开发指南 + +### 添加新组件 + +1. 在相应模块中创建Fortran源文件 +2. 实现工厂函数 +3. 使用`register_component_with_factory`注册组件 +4. 添加测试 + +### 扩展架构 + +项目采用模块化设计: +1. **注册系统** - 管理所有可用的组件 +2. **工厂模式** - 动态创建组件实例 +3. **抽象接口** - 确保组件兼容性 +4. **配置驱动** - 通过配置文件控制行为 + +## 故障排除 + +### 常见问题 + +1. **Intel环境未找到** + - 检查Intel oneAPI安装路径 + - 手动运行`setvars.bat` + +2. **CMake配置失败** + - 确保使用正确的生成器:`Visual Studio 17 2022` + - 检查Fortran编译器是否可用 + +3. **构建失败** + - 检查依赖项是否完整 + - 查看详细的错误信息 + +### 调试建议 + +- 使用`--verbose`参数获取详细输出 +- 检查`build/CMakeCache.txt`了解配置详情 +- 查看编译器错误日志 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/scripts/build.bat b/example/1d-linear-convection/weno3/fortran/registry/02h/scripts/build.bat new file mode 100644 index 000000000..6fd6dc031 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/scripts/build.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Project Builder +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python build script with full Intel environment support... +echo. + +python build.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Build failed + pause + exit /b 1 +) + +echo. +echo [INFO] Build completed successfully! +echo. +echo [INFO] To run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/scripts/build.py b/example/1d-linear-convection/weno3/fortran/registry/02h/scripts/build.py new file mode 100644 index 000000000..3bf6d5375 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/scripts/build.py @@ -0,0 +1,629 @@ +#!/usr/bin/env python3 +""" +Fortran CFD Project Builder - 完整Python解决方案 +在Python内部处理Intel oneAPI环境配置 +""" + +import os +import sys +import subprocess +import shutil +import argparse +import time +import platform +import tempfile +from pathlib import Path + +class IntelEnvironment: + """Intel oneAPI环境管理器""" + + def __init__(self): + self.setvars_path = None + self.env_vars = {} + + def find_setvars(self): + """查找setvars.bat文件""" + possible_paths = [ + r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat", + r"C:\Program Files\Intel\oneAPI\setvars.bat", + r"C:\Program Files (x86)\Intel\oneAPI\compiler\latest\env\vars.bat", + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\setvars.bat"), + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\compiler\latest\env\vars.bat"), + ] + + for path in possible_paths: + if os.path.exists(path): + self.setvars_path = path + return True + + return False + + def setup_environment(self): + """设置Intel环境""" + if not self.find_setvars(): + return False + + try: + # 创建临时的批处理文件来捕获环境变量 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + f.write(f'@echo off\n') + f.write(f'call "{self.setvars_path}" >nul 2>&1\n') + f.write(f'set\n') # 输出所有环境变量 + temp_bat = f.name + + # 运行批处理文件并捕获输出 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + shell=True + ) + + # 解析环境变量 + for line in result.stdout.split('\n'): + line = line.strip() + if '=' in line: + key, value = line.split('=', 1) + self.env_vars[key.strip()] = value.strip() + + # 清理临时文件 + os.unlink(temp_bat) + + # 更新当前进程的环境变量 + os.environ.update(self.env_vars) + + return True + + except Exception as e: + print(f"设置Intel环境失败: {e}") + return False + + def get_compiler_info(self): + """获取编译器信息""" + info = {} + + # 检查ifx编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + env={**os.environ, **self.env_vars} if self.env_vars else os.environ + ) + + if result.returncode == 0: + for line in result.stdout.split('\n'): + if 'Version' in line or '版本' in line: + info['ifx_version'] = line.strip() + break + except: + pass + + # 检查环境变量 + info['ifx_root'] = self.env_vars.get('IFX_ROOT', '') + info['compiler_root'] = self.env_vars.get('ONEAPI_ROOT', '') + + return info + +class BuildSystem: + """构建系统主类""" + + def __init__(self): + self.project_root = Path(__file__).parent.parent + self.build_dir = self.project_root / "build" + self.intel_env = IntelEnvironment() + + # 设置控制台编码 + if sys.platform == "win32": + try: + import ctypes + # 设置控制台输出为UTF-8 + ctypes.windll.kernel32.SetConsoleOutputCP(65001) + except: + pass + + def print_header(self, text): + """打印标题""" + print(f"\n{'='*70}") + print(f" {text}") + print(f"{'='*70}\n") + + def print_step(self, step, total, message): + """打印步骤""" + print(f"[{step}/{total}] {message}...") + + def print_success(self, message): + """打印成功""" + print(f"\033[92m✓ {message}\033[0m") + + def print_error(self, message): + """打印错误""" + print(f"\033[91m✗ {message}\033[0m") + + def print_warning(self, message): + """打印警告""" + print(f"\033[93m! {message}\033[0m") + + def print_info(self, message): + """打印信息""" + print(f"\033[94mℹ {message}\033[0m") + + def check_prerequisites(self): + """检查前提条件""" + self.print_step(1, 6, "检查前提条件") + + # 检查Python版本 + python_version = sys.version.split()[0] + self.print_info(f"Python版本: {python_version}") + + # 检查平台 + self.print_info(f"平台: {platform.system()} {platform.release()}") + self.print_info(f"处理器核心数: {os.cpu_count()}") + + # 检查CMake + try: + result = subprocess.run( + ["cmake", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + version_line = result.stdout.split('\n')[0] + self.print_success(f"CMake: {version_line}") + else: + self.print_error("CMake未找到") + return False + except FileNotFoundError: + self.print_error("CMake未安装") + return False + + return True + + def setup_intel_environment(self, args): + """设置Intel环境""" + self.print_step(2, 6, "配置Intel oneAPI环境") + + if not self.intel_env.find_setvars(): + self.print_warning("未找到Intel oneAPI setvars.bat") + self.print_info("将尝试使用系统环境中的编译器") + + # 检查是否能直接访问编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + self.print_success("Intel编译器在系统PATH中找到") + return True + else: + self.print_warning("Intel编译器未在PATH中找到") + except: + self.print_warning("无法访问Intel编译器") + + return True # 继续,让CMake自己找编译器 + + # 设置环境 + if self.intel_env.setup_environment(): + compiler_info = self.intel_env.get_compiler_info() + + if compiler_info.get('ifx_version'): + self.print_success(f"Intel Fortran编译器: {compiler_info['ifx_version']}") + elif compiler_info.get('ifx_root'): + self.print_success(f"Intel编译器路径: {compiler_info['ifx_root']}") + else: + self.print_success("Intel oneAPI环境配置完成") + + return True + else: + self.print_warning("Intel环境配置失败,将继续使用系统环境") + return True + + def clean_build_directory(self, args): + """清理构建目录""" + if args.clean and self.build_dir.exists(): + self.print_info("清理构建目录...") + try: + shutil.rmtree(self.build_dir) + self.print_success("构建目录已清理") + except Exception as e: + self.print_error(f"清理失败: {e}") + if not args.force: + return False + return True + + def run_command(self, cmd, cwd=None, check=True, env=None): + """运行命令""" + if isinstance(cmd, list): + cmd_str = ' '.join(str(c) for c in cmd if c) + else: + cmd_str = str(cmd) + + print(f" \033[96m$\033[0m {cmd_str}") + + try: + # 合并环境变量 + exec_env = os.environ.copy() + if env: + exec_env.update(env) + if self.intel_env.env_vars: + exec_env.update(self.intel_env.env_vars) + + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=False, + env=exec_env + ) + + # 处理输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or '完成' in line or '生成' in line: + print(f" \033[92m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + if check and result.returncode != 0: + self.print_error(f"命令执行失败,退出码: {result.returncode}") + return False + + return True + + except Exception as e: + self.print_error(f"命令执行异常: {e}") + return False + + def configure_cmake(self, args): + """配置CMake""" + self.print_step(3, 6, "配置CMake项目") + + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + f"-DCMAKE_BUILD_TYPE={args.build_type}", + ] + + if args.compiler == "ifx": + cmake_cmd.extend(["-T", "fortran=ifx"]) + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + success = self.run_command(cmake_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("CMake配置完成") + else: + self.print_error("CMake配置失败") + + return success + + def build_project(self, args): + """构建项目""" + self.print_step(4, 6, "构建项目") + + build_cmd = [ + "cmake", + "--build", ".", + "--config", args.build_type, + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + success = self.run_command(build_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("项目构建完成") + else: + self.print_error("构建失败") + + return success + + def run_tests_with_environment(self, test_exe): + """运行单个测试,确保有Intel环境""" + try: + # 创建临时的批处理文件来运行测试 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'"{test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'"{test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + return result + + except Exception as e: + print(f"运行测试失败: {e}") + return None + + def run_tests(self, args): + """运行测试""" + self.print_step(5, 6, "运行测试") + + # 查找测试可执行文件 + test_dir = self.build_dir / "bin" / args.build_type + if not test_dir.exists(): + test_dir = self.build_dir / "bin" + if not test_dir.exists(): + test_dir = self.build_dir + + test_files = list(test_dir.glob("test_*.exe")) + + if not test_files: + self.print_warning("未找到测试程序") + return True + + all_passed = True + + for test_exe in sorted(test_files): + test_name = test_exe.stem + self.print_info(f"运行测试: {test_name}") + print(f" {'-'*50}") + + # 运行测试 + result = self.run_tests_with_environment(str(test_exe)) + + if result is None: + self.print_error(f" {test_name} 运行失败") + all_passed = False + continue + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + print(f" {line}") + + if result.returncode == 0: + self.print_success(f" {test_name} 通过") + else: + self.print_error(f" {test_name} 失败 (退出码: {result.returncode})") + all_passed = False + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + print() # 空行 + + return all_passed + + def create_test_runner(self, args): + """创建独立的测试运行器""" + self.print_step(6, 6, "创建测试运行器") + + runner_path = self.build_dir / "run_tests.bat" + + content = f'''@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Test Runner +echo ======================================== +echo. + +REM Setup Intel oneAPI environment +set "SETVARS_PATH={self.intel_env.setvars_path or ''}" +if exist "%SETVARS_PATH%" ( + call "%SETVARS_PATH%" >nul + echo [INFO] Intel environment configured +) else ( + echo [WARNING] Intel environment not found + echo [WARNING] Tests may fail without runtime libraries +) + +echo. + +REM Run all test executables +set "TEST_COUNT=0" +set "PASS_COUNT=0" + +for %%f in ("bin\\{args.build_type}\\test_*.exe") do ( + set /a TEST_COUNT+=1 + echo [TEST %%f] + echo {'-'*50} + + %%f + if errorlevel 1 ( + echo [FAILED] %%f + ) else ( + echo [PASSED] %%f + set /a PASS_COUNT+=1 + ) + echo. +) + +echo ======================================== +echo Tests: %PASS_COUNT%/%TEST_COUNT% passed +if %PASS_COUNT% equ %TEST_COUNT% ( + echo [SUCCESS] All tests passed! +) else ( + echo [FAILURE] Some tests failed +) +echo ======================================== + +pause +''' + + with open(runner_path, 'w', encoding='utf-8') as f: + f.write(content) + + self.print_success(f"测试运行器已创建: {runner_path}") + self.print_info(f"使用方法: cd build && run_tests.bat") + + return runner_path + + def generate_report(self, args, build_time, tests_passed): + """生成构建报告""" + self.print_header("构建完成") + + print(f"项目: {self.project_root.name}") + print(f"构建类型: {args.build_type}") + print(f"编译器: {args.compiler}") + print(f"并行作业: {args.jobs}") + print(f"总耗时: {build_time:.1f}秒") + print(f"测试结果: {'全部通过' if tests_passed else '有失败'}") + + # 显示生成的可执行文件 + bin_dir = self.build_dir / "bin" / args.build_type + if bin_dir.exists(): + print(f"\n生成的可执行文件:") + for exe in sorted(bin_dir.glob("*.exe")): + size_mb = exe.stat().st_size / (1024 * 1024) + print(f" • {exe.name} ({size_mb:.2f} MB)") + + # 显示测试运行器信息 + runner_path = self.build_dir / "run_tests.bat" + if runner_path.exists(): + print(f"\n独立测试运行器:") + print(f" • {runner_path.name}") + print(f" 在Intel oneAPI环境中运行所有测试") + + def run(self): + """运行构建系统""" + parser = argparse.ArgumentParser( + description="Fortran CFD项目构建工具", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认构建 + %(prog)s --clean # 清理后构建 + %(prog)s --build-type Release # Release构建 + %(prog)s --no-tests # 只构建,不运行测试 + %(prog)s -j8 --verbose # 8线程并行构建,详细输出 + """ + ) + + parser.add_argument("--build-type", choices=["Debug", "Release"], + default="Debug", help="构建类型") + parser.add_argument("--compiler", choices=["ifx", "ifort"], + default="ifx", help="Fortran编译器") + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-tests", action="store_true", + help="跳过测试") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + + args = parser.parse_args() + + # 开始构建 + start_time = time.time() + + self.print_header("Fortran CFD 项目构建系统") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 检查前提条件 + if not self.check_prerequisites(): + if not args.force: + return 1 + + # 2. 设置Intel环境 + if not self.setup_intel_environment(args): + if not args.force: + return 1 + + # 3. 清理目录 + if not self.clean_build_directory(args): + if not args.force: + return 1 + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 4. 配置CMake + if not self.configure_cmake(args): + if not args.force: + return 1 + + # 5. 构建项目 + if not self.build_project(args): + if not args.force: + return 1 + + # 6. 运行测试和创建测试运行器 + tests_passed = True + if not args.no_tests: + tests_passed = self.run_tests(args) + + # 创建测试运行器 + self.create_test_runner(args) # 传递 args 参数 + + # 7. 生成报告 + build_time = time.time() - start_time + self.generate_report(args, build_time, tests_passed) + + return 0 if tests_passed else 1 + + except KeyboardInterrupt: + self.print_error("\n构建被用户中断") + return 1 + except Exception as e: + self.print_error(f"构建过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + builder = BuildSystem() + return builder.run() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/scripts/requirements.txt b/example/1d-linear-convection/weno3/fortran/registry/02h/scripts/requirements.txt new file mode 100644 index 000000000..d21a12b45 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/scripts/requirements.txt @@ -0,0 +1,2 @@ +# 构建脚本的Python依赖(可选) +# 目前没有特殊依赖,保持空文件或添加未来可能需要的包 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/scripts/run_all_steps.bat b/example/1d-linear-convection/weno3/fortran/registry/02h/scripts/run_all_steps.bat new file mode 100644 index 000000000..d506149b2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/scripts/run_all_steps.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo CFD Project: All Steps +echo ======================================== +echo. + +echo [INFO] Starting Step 1: Physics Modules Test... +call run_step1.bat + +if errorlevel 1 ( + echo [ERROR] Step 1 failed + pause + exit /b 1 +) + +echo. +echo [INFO] Starting Step 2: Configuration Physics Update... +call run_step2.bat + +if errorlevel 1 ( + echo [ERROR] Step 2 failed + pause + exit /b 1 +) + +echo. +echo ======================================== +echo All Steps Completed Successfully! +echo ======================================== +echo. +echo [INFO] Next: Update component manager for physics support +echo [INFO] Run: run_step3.bat (to be created) +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/scripts/run_step1.bat b/example/1d-linear-convection/weno3/fortran/registry/02h/scripts/run_step1.bat new file mode 100644 index 000000000..0b6b1f17e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/scripts/run_step1.bat @@ -0,0 +1,39 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Step 1: Physics Modules Test +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python step1 script with full Intel environment support... +echo. + +python run_step1.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Step 1 failed + pause + exit /b 1 +) + +echo. +echo [INFO] Step 1 completed successfully! +echo. +echo [INFO] Next step: Update config to include physics settings +echo [INFO] Run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/scripts/run_step1.py b/example/1d-linear-convection/weno3/fortran/registry/02h/scripts/run_step1.py new file mode 100644 index 000000000..5e087a690 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/scripts/run_step1.py @@ -0,0 +1,319 @@ +#!/usr/bin/env python3 +""" +Step 1: Physics Modules Test +扩展build.py,专门用于测试物理模块 +""" + +import os +import sys +import subprocess +import time +from pathlib import Path + +# 添加当前目录到路径,以便导入build.py的类 +sys.path.insert(0, str(Path(__file__).parent)) + +try: + from build import IntelEnvironment, BuildSystem +except ImportError: + print("Error: Cannot import build.py. Make sure build.py is in the same directory.") + sys.exit(1) + +class Step1System(BuildSystem): + """Step 1 测试系统,继承自BuildSystem""" + + def __init__(self): + super().__init__() + self.test_name = "test_physics_minimal" + self.test_exe = None + + def find_test_executable(self): + """查找测试可执行文件""" + possible_paths = [ + self.build_dir / "bin" / "Debug" / f"{self.test_name}.exe", + self.build_dir / "Debug" / f"{self.test_name}.exe", + self.build_dir / f"{self.test_name}.exe", + self.build_dir / "bin" / f"{self.test_name}.exe", + ] + + for path in possible_paths: + if path.exists(): + self.test_exe = path + self.print_success(f"Found test executable: {path}") + return True + + # 如果没有找到,尝试搜索 + self.print_warning(f"Could not find {self.test_name}.exe") + self.print_info("Searching for test executables...") + + try: + result = subprocess.run( + ["dir", str(self.build_dir), "/s", "/b", "*.exe"], + capture_output=True, + text=True, + encoding='utf-8', + shell=True + ) + + if result.returncode == 0: + test_files = [line.strip() for line in result.stdout.split('\n') + if line and 'test_' in line.lower()] + + if test_files: + self.print_info("Found test files:") + for test_file in test_files: + self.print_info(f" {test_file}") + return False + except: + pass + + return False + + def run_test_with_intel_env(self): + """在Intel环境下运行测试""" + if not self.test_exe: + self.print_error("No test executable found") + return False + + self.print_step(1, 2, f"Running test: {self.test_exe.name}") + + try: + # 创建临时的批处理文件来运行测试(包含Intel环境) + import tempfile + + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + # 设置Intel环境 + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'echo [INFO] Intel environment configured\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'echo [WARNING] Intel environment not found\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + self.print_info(f"Command: {temp_bat}") + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower() or 'fail' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or 'pass' in line.lower() or '✓' in line: + print(f" \033[92m{line}\033[0m") + elif '=' in line or '---' in line: + print(f" \033[96m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + self.print_warning("Test stderr output:") + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + self.print_step(2, 2, "Test execution completed") + + if result.returncode == 0: + self.print_success("Test passed") + return True + else: + self.print_error(f"Test failed (exit code: {result.returncode})") + return False + + except Exception as e: + self.print_error(f"Failed to run test: {e}") + return False + + def build_project_if_needed(self, args): + """如果需要,构建项目""" + if args.no_build: + self.print_info("Skipping build (--no-build flag)") + return True + + self.print_step(1, 3, "Building project") + + # 调用父类的构建方法 + build_args = argparse.Namespace() + build_args.clean = args.clean + build_args.build_type = "Debug" + build_args.compiler = "ifx" + build_args.no_tests = True # 不运行所有测试 + build_args.jobs = os.cpu_count() + build_args.verbose = args.verbose + build_args.force = args.force + + # 清理构建目录 + if args.clean and self.build_dir.exists(): + self.print_info("Cleaning build directory...") + import shutil + try: + shutil.rmtree(self.build_dir) + self.print_success("Build directory cleaned") + except Exception as e: + self.print_error(f"Clean failed: {e}") + if not args.force: + return False + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 配置CMake + self.print_step(2, 3, "Configuring CMake") + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + "-DCMAKE_BUILD_TYPE=Debug", + "-T", "fortran=ifx", + ] + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + if not self.run_command(cmake_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + # 构建项目 + self.print_step(3, 3, "Building project") + build_cmd = [ + "cmake", + "--build", ".", + "--config", "Debug", + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + if not self.run_command(build_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + self.print_success("Build completed") + return True + + def run(self): + """运行Step 1测试""" + parser = argparse.ArgumentParser( + description="Step 1: Physics Modules Test", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认运行 + %(prog)s --clean # 清理后构建并测试 + %(prog)s --no-build # 只运行测试,不重新构建 + %(prog)s --verbose # 详细输出 + %(prog)s -j4 # 使用4个并行作业构建 + """ + ) + + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-build", action="store_true", + help="不重新构建,直接运行测试") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + + args = parser.parse_args() + + # 开始测试 + start_time = time.time() + + self.print_header("Step 1: Physics Modules Implementation Test") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 设置Intel环境 + self.print_step(1, 4, "Setting up Intel oneAPI environment") + if not self.setup_intel_environment(args): + if not args.force: + self.print_error("Intel environment setup failed") + return 1 + self.print_warning("Intel environment setup failed, continuing...") + + # 2. 构建项目(如果需要) + self.print_step(2, 4, "Building project if needed") + if not self.build_project_if_needed(args): + if not args.force: + return 1 + + # 3. 查找测试可执行文件 + self.print_step(3, 4, "Finding test executable") + if not self.find_test_executable(): + if not args.force: + return 1 + self.print_warning("Test executable not found, but continuing due to --force") + return 0 + + # 4. 运行测试 + self.print_step(4, 4, "Running physics module test") + test_passed = self.run_test_with_intel_env() + + # 生成报告 + test_time = time.time() - start_time + self.print_header("Step 1 Complete") + + print(f"测试: {'通过 ✓' if test_passed else '失败 ✗'}") + print(f"测试程序: {self.test_exe.name if self.test_exe else '未找到'}") + print(f"总耗时: {test_time:.1f}秒") + + if test_passed: + print(f"\n下一步: 更新配置以包含物理设置") + print(f"建议: 修改config.f90,添加physics相关字段") + return 0 + else: + if args.force: + self.print_warning("测试失败,但由于--force标志继续执行") + return 0 + return 1 + + except KeyboardInterrupt: + self.print_error("\n测试被用户中断") + return 1 + except Exception as e: + self.print_error(f"测试过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + system = Step1System() + return system.run() + +if __name__ == "__main__": + # 需要导入argparse + import argparse + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/scripts/run_step2.bat b/example/1d-linear-convection/weno3/fortran/registry/02h/scripts/run_step2.bat new file mode 100644 index 000000000..9c1f62de4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/scripts/run_step2.bat @@ -0,0 +1,39 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Step 2: Configuration Physics Update +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python step2 script with full Intel environment support... +echo. + +python run_step2.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Step 2 failed + pause + exit /b 1 +) + +echo. +echo [INFO] Step 2 completed successfully! +echo. +echo [INFO] Next step: Update component manager to support physics +echo [INFO] Run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/scripts/run_step2.py b/example/1d-linear-convection/weno3/fortran/registry/02h/scripts/run_step2.py new file mode 100644 index 000000000..c16b76088 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/scripts/run_step2.py @@ -0,0 +1,284 @@ +#!/usr/bin/env python3 +""" +Step 2: Configuration Physics Update +测试配置模块的物理功能更新 +""" + +import os +import sys +import subprocess +import time +from pathlib import Path + +# 添加当前目录到路径,以便导入build.py的类 +sys.path.insert(0, str(Path(__file__).parent)) + +try: + from build import IntelEnvironment, BuildSystem +except ImportError: + print("Error: Cannot import build.py. Make sure build.py is in the same directory.") + sys.exit(1) + +class Step2System(BuildSystem): + """Step 2 测试系统,继承自BuildSystem""" + + def __init__(self): + super().__init__() + self.test_name = "test_config_physics" + self.test_exe = None + + def find_test_executable(self): + """查找测试可执行文件""" + possible_paths = [ + self.build_dir / "bin" / "Debug" / f"{self.test_name}.exe", + self.build_dir / "Debug" / f"{self.test_name}.exe", + self.build_dir / f"{self.test_name}.exe", + self.build_dir / "bin" / f"{self.test_name}.exe", + ] + + for path in possible_paths: + if path.exists(): + self.test_exe = path + self.print_success(f"Found test executable: {path}") + return True + + return False + + def run_test_with_intel_env(self): + """在Intel环境下运行测试""" + if not self.test_exe: + self.print_error("No test executable found") + return False + + self.print_step(1, 2, f"Running test: {self.test_exe.name}") + + try: + # 创建临时的批处理文件来运行测试(包含Intel环境) + import tempfile + + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + # 设置Intel环境 + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'echo [INFO] Intel environment configured\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'echo [WARNING] Intel environment not found\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + self.print_info(f"Command: {temp_bat}") + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower() or 'fail' in line.lower() or '✗' in line: + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or 'pass' in line.lower() or '✓' in line: + print(f" \033[92m{line}\033[0m") + elif '=' in line or '---' in line or '===' in line: + print(f" \033[96m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + self.print_warning("Test stderr output:") + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + self.print_step(2, 2, "Test execution completed") + + if result.returncode == 0: + self.print_success("Test passed") + return True + else: + self.print_error(f"Test failed (exit code: {result.returncode})") + return False + + except Exception as e: + self.print_error(f"Failed to run test: {e}") + return False + + def build_project(self, args): + """构建项目""" + if args.no_build: + self.print_info("Skipping build (--no-build flag)") + return True + + self.print_step(1, 3, "Building project") + + # 清理构建目录 + if args.clean and self.build_dir.exists(): + self.print_info("Cleaning build directory...") + import shutil + try: + shutil.rmtree(self.build_dir) + self.print_success("Build directory cleaned") + except Exception as e: + self.print_error(f"Clean failed: {e}") + if not args.force: + return False + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 配置CMake + self.print_step(2, 3, "Configuring CMake") + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + "-DCMAKE_BUILD_TYPE=Debug", + "-T", "fortran=ifx", + ] + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + if not self.run_command(cmake_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + # 构建项目 + self.print_step(3, 3, "Building project") + build_cmd = [ + "cmake", + "--build", ".", + "--config", "Debug", + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + if not self.run_command(build_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + self.print_success("Build completed") + return True + + def run(self): + """运行Step 2测试""" + import argparse + + parser = argparse.ArgumentParser( + description="Step 2: Configuration Physics Update", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认运行 + %(prog)s --clean # 清理后构建并测试 + %(prog)s --no-build # 只运行测试,不重新构建 + %(prog)s --verbose # 详细输出 + """ + ) + + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-build", action="store_true", + help="不重新构建,直接运行测试") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + + args = parser.parse_args() + + # 开始测试 + start_time = time.time() + + self.print_header("Step 2: Configuration Physics Update") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 设置Intel环境 + self.print_step(1, 4, "Setting up Intel oneAPI environment") + if not self.setup_intel_environment(args): + if not args.force: + self.print_error("Intel environment setup failed") + return 1 + self.print_warning("Intel environment setup failed, continuing...") + + # 2. 构建项目(如果需要) + self.print_step(2, 4, "Building project if needed") + if not self.build_project(args): + if not args.force: + return 1 + + # 3. 查找测试可执行文件 + self.print_step(3, 4, "Finding test executable") + if not self.find_test_executable(): + self.print_error(f"Test executable {self.test_name}.exe not found") + if not args.force: + return 1 + self.print_warning("Test executable not found, but continuing due to --force") + return 0 + + # 4. 运行测试 + self.print_step(4, 4, "Running configuration physics test") + test_passed = self.run_test_with_intel_env() + + # 生成报告 + test_time = time.time() - start_time + self.print_header("Step 2 Complete") + + print(f"测试: {'通过 ✓' if test_passed else '失败 ✗'}") + print(f"测试程序: {self.test_exe.name if self.test_exe else '未找到'}") + print(f"总耗时: {test_time:.1f}秒") + + if test_passed: + print(f"\n下一步: 更新组件管理器以支持物理模块") + print(f"建议: 修改component_manager.f90,添加physics组件创建") + return 0 + else: + if args.force: + self.print_warning("测试失败,但由于--flag继续执行") + return 0 + return 1 + + except KeyboardInterrupt: + self.print_error("\n测试被用户中断") + return 1 + except Exception as e: + self.print_error(f"测试过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + system = Step2System() + return system.run() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02h/src/CMakeLists.txt new file mode 100644 index 000000000..8a76bdb52 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/src/CMakeLists.txt @@ -0,0 +1,17 @@ +# ==================== src\CMakeLists.txt ==================== + +# src/CMakeLists.txt +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(base) +add_subdirectory(core) +add_subdirectory(infrastructure) +add_subdirectory(numerics) +add_subdirectory(physics) # ← 新增物理模块目录 +add_subdirectory(manager) + +message(STATUS "源代码目录配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/src/base/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02h/src/base/CMakeLists.txt new file mode 100644 index 000000000..74f4aa65f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/src/base/CMakeLists.txt @@ -0,0 +1,16 @@ +# src/base/CMakeLists.txt +message(STATUS "Configuring base module...") + +add_library(base STATIC + modules.f90 + precision.f90 # 新增 +) + +set_target_properties(base PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Base module configured") + +# src/core/CMakeLists.txt +message(STATUS "Configuring core module...") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/src/base/modules.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/src/base/modules.f90 new file mode 100644 index 000000000..43aaee241 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/src/base/modules.f90 @@ -0,0 +1,36 @@ +! src/base/modules.f90 +module base_modules + use, intrinsic :: iso_fortran_env, only: real64, int32 + implicit none + + public :: wp, ip, max_name_len, string_len, cfd_config_base, component_info + + integer, parameter :: wp = real64 + integer, parameter :: ip = int32 + integer, parameter :: string_len = 100 + integer, parameter :: max_name_len = 32 + + ! 基础配置类型 + type :: cfd_config_base + character(len=max_name_len) :: ic_type = "step" + character(len=max_name_len) :: recon_scheme = "eno" + character(len=max_name_len) :: flux_type = "rusanov" + integer(ip) :: rk_order = 1 + real(wp) :: wave_speed = 1.0_wp + real(wp) :: final_time = 0.625_wp + real(wp) :: dt = 0.025_wp + character(len=max_name_len) :: boundary_type = "periodic" + integer(ip) :: spatial_order = 2 + character(len=max_name_len) :: equation_type = "linear_advection" + character(len=max_name_len) :: problem_type = "linear_advection" + logical :: verbose = .true. + end type cfd_config_base + + ! 组件信息类型 + type :: component_info + character(len=max_name_len) :: category = "" + character(len=max_name_len) :: name = "" + integer(ip) :: order = 0 + end type component_info + +end module base_modules \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/src/base/precision.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/src/base/precision.f90 new file mode 100644 index 000000000..4ac5fd7ef --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/src/base/precision.f90 @@ -0,0 +1,9 @@ +! src/base/precision.f90(简单版本) +module precision_module + use base_modules, only: wp, ip + implicit none + + ! 重新导出,确保兼容 + public :: wp, ip + +end module precision_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02h/src/core/CMakeLists.txt new file mode 100644 index 000000000..d8b8df064 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/src/core/CMakeLists.txt @@ -0,0 +1,14 @@ +# src/core/CMakeLists.txt +message(STATUS "Configuring core module...") + +add_library(core STATIC + registry.f90 +) + +target_link_libraries(core PRIVATE base) + +set_target_properties(core PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Core module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/src/core/factory_base.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/src/core/factory_base.f90 new file mode 100644 index 000000000..302418a15 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/src/core/factory_base.f90 @@ -0,0 +1,57 @@ +! src/core/factory_base.f90 +module factory_base_module + use base_modules, only: wp, ip + use registry_module, only: create_component, has_component + + implicit none + private + public :: wp, ip, factory_base, factory_create + + ! 工厂基类 + type :: factory_base + character(len=max_name_length) :: category = "" + contains + procedure :: create => factory_base_create + procedure :: get_available => factory_base_get_available + end type factory_base + + ! 便捷函数类型 + abstract interface + function factory_function_interface(category, name) result(instance) + import :: wp + character(len=*), intent(in) :: category, name + class(*), allocatable :: instance + end function factory_function_interface + end interface + +contains + + ! 创建工厂实例 + function factory_create(category) result(factory) + character(len=*), intent(in) :: category + type(factory_base) :: factory + factory%category = trim(category) + end function factory_create + + ! 工厂创建方法 + function factory_base_create(this, name) result(instance) + class(factory_base), intent(in) :: this + character(len=*), intent(in) :: name + class(*), allocatable :: instance + + instance = create_component(this%category, name) + end function factory_base_create + + ! 获取可用组件列表(简化版) + subroutine factory_base_get_available(this, names, count) + class(factory_base), intent(in) :: this + character(len=*), allocatable, intent(out) :: names(:) + integer(ip), intent(out) :: count + + ! 这里需要实现从注册表获取列表的逻辑 + ! 暂时返回空列表 + count = 0 + allocate(character(len=max_name_length) :: names(0)) + end subroutine factory_base_get_available + +end module factory_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..a960167c3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/src/core/factory_interfaces.f90 @@ -0,0 +1,17 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, factory_procedure + + ! Factory procedure interface + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/src/core/registry.f90 new file mode 100644 index 000000000..acc63edb4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/src/core/registry.f90 @@ -0,0 +1,203 @@ +! src/core/registry.f90 +module registry_module + use base_modules, only: wp, ip, max_name_len, component_info + + implicit none + private + + ! 明确公开所有需要的接口 + public :: wp, ip ! 类型参数 + public :: component_info ! 类型 + public :: registry_init, registry_cleanup ! 初始化/清理 + public :: register_component_simple ! 注册组件 + public :: has_component_simple ! 检查组件 + public :: list_components ! 列出组件 + public :: registry_is_initialized ! 检查初始化状态 ← 新增 + public :: registry_get_size ! 获取大小 ← 新增 + + ! 全局注册表 + type :: component_registry + type(component_info), allocatable :: components(:) + integer(ip) :: count = 0 + integer(ip) :: capacity = 100 + logical :: initialized = .false. + logical :: verbose = .true. + end type component_registry + + type(component_registry) :: registry + +contains + + ! ==================== 公共API ==================== + + subroutine registry_init(verbose) + logical, optional, intent(in) :: verbose + + if (registry%initialized) then + if (registry%verbose) then + print *, "[REGISTRY] Already initialized" + end if + return + end if + + if (present(verbose)) then + registry%verbose = verbose + end if + + allocate(registry%components(registry%capacity)) + registry%initialized = .true. + + if (registry%verbose) then + print *, "[REGISTRY] Initialized with capacity:", registry%capacity + end if + end subroutine registry_init + + subroutine registry_cleanup() + if (allocated(registry%components)) then + deallocate(registry%components) + end if + registry%initialized = .false. + registry%count = 0 + + if (registry%verbose) then + print *, "[REGISTRY] Cleaned up" + end if + end subroutine registry_cleanup + + subroutine register_component_simple(category, name, order) + character(len=*), intent(in) :: category, name + integer(ip), optional, intent(in) :: order + + integer(ip) :: i + type(component_info) :: info + + if (.not. registry%initialized) then + call registry_init() + end if + + ! 检查是否已存在 + do i = 1, registry%count + if (trim(registry%components(i)%category) == trim(category) .and. & + trim(registry%components(i)%name) == trim(name)) then + if (registry%verbose) then + print *, "[WARN] Overwriting component: ", trim(category), ".", trim(name) + end if + + ! 更新 + if (present(order)) then + registry%components(i)%order = order + else + registry%components(i)%order = 0 + end if + return + end if + end do + + ! 扩展数组 + if (registry%count >= registry%capacity) then + call expand_registry() + end if + + ! 添加新组件 + registry%count = registry%count + 1 + + info%category = trim(category) + info%name = trim(name) + info%order = 0 + if (present(order)) then + info%order = order + end if + + registry%components(registry%count) = info + + if (registry%verbose) then + print *, "[OK] Registered simple: ", trim(category), ".", trim(name) + end if + end subroutine register_component_simple + + logical function has_component_simple(category, name) + character(len=*), intent(in) :: category, name + + integer(ip) :: i + + has_component_simple = .false. + + if (.not. registry%initialized) return + + do i = 1, registry%count + if (trim(registry%components(i)%category) == trim(category) .and. & + trim(registry%components(i)%name) == trim(name)) then + has_component_simple = .true. + return + end if + end do + end function has_component_simple + + subroutine list_components(category) + character(len=*), optional, intent(in) :: category + + integer(ip) :: i, count + + if (.not. registry%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + if (registry%count == 0) then + print *, "[INFO] No components registered" + return + end if + + count = 0 + print *, "=== Registry Contents ===" + do i = 1, registry%count + if (.not. present(category) .or. & + trim(registry%components(i)%category) == trim(category)) then + call print_component_info(registry%components(i)) + count = count + 1 + end if + end do + + print *, "Total:", count, "components" + print *, "==========================" + end subroutine list_components + + ! ==================== 新增函数 ==================== + + logical function registry_is_initialized() + ! 检查注册表是否已初始化 + registry_is_initialized = registry%initialized + end function registry_is_initialized + + integer(ip) function registry_get_size() + ! 获取注册表中的组件数量 + registry_get_size = registry%count + end function registry_get_size + + ! ==================== 内部辅助函数 ==================== + + subroutine expand_registry() + type(component_info), allocatable :: temp(:) + + registry%capacity = registry%capacity * 2 + allocate(temp(registry%capacity)) + temp(1:registry%count) = registry%components(1:registry%count) + call move_alloc(temp, registry%components) + + if (registry%verbose) then + print *, "[INFO] Registry expanded to capacity:", registry%capacity + end if + end subroutine expand_registry + + subroutine print_component_info(info) + type(component_info), intent(in) :: info + + if (info%order > 0) then + print *, " [", trim(info%category), ".", trim(info%name), & + " (order:", info%order, ")]" + else + print *, " [", trim(info%category), ".", trim(info%name), "]" + end if + end subroutine print_component_info + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02h/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..70cbbd2f3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/src/infrastructure/CMakeLists.txt @@ -0,0 +1,17 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "Configuring infrastructure module...") + +add_library(infrastructure STATIC + config.f90 + mesh.f90 + domain.f90 # 新增 + solution.f90 # 新增 +) + +target_link_libraries(infrastructure PRIVATE base) + +set_target_properties(infrastructure PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Infrastructure module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/src/infrastructure/config.f90 new file mode 100644 index 000000000..7586a1a50 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/src/infrastructure/config.f90 @@ -0,0 +1,144 @@ +! src/infrastructure/config.f90 (修复版) +module config_module + use base_modules, only: wp, ip, max_name_len, cfd_config_base + + implicit none + public :: wp, ip, cfd_config, config_print, config_with_reconstruction + + ! 扩展配置类型 - 添加物理相关字段 + type, extends(cfd_config_base) :: cfd_config + ! 物理参数 + real(wp) :: left_boundary_value = 1.0_wp + real(wp) :: right_boundary_value = 2.0_wp + real(wp) :: domain_length = 2.0_wp + + ! 新增:物理模块相关配置 + real(wp) :: pulse_center = 0.5_wp ! 高斯脉冲中心 + real(wp) :: pulse_width = 0.1_wp ! 高斯脉冲宽度 + logical :: enable_physics = .true. ! 是否启用物理模块 + contains + ! 新增:物理相关配置方法 + procedure :: set_physics_parameters + procedure :: get_physics_info + end type cfd_config + +contains + + subroutine config_print(cfg) + type(cfd_config), intent(in) :: cfg + + print *, "=== CFD Configuration ===" + print *, "Initial condition: ", trim(cfg%ic_type) + print *, "Reconstruction: ", trim(cfg%recon_scheme), " (order:", cfg%spatial_order, ")" + print *, "Flux type: ", trim(cfg%flux_type) + print *, "Time integration: RK", cfg%rk_order + print *, "Wave speed: ", cfg%wave_speed + print *, "Final time: ", cfg%final_time + print *, "Time step: ", cfg%dt + print *, "Boundary: ", trim(cfg%boundary_type) + + ! 新增:物理配置信息 + print *, "--- Physics Configuration ---" + print *, "Equation type: ", trim(cfg%equation_type) + print *, "Problem type: ", trim(cfg%problem_type) + print *, "Domain length: ", cfg%domain_length + print *, "Physics enabled: ", cfg%enable_physics + + if (cfg%ic_type == "gaussian") then + print *, "Pulse center: ", cfg%pulse_center + print *, "Pulse width: ", cfg%pulse_width + end if + + print *, "===============================" + end subroutine config_print + + subroutine config_with_reconstruction(cfg, scheme, order) + type(cfd_config), intent(inout) :: cfg + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + integer :: i + + ! 转换为小写 + cfg%recon_scheme = scheme + do i = 1, len_trim(cfg%recon_scheme) + if (cfg%recon_scheme(i:i) >= 'A' .and. cfg%recon_scheme(i:i) <= 'Z') then + cfg%recon_scheme(i:i) = char(ichar(cfg%recon_scheme(i:i)) + 32) + end if + end do + + ! 设置阶数 + if (present(order)) then + cfg%spatial_order = order + else + if (index(cfg%recon_scheme, 'weno') > 0) then + cfg%spatial_order = 5 + else if (trim(cfg%recon_scheme) == 'eno') then + cfg%spatial_order = 3 + end if + end if + + if (cfg%verbose) then + print *, "[CONFIG] Reconstruction: ", trim(cfg%recon_scheme), & + " Order: ", cfg%spatial_order + end if + end subroutine config_with_reconstruction + + ! ========== 新增:物理参数设置方法 ========== + + subroutine set_physics_parameters(this, equation_type, problem_type, & + domain_length, enable_physics) + class(cfd_config), intent(inout) :: this + character(len=*), intent(in), optional :: equation_type, problem_type + real(wp), intent(in), optional :: domain_length + logical, intent(in), optional :: enable_physics + + if (present(equation_type)) then + this%equation_type = trim(equation_type) + if (this%verbose) then + print *, "[CONFIG] Set equation type: ", trim(this%equation_type) + end if + end if + + if (present(problem_type)) then + this%problem_type = trim(problem_type) + if (this%verbose) then + print *, "[CONFIG] Set problem type: ", trim(this%problem_type) + end if + end if + + if (present(domain_length)) then + this%domain_length = domain_length + if (this%verbose) then + print *, "[CONFIG] Set domain length: ", this%domain_length + end if + end if + + if (present(enable_physics)) then + this%enable_physics = enable_physics + if (this%verbose) then + print *, "[CONFIG] Physics module enabled: ", this%enable_physics + end if + end if + end subroutine set_physics_parameters + + subroutine get_physics_info(this) + class(cfd_config), intent(in) :: this + + print *, "=== Physics Configuration Info ===" + print *, "Equation type: ", trim(this%equation_type) + print *, "Problem type: ", trim(this%problem_type) + print *, "Domain length: ", this%domain_length + print *, "Wave speed: ", this%wave_speed + print *, "Physics enabled: ", this%enable_physics + + if (this%ic_type == "gaussian") then + print *, "Pulse parameters:" + print *, " Center: ", this%pulse_center + print *, " Width: ", this%pulse_width + end if + + print *, "==================================" + end subroutine get_physics_info + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/src/infrastructure/domain.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/src/infrastructure/domain.f90 new file mode 100644 index 000000000..c3662f039 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/src/infrastructure/domain.f90 @@ -0,0 +1,102 @@ +! src/infrastructure/domain.f90 +module domain_module + use base_modules, only: wp, ip, max_name_len + use config_module, only: cfd_config + use mesh_module, only: mesh_type + + implicit none + private + public :: wp, ip, domain_type, domain_create, is_physical_cell + + type :: domain_type + type(cfd_config), pointer :: config => null() + type(mesh_type), pointer :: mesh => null() + integer(ip) :: nghosts = 0 + integer(ip) :: ist = 1 ! 物理区域起始索引(1-based) + integer(ip) :: ied = 1 ! 物理区域结束索引(exclusive) + integer(ip) :: ntcells = 0 ! 总单元数(含ghost) + contains + procedure :: print_info => domain_print_info + procedure :: get_physical_indices => domain_get_physical_indices + end type domain_type + +contains + + function domain_create(config, mesh) result(domain) + type(cfd_config), target, intent(in) :: config + type(mesh_type), target, intent(in) :: mesh + type(domain_type) :: domain + + domain%config => config + domain%mesh => mesh + + ! 计算ghost层数(参考Julia的_calc_nghosts) + domain%nghosts = calc_nghosts(config) + domain%ist = domain%nghosts + 1 + domain%ied = domain%ist + mesh%ncells + domain%ntcells = mesh%ncells + 2 * domain%nghosts + + if (config%verbose) then + print *, "[DOMAIN] Created:" + print *, " Ghost layers: ", domain%nghosts + print *, " Physical cells: ", domain%ist, " to ", domain%ied - 1 + print *, " Total cells: ", domain%ntcells + end if + end function domain_create + + function calc_nghosts(config) result(nghosts) + type(cfd_config), intent(in) :: config + integer(ip) :: nghosts + + character(len=max_name_len) :: scheme + + scheme = config%recon_scheme + + if (scheme == "eno") then + nghosts = config%spatial_order + else if (index(scheme, "weno") > 0) then + nghosts = config%spatial_order / 2 + 1 + else + print *, "[WARNING] Unknown scheme, using default nghosts=2" + nghosts = 2 + end if + + if (nghosts <= 0) then + print *, "[ERROR] Invalid nghosts: ", nghosts + nghosts = 2 + end if + end function calc_nghosts + + logical function is_physical_cell(this, idx) + class(domain_type), intent(in) :: this + integer(ip), intent(in) :: idx + is_physical_cell = (idx >= this%ist .and. idx < this%ied) + end function is_physical_cell + + function domain_get_physical_indices(this) result(indices) + class(domain_type), intent(in) :: this + integer(ip), allocatable :: indices(:) + integer(ip) :: i, count + + count = this%ied - this%ist + allocate(indices(count)) + + do i = 1, count + indices(i) = this%ist + i - 1 + end do + end function domain_get_physical_indices + + subroutine domain_print_info(this) + class(domain_type), intent(in) :: this + + print *, "=== Domain Information ===" + print *, "Configuration: ", trim(this%config%recon_scheme), & + " order ", this%config%spatial_order + print *, "Ghost layers: ", this%nghosts + print *, "Physical cells: ", this%ist, " to ", this%ied - 1 + print *, "Total cells: ", this%ntcells + print *, "Mesh cells: ", this%mesh%ncells + print *, "==========================" + end subroutine domain_print_info + +end module domain_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..f810f3a1b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/src/infrastructure/mesh.f90 @@ -0,0 +1,73 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use base_modules, only: wp, ip + + implicit none + public :: wp, ip, mesh_type, mesh_init, mesh_print_info + + ! 网格类型 + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer(ip) :: ncells = 40 + integer(ip) :: nnodes + integer(ip) :: nx + real(wp), allocatable :: x(:) ! 节点坐标 + real(wp), allocatable :: xcc(:) ! 单元中心坐标 + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer(ip), optional, intent(in) :: ncells + + integer(ip) :: i + + ! 设置参数 + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! 计算 + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! 分配内存 + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! 生成节点坐标 + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! 生成单元中心坐标 + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== Mesh Information ===" + print *, "Domain: [", this%xmin, ", ", this%xmax, "]" + print *, "Cells: ", this%ncells + print *, "Nodes: ", this%nnodes + print *, "dx: ", this%dx + print *, "L: ", this%L + print *, "========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/src/infrastructure/solution.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/src/infrastructure/solution.f90 new file mode 100644 index 000000000..ce88fd8a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/src/infrastructure/solution.f90 @@ -0,0 +1,131 @@ +! src/infrastructure/solution.f90 +module solution_module + use base_modules, only: wp, ip + use domain_module, only: domain_type + + implicit none + private + public :: wp, ip, solution_type, solution_create, solution_reset + + type :: solution_type + type(domain_type), pointer :: domain => null() + real(wp), allocatable :: u(:) ! 当前解(含ghost) + real(wp), allocatable :: un(:) ! 旧解 + real(wp), allocatable :: q_face_left(:) ! 左界面值 + real(wp), allocatable :: q_face_right(:)! 右界面值 + real(wp), allocatable :: flux(:) ! 通量 + real(wp), allocatable :: res(:) ! 残差 + contains + procedure :: initialize => solution_initialize + procedure :: update_old_field => solution_update_old_field + procedure :: print_info => solution_print_info + procedure :: reset => solution_reset_instance + end type solution_type + +contains + + function solution_create(domain) result(solution) + type(domain_type), target, intent(in) :: domain + type(solution_type) :: solution + + integer(ip) :: ncells, nnodes, ntcells + + solution%domain => domain + + ncells = domain%mesh%ncells + nnodes = domain%mesh%nnodes + ntcells = domain%ntcells + + ! 分配数组(与Julia solution.jl一致) + allocate(solution%u(ntcells), source=0.0_wp) + allocate(solution%un(ntcells), source=0.0_wp) + allocate(solution%q_face_left(nnodes), source=0.0_wp) + allocate(solution%q_face_right(nnodes), source=0.0_wp) + allocate(solution%flux(nnodes), source=0.0_wp) + allocate(solution%res(ncells), source=0.0_wp) + + if (domain%config%verbose) then + print *, "[SOLUTION] Created:" + print *, " u size: ", size(solution%u), " (with ghosts)" + print *, " flux size: ", size(solution%flux) + print *, " res size: ", size(solution%res) + end if + end function solution_create + + subroutine solution_initialize(this, initial_values) + class(solution_type), intent(inout) :: this + real(wp), intent(in), optional :: initial_values(:) + + integer(ip) :: i, idx + type(domain_type), pointer :: domain + + domain => this%domain + + if (present(initial_values)) then + ! 应用初始值到物理区域 + do i = domain%ist, domain%ied - 1 + idx = i - domain%ist + 1 + if (idx <= size(initial_values)) then + this%u(i) = initial_values(idx) + end if + end do + else + ! 默认为0 + this%u = 0.0_wp + end if + + ! 同步旧场(与Julia的update_old_field一致) + call this%update_old_field() + + if (domain%config%verbose) then + print *, "[SOLUTION] Initialized" + print *, " u range: ", minval(this%u), " to ", maxval(this%u) + end if + end subroutine solution_initialize + + subroutine solution_update_old_field(this) + class(solution_type), intent(inout) :: this + this%un = this%u ! 与Julia的 un .= u 一致 + end subroutine solution_update_old_field + + subroutine solution_reset_instance(this) + class(solution_type), intent(inout) :: this + call solution_reset(this) + end subroutine solution_reset_instance + + subroutine solution_reset(solution) + type(solution_type), intent(inout) :: solution + + if (allocated(solution%u)) solution%u = 0.0_wp + if (allocated(solution%un)) solution%un = 0.0_wp + if (allocated(solution%q_face_left)) solution%q_face_left = 0.0_wp + if (allocated(solution%q_face_right)) solution%q_face_right = 0.0_wp + if (allocated(solution%flux)) solution%flux = 0.0_wp + if (allocated(solution%res)) solution%res = 0.0_wp + + if (associated(solution%domain) .and. solution%domain%config%verbose) then + print *, "[SOLUTION] Reset" + end if + end subroutine solution_reset + + subroutine solution_print_info(this) + class(solution_type), intent(in) :: this + + print *, "=== Solution Information ===" + print *, "Arrays:" + print *, " u: ", size(this%u), " elements" + print *, " un: ", size(this%un), " elements" + print *, " q_face_left: ", size(this%q_face_left), " elements" + print *, " q_face_right: ", size(this%q_face_right), " elements" + print *, " flux: ", size(this%flux), " elements" + print *, " res: ", size(this%res), " elements" + + if (allocated(this%u)) then + print *, "Values:" + print *, " u min/max: ", minval(this%u), maxval(this%u) + print *, " un min/max: ", minval(this%un), maxval(this%un) + end if + print *, "============================" + end subroutine solution_print_info + +end module solution_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/src/manager/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02h/src/manager/CMakeLists.txt new file mode 100644 index 000000000..00c8bf49b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/src/manager/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/manager/CMakeLists.txt +message(STATUS "配置管理器模块...") + +# 创建管理器库 +add_library(manager STATIC + component_manager.f90 + component_factory.f90 +) + +# 明确依赖关系:管理器依赖所有其他模块 +target_link_libraries(manager + PRIVATE + core + infrastructure + reconstructor + flux +) + +# 设置模块输出目录 +set_target_properties(manager PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "管理器模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/src/manager/component_factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/src/manager/component_factory.f90 new file mode 100644 index 000000000..114fedea5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/src/manager/component_factory.f90 @@ -0,0 +1,127 @@ +! src/manager/component_factory.f90 +module component_factory_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use config_module, only: cfd_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + use eno_reconstructor_module, only: eno_reconstructor, create_eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor, create_weno3_reconstructor + use rusanov_flux_module, only: rusanov_flux, create_rusanov_flux + + implicit none + private + public :: wp, create_reconstructor, create_flux_calculator + + ! 错误代码 + integer, parameter :: CM_SUCCESS = 0 + integer, parameter :: CM_ERROR_UNKNOWN_SCHEME = 1 + integer, parameter :: CM_ERROR_UNKNOWN_FLUX = 2 + integer, parameter :: CM_ERROR_INVALID_ORDER = 3 + +contains + + ! ==================== 重构器创建 ==================== + + function create_reconstructor(config, status) result(recon) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(reconstructor_base), allocatable :: recon + + character(len=20) :: scheme + integer :: order, error_code + + scheme = trim(adjustl(config%recon_scheme)) + order = config%spatial_order + + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating reconstructor: ", scheme, " order=", order + end if + + select case(scheme) + case('eno') + allocate(eno_reconstructor :: recon) + select type(recon) + type is(eno_reconstructor) + recon = create_eno_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case('weno3') + allocate(weno3_reconstructor :: recon) + select type(recon) + type is(weno3_reconstructor) + recon = create_weno3_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case default + error_code = CM_ERROR_UNKNOWN_SCHEME + if (config%verbose) then + print *, "[ERROR] Unknown reconstructor scheme: ", scheme + print *, " Available: eno, weno3" + end if + end select + + ! 检查阶数有效性 + if (error_code == CM_SUCCESS) then + if (order < 1) then + error_code = CM_ERROR_INVALID_ORDER + if (config%verbose) then + print *, "[ERROR] Invalid spatial order: ", order + end if + end if + end if + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Reconstructor creation failed" + end if + end function create_reconstructor + + ! ==================== 通量计算器创建 ==================== + + function create_flux_calculator(config, status) result(flux) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(flux_calculator_base), allocatable :: flux + + character(len=20) :: flux_type + integer :: error_code + + flux_type = trim(adjustl(config%flux_type)) + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating flux calculator: ", flux_type + end if + + select case(flux_type) + case('rusanov') + allocate(rusanov_flux :: flux) + select type(flux) + type is(rusanov_flux) + flux = create_rusanov_flux() + flux%wave_speed_default = config%wave_speed + end select + + case default + error_code = CM_ERROR_UNKNOWN_FLUX + if (config%verbose) then + print *, "[ERROR] Unknown flux type: ", flux_type + print *, " Available: rusanov" + end if + end select + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Flux calculator creation failed" + end if + end function create_flux_calculator + +end module component_factory_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/src/manager/component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/src/manager/component_manager.f90 new file mode 100644 index 000000000..9e095c257 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/src/manager/component_manager.f90 @@ -0,0 +1,75 @@ +! src/manager/component_manager.f90 +module component_manager_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use config_module, only: cfd_config + use component_factory_module, only: create_reconstructor, create_flux_calculator + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + + implicit none + private + public :: wp, component_manager_info, validate_config + public :: create_reconstructor, create_flux_calculator + +contains + + ! ==================== 配置验证 ==================== + + function validate_config(config) result(is_valid) + type(cfd_config), intent(in) :: config + logical :: is_valid + + integer :: status + class(reconstructor_base), allocatable :: test_recon + class(flux_calculator_base), allocatable :: test_flux + + is_valid = .false. + + ! 测试创建重构器 + test_recon = create_reconstructor(config, status) + if (status /= 0) then + if (config%verbose) then + print *, "[CONFIG VALIDATION] Invalid reconstructor configuration" + end if + return + end if + + ! 测试创建通量计算器 + test_flux = create_flux_calculator(config, status) + if (status /= 0) then + if (config%verbose) then + print *, "[CONFIG VALIDATION] Invalid flux configuration" + end if + return + end if + + ! 清理测试组件 + if (allocated(test_recon)) deallocate(test_recon) + if (allocated(test_flux)) deallocate(test_flux) + + is_valid = .true. + + if (config%verbose) then + print *, "[CONFIG VALIDATION] Configuration is valid" + end if + end function validate_config + + ! ==================== 信息显示 ==================== + + subroutine component_manager_info() + print *, "=== Component Manager ===" + print *, "Available reconstructors:" + print *, " - eno (orders: 1-7)" + print *, " - weno3 (order: 3)" + print *, "" + print *, "Available flux calculators:" + print *, " - rusanov" + print *, "" + print *, "Features:" + print *, " - Configuration validation" + print *, " - Component creation from config" + print *, " - Error handling with status codes" + print *, "=========================" + end subroutine component_manager_info + +end module component_manager_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..66874a7ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/CMakeLists.txt @@ -0,0 +1,7 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..3fde7746b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,28 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +# Start with just the base module +add_library(flux STATIC + base.f90 + rusanov.f90 +) + +#target_link_libraries(flux PRIVATE core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 明确设置不使用 /Qm64 选项 +if(CMAKE_Fortran_COMPILER_ID MATCHES "IntelLLVM|Intel") + set_target_properties(flux PROPERTIES + Fortran_FLAGS "${CMAKE_Fortran_FLAGS} /Qm64" # 明确设置,避免继承问题 + ) +endif() + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/flux/base.f90 new file mode 100644 index 000000000..7080a7ae4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/flux/base.f90 @@ -0,0 +1,30 @@ +!src/numerics/flux/base.f90 +module flux_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, flux_calculator_base + + ! Base flux calculator type + type :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure :: info => flux_info + procedure :: print_basic_info => flux_print_basic ! 添加辅助方法 + end type flux_calculator_base + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "Flux calculator information:" + print *, " Name: ", trim(this%name) + end subroutine flux_info + + subroutine flux_print_basic(this) + class(flux_calculator_base), intent(in) :: this + print *, " Name: ", trim(this%name) + end subroutine flux_print_basic + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/flux/rusanov.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/flux/rusanov.f90 new file mode 100644 index 000000000..daa9e3bb5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/flux/rusanov.f90 @@ -0,0 +1,41 @@ +! src/numerics/flux/rusanov.f90 +module rusanov_flux_module + use, intrinsic :: iso_fortran_env, only: real64 + use flux_base_module, only: flux_calculator_base + implicit none + + private + public :: real64, rusanov_flux, create_rusanov_flux + + type, extends(flux_calculator_base) :: rusanov_flux + real(real64) :: wave_speed_default = 1.0_real64 + contains + procedure :: info => rusanov_info + end type rusanov_flux + + ! 构造函数接口 + interface rusanov_flux + module procedure create_rusanov_flux + end interface + +contains + + ! 构造函数 + type(rusanov_flux) function create_rusanov_flux() result(this) + this%name = "Rusanov" + this%wave_speed_default = 1.0_real64 + end function create_rusanov_flux + + subroutine rusanov_info(this) + class(rusanov_flux), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Flux calculator information:" + call this%print_basic_info() + + ! 添加Rusanov特有信息 + print *, " Type: Rusanov flux" + print *, " Default wave speed: ", this%wave_speed_default + end subroutine rusanov_info + +end module rusanov_flux_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..5e4b938d5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,22 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "Configuring reconstructor module...") + +# Start with just the base module +add_library(reconstructor STATIC + base.f90 + eno.f90 + weno3.f90 +) + +target_link_libraries(reconstructor PRIVATE core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Reconstructor module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..53798d021 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/reconstructor/base.f90 @@ -0,0 +1,33 @@ +!src/numerics/reconstructor/base.f90 +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, reconstructor_base + + ! Base reconstructor type + type :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + contains + procedure :: info => reconstructor_info + procedure :: print_basic_info => reconstructor_print_basic ! 添加一个辅助方法 + end type reconstructor_base + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + print *, "Reconstructor information:" + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_info + + subroutine reconstructor_print_basic(this) + class(reconstructor_base), intent(in) :: this + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_print_basic + +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/reconstructor/eno.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/reconstructor/eno.f90 new file mode 100644 index 000000000..f973e8b32 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/reconstructor/eno.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/eno.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, eno_reconstructor, create_eno_reconstructor ! ← 添加这个 + + type, extends(reconstructor_base) :: eno_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => eno_info + end type eno_reconstructor + + ! 构造函数接口 + interface eno_reconstructor + module procedure create_eno_reconstructor + end interface + +contains + + ! 构造函数 + type(eno_reconstructor) function create_eno_reconstructor() result(this) + this%name = "ENO" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_eno_reconstructor + + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加ENO特有信息 + print *, " Type: ENO reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/reconstructor/weno3.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/reconstructor/weno3.f90 new file mode 100644 index 000000000..d5b7a7477 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/src/numerics/reconstructor/weno3.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/weno3.f90 +module weno3_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno3_reconstructor, create_weno3_reconstructor + + type, extends(reconstructor_base) :: weno3_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno3_info + end type weno3_reconstructor + + ! 构造函数接口 + interface weno3_reconstructor + module procedure create_weno3_reconstructor + end interface + +contains + + ! 构造函数 + type(weno3_reconstructor) function create_weno3_reconstructor() result(this) + this%name = "WENO3" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_weno3_reconstructor + + subroutine weno3_info(this) + class(weno3_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加WENO3特有信息 + print *, " Type: WENO-3 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno3_info + +end module weno3_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/src/physics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02h/src/physics/CMakeLists.txt new file mode 100644 index 000000000..cc4e233ab --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/src/physics/CMakeLists.txt @@ -0,0 +1,19 @@ +# src/physics/CMakeLists.txt +message(STATUS "配置物理模块...") + +# 创建物理模块库 +add_library(physics STATIC + physics_interface.f90 + equations/linear_convection.f90 + problems/linear_convection_problem.f90 +) + +# 链接依赖 +target_link_libraries(physics PRIVATE base) + +# 设置模块输出目录 +set_target_properties(physics PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "物理模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/src/physics/equations/linear_convection.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/src/physics/equations/linear_convection.f90 new file mode 100644 index 000000000..595a21107 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/src/physics/equations/linear_convection.f90 @@ -0,0 +1,83 @@ +! src/physics/equations/linear_convection.f90 +module linear_convection_equation + use precision_module, only: wp, ip + use physics_interface, only: physics_equation + implicit none + private + + public :: linear_convection_eq, create_linear_convection_eq + public :: linear_convection_flux, linear_convection_speed + + ! 具体方程类型 + type, extends(physics_equation) :: linear_convection_eq + real(wp) :: wave_speed = 1.0_wp + contains + procedure :: flux => lc_flux + procedure :: speed => lc_speed + end type linear_convection_eq + + ! 独立函数(供外部调用) + interface linear_convection_flux + module procedure :: lc_flux_func + end interface + + interface linear_convection_speed + module procedure :: lc_speed_func + end interface + +contains + + ! 构造函数 + function create_linear_convection_eq(wave_speed) result(eq) + real(wp), intent(in), optional :: wave_speed + type(linear_convection_eq) :: eq + + eq%name = "Linear Convection" + if (present(wave_speed)) then + eq%wave_speed = wave_speed + else + eq%wave_speed = 1.0_wp + end if + end function create_linear_convection_eq + + ! 方法实现 + pure function lc_flux(this, u) result(f) + class(linear_convection_eq), intent(in) :: this + real(wp), intent(in) :: u + real(wp) :: f + f = this%wave_speed * u + end function lc_flux + + pure function lc_speed(this) result(a) + class(linear_convection_eq), intent(in) :: this + real(wp) :: a + a = this%wave_speed + end function lc_speed + + ! 独立函数 + pure function lc_flux_func(u, wave_speed) result(f) + real(wp), intent(in) :: u + real(wp), intent(in), optional :: wave_speed + real(wp) :: f + real(wp) :: a + + if (present(wave_speed)) then + a = wave_speed + else + a = 1.0_wp + end if + f = a * u + end function lc_flux_func + + pure function lc_speed_func(wave_speed) result(a) + real(wp), intent(in), optional :: wave_speed + real(wp) :: a + + if (present(wave_speed)) then + a = wave_speed + else + a = 1.0_wp + end if + end function lc_speed_func + +end module linear_convection_equation \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/src/physics/physics_interface.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/src/physics/physics_interface.f90 new file mode 100644 index 000000000..55662e5c2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/src/physics/physics_interface.f90 @@ -0,0 +1,64 @@ +! src/physics/physics_interface.f90 +module physics_interface + use precision_module, only: wp, ip + implicit none + private + + ! 定义抽象基类型 + type, abstract :: physics_equation + character(len=:), allocatable :: name + contains + procedure(eq_flux_abs), deferred :: flux + procedure(eq_speed_abs), deferred :: speed + end type physics_equation + + type, abstract :: physics_problem + character(len=:), allocatable :: name + contains + procedure(prob_ic_abs), deferred :: initial_condition + procedure(prob_bc_abs), deferred :: boundary_condition + procedure(prob_exact_abs), deferred :: exact_solution + end type physics_problem + + ! 抽象接口定义 + abstract interface + pure function eq_flux_abs(this, u) result(f) + import :: physics_equation, wp + class(physics_equation), intent(in) :: this + real(wp), intent(in) :: u + real(wp) :: f + end function eq_flux_abs + + pure function eq_speed_abs(this) result(a) + import :: physics_equation, wp + class(physics_equation), intent(in) :: this + real(wp) :: a + end function eq_speed_abs + + subroutine prob_ic_abs(this, x, u) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(in) :: x(:) + real(wp), intent(out) :: u(:) + end subroutine prob_ic_abs + + subroutine prob_bc_abs(this, u, t) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(inout) :: u(:) + real(wp), intent(in), optional :: t + end subroutine prob_bc_abs + + function prob_exact_abs(this, x, t) result(u) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(in) :: x(:), t + real(wp), dimension(size(x)) :: u + end function prob_exact_abs + end interface + + ! 公开接口 + public :: physics_equation, physics_problem + public :: wp, ip + +end module physics_interface \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/src/physics/problems/linear_convection_problem.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/src/physics/problems/linear_convection_problem.f90 new file mode 100644 index 000000000..312107105 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/src/physics/problems/linear_convection_problem.f90 @@ -0,0 +1,116 @@ +! src/physics/problems/linear_convection_problem.f90 +module linear_convection_problem + use precision_module, only: wp, ip + use physics_interface, only: physics_problem + implicit none + private + + public :: linear_convection_prob, create_linear_convection_prob + + ! 具体问题类型 + type, extends(physics_problem) :: linear_convection_prob + real(wp) :: wave_speed = 1.0_wp + real(wp) :: domain_length = 2.0_wp + character(len=20) :: ic_type = "step" + character(len=20) :: boundary_type = "periodic" + contains + procedure :: initial_condition => lc_initial_condition + procedure :: boundary_condition => lc_boundary_condition + procedure :: exact_solution => lc_exact_solution + end type linear_convection_prob + +contains + + ! 构造函数 + function create_linear_convection_prob(wave_speed, domain_length, & + ic_type, boundary_type) result(prob) + real(wp), intent(in), optional :: wave_speed, domain_length + character(len=*), intent(in), optional :: ic_type, boundary_type + type(linear_convection_prob) :: prob + + prob%name = "Linear Convection Problem" + + if (present(wave_speed)) prob%wave_speed = wave_speed + if (present(domain_length)) prob%domain_length = domain_length + if (present(ic_type)) prob%ic_type = ic_type + if (present(boundary_type)) prob%boundary_type = boundary_type + end function create_linear_convection_prob + + ! 初始条件 + subroutine lc_initial_condition(this, x, u) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(in) :: x(:) + real(wp), intent(out) :: u(:) + + integer :: i + + select case (trim(this%ic_type)) + case ("step") + do i = 1, size(x) + if (x(i) >= 0.5_wp .and. x(i) <= 1.0_wp) then + u(i) = 2.0_wp + else + u(i) = 1.0_wp + end if + end do + + case ("sin", "sine") + do i = 1, size(x) + u(i) = sin(2.0_wp * 3.141592653589793_wp * x(i) / this%domain_length) + end do + + case ("gaussian") + do i = 1, size(x) + u(i) = exp(-((x(i) - 0.5_wp) / 0.1_wp)**2) + end do + + case default + ! 默认阶跃函数 + do i = 1, size(x) + if (x(i) >= 0.5_wp .and. x(i) <= 1.0_wp) then + u(i) = 2.0_wp + else + u(i) = 1.0_wp + end if + end do + end select + end subroutine lc_initial_condition + + ! 边界条件(虚拟实现,实际在boundary模块) + subroutine lc_boundary_condition(this, u, t) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(inout) :: u(:) + real(wp), intent(in), optional :: t + + ! 边界条件将在独立模块实现 + print *, "[PROBLEM] Boundary condition placeholder" + if (present(t)) then + print *, " Time = ", t + end if + end subroutine lc_boundary_condition + + ! 精确解(周期性平移) + function lc_exact_solution(this, x, t) result(u) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(in) :: x(:), t + real(wp), dimension(size(x)) :: u + real(wp), dimension(size(x)) :: x_shifted + integer :: i + + ! 周期性平移 + do i = 1, size(x) + x_shifted(i) = x(i) - this%wave_speed * t + ! 确保在 [0, domain_length) 范围内 + do while (x_shifted(i) < 0.0_wp) + x_shifted(i) = x_shifted(i) + this%domain_length + end do + do while (x_shifted(i) >= this%domain_length) + x_shifted(i) = x_shifted(i) - this%domain_length + end do + end do + + ! 重用初始条件函数 + call this%initial_condition(x_shifted, u) + end function lc_exact_solution + +end module linear_convection_problem \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/02h/tests/CMakeLists.txt new file mode 100644 index 000000000..ebbfc4f9f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/tests/CMakeLists.txt @@ -0,0 +1,66 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +# +#message(STATUS "CMAKE_Fortran_MODULE_DIRECTORY=${CMAKE_Fortran_MODULE_DIRECTORY}") +# +add_executable(test_minimal_simple test_minimal_simple.f90) +target_link_libraries(test_minimal_simple + PRIVATE + core # 必须链接core库 + infrastructure +) + + +add_executable(test_simple_link test_simple_link.f90) +target_link_libraries(test_simple_link + PRIVATE + reconstructor + flux +) + + +add_executable(test_factory_simple test_factory_simple.f90) +target_link_libraries(test_factory_simple + PRIVATE + core + infrastructure + reconstructor + flux +) + + +add_executable(test_component_manager test_component_manager.f90) + +target_link_libraries(test_component_manager + PRIVATE + manager # ← 链接到新的管理器库 + infrastructure +) +add_executable(test_basic_only test_basic_only.f90) +target_link_libraries(test_basic_only + PRIVATE + infrastructure + core +) + +add_executable(test_physics_minimal test_physics_minimal.f90) +target_link_libraries(test_physics_minimal + PRIVATE + physics + base +) + +add_executable(test_domain_solution test_domain_solution.f90) +target_link_libraries(test_domain_solution + PRIVATE + infrastructure + core +) + +add_executable(test_config_physics test_config_physics.f90) +target_link_libraries(test_config_physics + PRIVATE + infrastructure + core +) diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_architecture.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_architecture.f90 new file mode 100644 index 000000000..1c40d4677 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_architecture.f90 @@ -0,0 +1,117 @@ +! tests/test_architecture.f90 +program test_architecture + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module + use config_module + use mesh_module + use component_manager_module + + implicit none + + print *, "=== CFD架构测试 ===" + print *, "" + + ! 测试1: 基本系统 + call test_basic_systems() + + ! 测试2: 组件注册 + call test_registry_integration() + + ! 测试3: 配置验证 + call test_config_validation() + + ! 测试4: 完整流程 + call test_full_workflow() + + print *, "" + print *, "=== 架构测试完成 ===" + +contains + + subroutine test_basic_systems() + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "1. 测试基本系统..." + print *, "-------------------" + + ! 测试配置 + call config_print(config) + print *, "✓ 配置创建成功" + + ! 测试网格 + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "✓ 网格创建成功" + print *, "" + end subroutine test_basic_systems + + subroutine test_registry_integration() + print *, "2. 测试注册系统集成..." + print *, "------------------------" + + call initialize_registry(verbose=.true.) + + ! 注册核心组件 + print *, "注册核心组件:" + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + ! 显示注册内容 + call component_registry%list_simple() + print *, "注册表大小: ", registry_get_size() + + call cleanup_registry() + print *, "✓ 注册系统测试完成" + print *, "" + end subroutine test_registry_integration + + subroutine test_config_validation() + type(cfd_config) :: config + logical :: is_valid + + print *, "3. 测试配置验证..." + print *, "-------------------" + + ! 测试有效配置 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + print *, "测试配置:" + print *, " - 重构器: ", trim(config%recon_scheme), " 阶数: ", config%spatial_order + print *, " - 通量: ", trim(config%flux_type) + print *, " - 波速: ", config%wave_speed + + ! 这里调用验证函数(需要先实现) + ! is_valid = validate_config(config) + print *, "✓ 配置验证接口准备就绪" + print *, "" + end subroutine test_config_validation + + subroutine test_full_workflow() + print *, "4. 测试完整流程..." + print *, "-------------------" + + print *, "步骤1: 初始化系统 ✓" + print *, "步骤2: 创建网格 ✓" + print *, "步骤3: 设置配置 ✓" + print *, "步骤4: 创建组件 (待实现)" + print *, "步骤5: 初始化解 (待实现)" + print *, "步骤6: 时间推进 (待实现)" + print *, "步骤7: 输出结果 (待实现)" + print *, "" + + print *, "✓ 流程框架定义完成" + end subroutine test_full_workflow + +end program test_architecture \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_basic_only.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_basic_only.f90 new file mode 100644 index 000000000..20901ddcc --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_basic_only.f90 @@ -0,0 +1,56 @@ +! tests/test_basic_only.f90 +program test_basic_only + ! 只测试最基本的功能,不依赖复杂模块 + use config_module, only: cfd_config, config_print, wp + use mesh_module, only: mesh_type + use registry_module, only: registry_init, registry_cleanup, & + register_component_simple, list_components + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "=== BASIC TEST - Minimal Functionality ===" + print *, "" + + ! 测试1: 配置 + print *, "1. Testing configuration..." + print *, "----------------------------" + call config_print(config) + print *, "" + + ! 测试2: 网格 + print *, "2. Testing mesh..." + print *, "------------------" + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=5) + print *, "Mesh initialized:" + print *, " Cells: ", mesh%ncells + print *, " Nodes: ", mesh%nnodes + print *, " dx: ", mesh%dx + print *, "" + + ! 测试3: 注册系统 + print *, "3. Testing registry..." + print *, "----------------------" + + call registry_init() + + ! 注册组件(使用简化版本) + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("flux", "rusanov") + + ! 列出组件 + call list_components() + print *, "" + + ! 清理 + call registry_cleanup() + + print *, "=== TEST PASSED ===" + print *, "✓ Configuration works" + print *, "✓ Mesh works" + print *, "✓ Registry works" + +end program test_basic_only \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_cfd_architecture.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_cfd_architecture.f90 new file mode 100644 index 000000000..1c40d4677 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_cfd_architecture.f90 @@ -0,0 +1,117 @@ +! tests/test_architecture.f90 +program test_architecture + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module + use config_module + use mesh_module + use component_manager_module + + implicit none + + print *, "=== CFD架构测试 ===" + print *, "" + + ! 测试1: 基本系统 + call test_basic_systems() + + ! 测试2: 组件注册 + call test_registry_integration() + + ! 测试3: 配置验证 + call test_config_validation() + + ! 测试4: 完整流程 + call test_full_workflow() + + print *, "" + print *, "=== 架构测试完成 ===" + +contains + + subroutine test_basic_systems() + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "1. 测试基本系统..." + print *, "-------------------" + + ! 测试配置 + call config_print(config) + print *, "✓ 配置创建成功" + + ! 测试网格 + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "✓ 网格创建成功" + print *, "" + end subroutine test_basic_systems + + subroutine test_registry_integration() + print *, "2. 测试注册系统集成..." + print *, "------------------------" + + call initialize_registry(verbose=.true.) + + ! 注册核心组件 + print *, "注册核心组件:" + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + ! 显示注册内容 + call component_registry%list_simple() + print *, "注册表大小: ", registry_get_size() + + call cleanup_registry() + print *, "✓ 注册系统测试完成" + print *, "" + end subroutine test_registry_integration + + subroutine test_config_validation() + type(cfd_config) :: config + logical :: is_valid + + print *, "3. 测试配置验证..." + print *, "-------------------" + + ! 测试有效配置 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + print *, "测试配置:" + print *, " - 重构器: ", trim(config%recon_scheme), " 阶数: ", config%spatial_order + print *, " - 通量: ", trim(config%flux_type) + print *, " - 波速: ", config%wave_speed + + ! 这里调用验证函数(需要先实现) + ! is_valid = validate_config(config) + print *, "✓ 配置验证接口准备就绪" + print *, "" + end subroutine test_config_validation + + subroutine test_full_workflow() + print *, "4. 测试完整流程..." + print *, "-------------------" + + print *, "步骤1: 初始化系统 ✓" + print *, "步骤2: 创建网格 ✓" + print *, "步骤3: 设置配置 ✓" + print *, "步骤4: 创建组件 (待实现)" + print *, "步骤5: 初始化解 (待实现)" + print *, "步骤6: 时间推进 (待实现)" + print *, "步骤7: 输出结果 (待实现)" + print *, "" + + print *, "✓ 流程框架定义完成" + end subroutine test_full_workflow + +end program test_architecture \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_component_manager.f90 new file mode 100644 index 000000000..f60c35056 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_component_manager.f90 @@ -0,0 +1,111 @@ +! tests/test_component_manager.f90 +program test_component_manager + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use component_manager_module, only: create_reconstructor, create_flux_calculator + use component_manager_module, only: component_manager_info, validate_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + implicit none + + type(cfd_config) :: config + class(reconstructor_base), allocatable :: recon + class(flux_calculator_base), allocatable :: flux + integer :: status + logical :: is_valid + + print *, "=== Component Manager Test ===" + print *, "" + + ! 显示组件管理器信息 + call component_manager_info() + print *, "" + + ! 测试1: 基本配置 + print *, "1. Testing basic ENO3 + Rusanov configuration..." + print *, "-----------------------------------------------" + + config%verbose = .true. + call config_print(config) + + ! 配置ENO3重构 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + call config_print(config) + print *, "" + + ! 验证配置 + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Configuration is valid" + else + print *, "[ERROR] Configuration is invalid" + end if + print *, "" + + ! 测试2: 创建组件 + print *, "2. Testing component creation..." + print *, "--------------------------------" + + ! 创建重构器(带状态检查) + recon = create_reconstructor(config, status) + if (status == 0) then + print *, "[OK] Reconstructor created successfully" + call recon%info() + else + print *, "[ERROR] Failed to create reconstructor, code:", status + end if + print *, "" + + ! 创建通量计算器 + flux = create_flux_calculator(config, status) + if (status == 0) then + print *, "[OK] Flux calculator created successfully" + call flux%info() + else + print *, "[ERROR] Failed to create flux calculator, code:", status + end if + print *, "" + + ! 测试3: WENO3重构测试 + print *, "3. Testing WENO3 configuration..." + print *, "---------------------------------" + + call config_with_reconstruction(config, "weno3", 3) + + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] WENO3 configuration is valid" + + ! 创建WENO3重构器 + recon = create_reconstructor(config) + call recon%info() + else + print *, "[ERROR] WENO3 configuration is invalid" + end if + print *, "" + + ! 测试4: 错误配置测试 + print *, "4. Testing invalid configuration..." + print *, "-----------------------------------" + + config%recon_scheme = "unknown_scheme" + config%flux_type = "unknown_flux" + + is_valid = validate_config(config) + if (.not. is_valid) then + print *, "[OK] Invalid configuration correctly rejected" + else + print *, "[ERROR] Invalid configuration should have been rejected" + end if + + ! 清理 + if (allocated(recon)) deallocate(recon) + if (allocated(flux)) deallocate(flux) + + print *, "" + print *, "=== Component manager test completed successfully ===" + +end program test_component_manager \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_config_physics.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_config_physics.f90 new file mode 100644 index 000000000..c6fef5c0b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_config_physics.f90 @@ -0,0 +1,141 @@ +! tests/test_config_physics.f90 (修复版) +program test_config_physics + use base_modules, only: wp + use config_module, only: cfd_config, config_print, config_with_reconstruction + + implicit none + + type(cfd_config) :: config + + print *, "=== Configuration Physics Test (Simplified) ===" + print *, "" + + ! 测试1: 默认配置 + print *, "1. Testing default configuration..." + print *, "-----------------------------------" + call config_print(config) + print *, "" + + ! 测试2: 验证基础物理字段 + print *, "2. Testing basic physics fields..." + print *, "----------------------------------" + + print *, "Verifying default physics fields:" + + if (trim(config%equation_type) == "linear_advection") then + print *, " ✓ Default equation type: linear_advection" + else + print *, " ✗ Unexpected equation type: ", trim(config%equation_type) + end if + + if (trim(config%problem_type) == "linear_advection") then + print *, " ✓ Default problem type: linear_advection" + else + print *, " ✗ Unexpected problem type: ", trim(config%problem_type) + end if + + if (abs(config%domain_length - 2.0_wp) < 1e-10_wp) then + print *, " ✓ Default domain length: 2.0" + else + print *, " ✗ Unexpected domain length: ", config%domain_length + end if + + if (config%enable_physics) then + print *, " ✓ Physics enabled by default" + else + print *, " ✗ Physics not enabled by default" + end if + + print *, "" + + ! 测试3: 使用类型绑定的方法(正确的方法名) + print *, "3. Testing type-bound procedures..." + print *, "--------------------------------------" + + call config%set_physics_parameters( & + equation_type="burgers_equation", & + problem_type="sod_shock_tube", & + domain_length=3.0_wp, & + enable_physics=.false.) + + print *, "After set_physics_parameters:" + print *, " Equation type: ", trim(config%equation_type) + print *, " Problem type: ", trim(config%problem_type) + print *, " Domain length: ", config%domain_length + print *, " Physics enabled: ", config%enable_physics + + if (trim(config%equation_type) == "burgers_equation") then + print *, " ✓ Equation type modified successfully via set_physics_parameters" + end if + + if (trim(config%problem_type) == "sod_shock_tube") then + print *, " ✓ Problem type modified successfully via set_physics_parameters" + end if + + if (abs(config%domain_length - 3.0_wp) < 1e-10_wp) then + print *, " ✓ Domain length modified successfully via set_physics_parameters" + end if + + if (.not. config%enable_physics) then + print *, " ✓ Physics disabled successfully via set_physics_parameters" + end if + + print *, "" + + ! 测试4: 调用get_physics_info方法 + print *, "4. Testing get_physics_info method..." + print *, "--------------------------------------" + call config%get_physics_info() + print *, "" + + ! 测试5: 高斯脉冲配置 + print *, "5. Testing Gaussian pulse configuration..." + print *, "-----------------------------------------" + + config%ic_type = "gaussian" + config%pulse_center = 0.6_wp + config%pulse_width = 0.15_wp + + print *, "Gaussian pulse parameters:" + print *, " IC type: ", trim(config%ic_type) + print *, " Center: ", config%pulse_center + print *, " Width: ", config%pulse_width + + if (trim(config%ic_type) == "gaussian") then + print *, " ✓ Gaussian IC type set" + end if + + if (abs(config%pulse_center - 0.6_wp) < 1e-10_wp) then + print *, " ✓ Pulse center set" + end if + + if (abs(config%pulse_width - 0.15_wp) < 1e-10_wp) then + print *, " ✓ Pulse width set" + end if + + print *, "" + + ! 测试6: 重构配置 + print *, "6. Testing reconstruction configuration..." + print *, "------------------------------------------" + + call config_with_reconstruction(config, "weno", 5) + + print *, "Reconstruction configuration:" + print *, " Scheme: ", trim(config%recon_scheme) + print *, " Order: ", config%spatial_order + + if (trim(config%recon_scheme) == "weno" .and. config%spatial_order == 5) then + print *, " ✓ WENO5 configuration successful" + else + print *, " ✗ Reconstruction configuration failed" + end if + + print *, "" + + print *, "=== Configuration Physics Test Complete ===" + print *, "✓ Config module updated with physics support" + print *, "✓ Fields can be directly accessed and modified" + print *, "✓ Type-bound procedures work correctly" + +end program test_config_physics \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_domain_solution.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_domain_solution.f90 new file mode 100644 index 000000000..ff659bac2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_domain_solution.f90 @@ -0,0 +1,102 @@ +! tests/test_domain_solution.f90 +program test_domain_solution + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create, solution_reset + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + real(wp), allocatable :: initial_values(:) + integer :: i + + print *, "=== Domain and Solution Test ===" + print *, "" + + ! 测试1: 不同重构方案的ghost层计算 + print *, "1. Testing ghost layer calculation..." + print *, "--------------------------------------" + + ! ENO3 + call config_with_reconstruction(config, "eno", 3) + config%verbose = .false. + call mesh%init(ncells=10) + domain = domain_create(config, mesh) + print *, "ENO3: nghosts = ", domain%nghosts, " (expected: 3)" + + ! WENO3 + call config_with_reconstruction(config, "weno3", 3) + domain = domain_create(config, mesh) + print *, "WENO3: nghosts = ", domain%nghosts, " (expected: 2)" + + ! WENO5 + call config_with_reconstruction(config, "weno", 5) + domain = domain_create(config, mesh) + print *, "WENO5: nghosts = ", domain%nghosts, " (expected: 3)" + print *, "" + + ! 测试2: Solution数组 + print *, "2. Testing solution arrays..." + print *, "------------------------------" + + call config_with_reconstruction(config, "eno", 3) + config%verbose = .true. + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=10) + + domain = domain_create(config, mesh) + call domain%print_info() + print *, "" + + solution = solution_create(domain) + call solution%print_info() + print *, "" + + ! 测试3: 初始化和更新 + print *, "3. Testing initialization and update..." + print *, "----------------------------------------" + + allocate(initial_values(mesh%ncells)) + do i = 1, mesh%ncells + initial_values(i) = sin(2.0_wp * 3.14159265358979_wp * mesh%xcc(i) / mesh%L) + end do + + call solution%initialize(initial_values) + print *, "After initialization:" + print *, " u range: ", minval(solution%u), " to ", maxval(solution%u) + print *, " un range: ", minval(solution%un), " to ", maxval(solution%un) + + ! 修改当前解,测试更新 + solution%u = solution%u * 2.0_wp + call solution%update_old_field() + print *, "After update: max|u - un| = ", maxval(abs(solution%u - solution%un)) + print *, "" + + ! 测试4: 重置 + print *, "4. Testing reset..." + print *, "-------------------" + + call solution_reset(solution) + print *, "After reset:" + print *, " u max: ", maxval(abs(solution%u)) + print *, " un max: ", maxval(abs(solution%un)) + print *, " flux max: ", maxval(abs(solution%flux)) + print *, "" + + deallocate(initial_values) + + print *, "=== Test Summary ===" + print *, "✓ Ghost layer calculation works" + print *, "✓ Domain creation works" + print *, "✓ Solution arrays work" + print *, "✓ Initialization works" + print *, "✓ Field update works" + print *, "✓ Reset works" + print *, "" + print *, "Ready for next step: Implementing Physics modules" + +end program test_domain_solution \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_factory_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_factory_simple.f90 new file mode 100644 index 000000000..db65da7cf --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_factory_simple.f90 @@ -0,0 +1,58 @@ +! tests/test_factory_simple.f90 (修复版) +program test_factory_simple + use base_modules, only: wp ! ← 添加这行 + use config_module, only: cfd_config, config_print + use mesh_module, only: mesh_type + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(eno_reconstructor) :: eno + type(weno3_reconstructor) :: weno3 + type(rusanov_flux) :: rusanov + + print *, "=== Factory Pattern Simple Test ===" + print *, "" + + ! Test 1: Basic systems + print *, "1. Testing basic systems..." + print *, "-----------------------------" + call config_print(config) + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 2: Creating reconstructors + print *, "2. Testing reconstructors..." + print *, "------------------------------" + + ! 创建并测试ENO重构器 + print *, "Creating ENO reconstructor..." + eno = eno_reconstructor() ! 使用构造函数 + call eno%info() ! 必须调用info方法 + + print *, "" + print *, "Creating WENO3 reconstructor..." + weno3 = weno3_reconstructor() ! 使用构造函数 + call weno3%info() ! 必须调用info方法 + print *, "" + + ! Test 3: Creating flux calculator + print *, "3. Testing flux calculator..." + print *, "-------------------------------" + + print *, "Creating Rusanov flux calculator..." + rusanov = rusanov_flux() ! 使用构造函数 + call rusanov%info() ! 必须调用info方法 + print *, "" + + print *, "=== Factory pattern simple test completed successfully ===" + +end program test_factory_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_minimal_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_minimal_simple.f90 new file mode 100644 index 000000000..ec03ccf89 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_minimal_simple.f90 @@ -0,0 +1,87 @@ +! tests/test_minimal_simple.f90 +program test_minimal_simple + use base_modules, only: wp ! ← 添加这行 + use registry_module + use config_module + use mesh_module + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Simple Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call registry_init() + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call list_components() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + + if (has_component_simple("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component_simple("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components (simple version) + print *, "5. Testing registry functionality" + print *, "---------------------------------" + print *, "Registry initialized: ", registry_is_initialized() + print *, "Number of components: ", registry_get_size() + print *, "" + + ! Cleanup + call registry_cleanup() + + print *, "=== Simple test completed successfully ===" + +end program test_minimal_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_physics_minimal.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_physics_minimal.f90 new file mode 100644 index 000000000..cf2a28f19 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_physics_minimal.f90 @@ -0,0 +1,91 @@ +! tests/test_physics_minimal.f90 +program test_physics_minimal + use precision_module, only: wp, ip + use linear_convection_equation, only: linear_convection_eq, create_linear_convection_eq + use linear_convection_problem, only: linear_convection_prob, create_linear_convection_prob + + implicit none + + type(linear_convection_eq) :: eq + type(linear_convection_prob) :: prob + real(wp) :: u, f, a + real(wp), allocatable :: x(:), u_ic(:), u_exact(:) + integer :: i, nx = 10 + + print *, "=== 最小物理模块测试 ===" + print *, "" + + ! 测试1: 方程功能 + print *, "1. 测试方程功能..." + print *, "-------------------" + + eq = create_linear_convection_eq(wave_speed=2.0_wp) + print *, "方程: ", eq%name + print *, "波速: ", eq%wave_speed + + u = 1.5_wp + f = eq%flux(u) + a = eq%speed() + + print *, "u = ", u + print *, "F(u) = ", f, " (期望: 3.0)" + print *, "波速 a = ", a, " (期望: 2.0)" + + if (abs(f - 3.0_wp) < 1e-10_wp .and. abs(a - 2.0_wp) < 1e-10_wp) then + print *, "✓ 方程功能正常" + else + print *, "✗ 方程功能异常" + end if + print *, "" + + ! 测试2: 问题功能 + print *, "2. 测试问题功能..." + print *, "-------------------" + + prob = create_linear_convection_prob(ic_type="step", domain_length=2.0_wp) + print *, "问题: ", prob%name + print *, "IC类型: ", trim(prob%ic_type) + print *, "域长度: ", prob%domain_length + + allocate(x(nx), u_ic(nx), u_exact(nx)) + do i = 1, nx + x(i) = 0.0_wp + (i-1) * 0.2_wp + end do + + ! 测试初始条件 + call prob%initial_condition(x, u_ic) + print *, "初始条件范围: ", minval(u_ic), " 到 ", maxval(u_ic) + + ! 测试精确解 + u_exact = prob%exact_solution(x, 0.0_wp) + print *, "t=0时精确解范围: ", minval(u_exact), " 到 ", maxval(u_exact) + + ! 检查阶跃函数 + if (abs(u_ic(1) - 1.0_wp) < 1e-10_wp .and. & + abs(u_ic(6) - 2.0_wp) < 1e-10_wp) then + print *, "✓ 阶跃初始条件正确" + else + print *, "✗ 阶跃初始条件错误" + end if + + ! 检查精确解与初始条件一致 + if (maxval(abs(u_ic - u_exact)) < 1e-10_wp) then + print *, "✓ t=0时精确解与初始条件一致" + else + print *, "✗ 精确解计算错误" + end if + print *, "" + + ! 测试3: 边界条件接口 + print *, "3. 测试边界条件接口..." + print *, "----------------------" + call prob%boundary_condition(u_ic, 0.0_wp) + print *, "✓ 边界条件接口正常" + print *, "" + + deallocate(x, u_ic, u_exact) + + print *, "=== 物理模块最小测试完成 ===" + print *, "下一步: 将物理模块集成到现有系统中" + +end program test_physics_minimal \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_simple_link.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_simple_link.f90 new file mode 100644 index 000000000..71cc614e8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_simple_link.f90 @@ -0,0 +1,78 @@ +! tests/test_simple_link.f90 +program test_simple_link + use base_modules, only: wp ! ← 添加这行 + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call registry_init() + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call list_components() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component_simple("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component_simple("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Cleanup + call registry_cleanup() + + print *, "=== Minimal test completed successfully ===" + +end program test_simple_link \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_solver_framework.f90 b/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_solver_framework.f90 new file mode 100644 index 000000000..6754323d0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/02h/tests/test_solver_framework.f90 @@ -0,0 +1,91 @@ +! tests/test_solver_framework.f90 +program test_solver_framework + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use mesh_module, only: mesh_type + use solver_module, only: cfd_solver, solver_create, solver_run, solver_cleanup + use solver_module, only: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, & + SOLVER_COMPLETED, SOLVER_ERROR + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(cfd_solver) :: solver + + print *, "=== 求解器框架测试 ===" + print *, "" + + ! 测试1: 基本创建 + print *, "1. 测试求解器创建..." + print *, "----------------------" + + ! 创建配置 + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.0_real64 + config%dt = 0.01_real64 + + call config_print(config) + print *, "" + + ! 创建网格 + call mesh%init(xmin=0.0_real64, xmax=2.0_real64, ncells=20) + call mesh%print_info() + print *, "" + + ! 创建求解器 + solver = solver_create(config, mesh) + print *, "✓ 求解器创建成功" + print *, " 状态: ", solver%get_state() + print *, "" + + ! 测试2: 求解器初始化 + print *, "2. 测试求解器初始化..." + print *, "------------------------" + + call solver%initialize() + print *, "✓ 求解器初始化完成" + print *, " 状态: ", solver%get_state() + print *, " 错误信息: '", trim(solver%get_error()), "'" + print *, "" + + ! 测试3: 简单运行 + print *, "3. 测试求解器运行..." + print *, "----------------------" + + call solver_run(solver, 0.05_real64) ! 运行到0.05秒 + print *, "✓ 求解器运行完成" + print *, " 最终状态: ", solver%get_state() + print *, "" + + ! 测试4: 清理 + print *, "4. 测试求解器清理..." + print *, "----------------------" + + call solver_cleanup(solver) + print *, "✓ 求解器清理完成" + print *, " 状态: ", solver%get_state() + print *, "" + + ! 测试5: 错误处理 + print *, "5. 测试错误处理..." + print *, "-------------------" + + ! 尝试重复初始化 + call solver%initialize() + print *, " 重复初始化状态: ", solver%get_state() + print *, " 错误信息: '", trim(solver%get_error()), "'" + + call solver_cleanup(solver) + print *, "" + + print *, "=== 框架测试总结 ===" + print *, "✓ 求解器创建/初始化/运行/清理流程验证完成" + print *, "✓ 状态管理正常工作" + print *, "✓ 错误处理机制就绪" + print *, "" + print *, "下一步: 添加实际数值计算功能" + +end program test_solver_framework \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03/CMakeLists.txt new file mode 100644 index 000000000..ef66d5848 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/README.md b/example/1d-linear-convection/weno3/fortran/registry/03/README.md new file mode 100644 index 000000000..c4889757b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/README.md @@ -0,0 +1,221 @@ +# Fortran CFD Project + +基于工厂模式和注册系统的CFD求解器框架。 + +## 项目结构 + +``` +fortran_cfd/ +├── CMakeLists.txt # 根CMake配置 +├── scripts/ # 构建脚本 +│ ├── build.py # Python构建脚本(推荐) +│ ├── build.bat # Batch包装脚本 +│ ├── build.ps1 # PowerShell包装脚本 +│ └── requirements.txt # Python依赖 +├── src/ # 源代码 +│ ├── CMakeLists.txt +│ ├── core/ # 核心模块(注册系统、工厂接口) +│ ├── infrastructure/ # 基础设施(配置、网格) +│ └── numerics/ # 数值方法 +├── tests/ # 测试 +│ ├── CMakeLists.txt +│ ├── test_minimal_simple.f90 # 简单测试 +│ └── test_factory.f90 # 工厂模式测试 +└── README.md # 项目说明(本文件) +``` + +## 快速开始 + +### 使用Python脚本(推荐) + +```bash +# 进入脚本目录 +cd scripts + +# 基本构建 +python build.py + +# 清理并构建 +python build.py --clean + +# 发布构建 +python build.py --build-type Release --clean + +# 并行构建(使用所有核心) +python build.py -j + +# 不运行测试 +python build.py --no-tests + +# 查看帮助 +python build.py --help +``` + +### 使用批处理脚本 + +```bash +cd scripts +.\build.bat +``` + +### 使用PowerShell脚本 + +```powershell +cd scripts +.\build.ps1 +``` + +## 手动构建 + +```bash +# 设置Intel环境 +cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && powershell' + +# 配置项目 +mkdir build +cd build +cmake -G "Visual Studio 17 2022" -A x64 -T fortran=ifx .. + +# 构建项目 +cmake --build . --config Debug + +# 运行测试 +Debug\test_simple.exe +Debug\test_factory.exe +``` + +## 功能特性 + +✅ 组件注册系统 +✅ 工厂模式 +✅ ENO/WENO重构器 +✅ Rusanov通量计算器 +✅ 可扩展架构 +✅ 自动化测试 + +## 依赖 + +- Intel oneAPI(包含ifx编译器) +- CMake 3.12+ +- Python 3.6+(仅用于构建脚本) + +## 源代码文件 + +### 核心模块 +- `src/core/registry.f90` - 组件注册系统 +- `src/core/factory_interfaces.f90` - 工厂接口 + +### 基础设施 +- `src/infrastructure/config.f90` - 配置管理 +- `src/infrastructure/mesh.f90` - 网格生成 + +### 数值方法 +- `src/numerics/reconstructor/base.f90` - 重构器基类 +- `src/numerics/reconstructor/eno.f90` - ENO重构器 +- `src/numerics/reconstructor/weno3.f90` - WENO-3重构器 +- `src/numerics/flux/base.f90` - 通量计算器基类 +- `src/numerics/flux/rusanov.f90` - Rusanov通量 + +### 测试 +- `tests/test_minimal_simple.f90` - 简单功能测试 +- `tests/test_factory.f90` - 工厂模式测试 + +## 构建脚本 + +### Python脚本 (build.py) +主构建脚本,支持: +- 自动设置Intel oneAPI环境 +- 参数化构建选项 +- 并行编译 +- 自动化测试 +- 彩色输出 + +### Batch脚本 (build.bat) +Windows批处理包装器,调用Python脚本。 + +### PowerShell脚本 (build.ps1) +PowerShell包装器,调用Python脚本。 + +## 示例输出 + +成功构建后,会看到类似输出: + +``` +============================================================ + Fortran CFD Project Builder +============================================================ + +[1/4] Setting up Intel Fortran compiler... +✓ Intel oneAPI environment configured + +[2/4] Preparing build directory... +✓ Cleaned build directory + +[3/4] Configuring project... + $ cmake -G Visual Studio 17 2022 -A x64 -T fortran=ifx -DCMAKE_BUILD_TYPE=Debug .. +✓ CMake configuration successful + +[4/4] Building project... + $ cmake --build . --config Debug +✓ Build completed in 12.3 seconds + +============================================================ + Running Tests +============================================================ + +[TEST] Simple functionality test... +✓ Simple test passed + +[TEST] Factory pattern test... +✓ Factory test passed + +============================================================ + Build Summary +============================================================ +✓ Build directory: D:\path\to\build +✓ Build type: Debug +✓ Total time: 15.2s +``` + +## 开发指南 + +### 添加新组件 + +1. 在相应模块中创建Fortran源文件 +2. 实现工厂函数 +3. 使用`register_component_with_factory`注册组件 +4. 添加测试 + +### 扩展架构 + +项目采用模块化设计: +1. **注册系统** - 管理所有可用的组件 +2. **工厂模式** - 动态创建组件实例 +3. **抽象接口** - 确保组件兼容性 +4. **配置驱动** - 通过配置文件控制行为 + +## 故障排除 + +### 常见问题 + +1. **Intel环境未找到** + - 检查Intel oneAPI安装路径 + - 手动运行`setvars.bat` + +2. **CMake配置失败** + - 确保使用正确的生成器:`Visual Studio 17 2022` + - 检查Fortran编译器是否可用 + +3. **构建失败** + - 检查依赖项是否完整 + - 查看详细的错误信息 + +### 调试建议 + +- 使用`--verbose`参数获取详细输出 +- 检查`build/CMakeCache.txt`了解配置详情 +- 查看编译器错误日志 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/scripts/build.bat b/example/1d-linear-convection/weno3/fortran/registry/03/scripts/build.bat new file mode 100644 index 000000000..6fd6dc031 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/scripts/build.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Project Builder +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python build script with full Intel environment support... +echo. + +python build.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Build failed + pause + exit /b 1 +) + +echo. +echo [INFO] Build completed successfully! +echo. +echo [INFO] To run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/scripts/build.py b/example/1d-linear-convection/weno3/fortran/registry/03/scripts/build.py new file mode 100644 index 000000000..3bf6d5375 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/scripts/build.py @@ -0,0 +1,629 @@ +#!/usr/bin/env python3 +""" +Fortran CFD Project Builder - 完整Python解决方案 +在Python内部处理Intel oneAPI环境配置 +""" + +import os +import sys +import subprocess +import shutil +import argparse +import time +import platform +import tempfile +from pathlib import Path + +class IntelEnvironment: + """Intel oneAPI环境管理器""" + + def __init__(self): + self.setvars_path = None + self.env_vars = {} + + def find_setvars(self): + """查找setvars.bat文件""" + possible_paths = [ + r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat", + r"C:\Program Files\Intel\oneAPI\setvars.bat", + r"C:\Program Files (x86)\Intel\oneAPI\compiler\latest\env\vars.bat", + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\setvars.bat"), + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\compiler\latest\env\vars.bat"), + ] + + for path in possible_paths: + if os.path.exists(path): + self.setvars_path = path + return True + + return False + + def setup_environment(self): + """设置Intel环境""" + if not self.find_setvars(): + return False + + try: + # 创建临时的批处理文件来捕获环境变量 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + f.write(f'@echo off\n') + f.write(f'call "{self.setvars_path}" >nul 2>&1\n') + f.write(f'set\n') # 输出所有环境变量 + temp_bat = f.name + + # 运行批处理文件并捕获输出 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + shell=True + ) + + # 解析环境变量 + for line in result.stdout.split('\n'): + line = line.strip() + if '=' in line: + key, value = line.split('=', 1) + self.env_vars[key.strip()] = value.strip() + + # 清理临时文件 + os.unlink(temp_bat) + + # 更新当前进程的环境变量 + os.environ.update(self.env_vars) + + return True + + except Exception as e: + print(f"设置Intel环境失败: {e}") + return False + + def get_compiler_info(self): + """获取编译器信息""" + info = {} + + # 检查ifx编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + env={**os.environ, **self.env_vars} if self.env_vars else os.environ + ) + + if result.returncode == 0: + for line in result.stdout.split('\n'): + if 'Version' in line or '版本' in line: + info['ifx_version'] = line.strip() + break + except: + pass + + # 检查环境变量 + info['ifx_root'] = self.env_vars.get('IFX_ROOT', '') + info['compiler_root'] = self.env_vars.get('ONEAPI_ROOT', '') + + return info + +class BuildSystem: + """构建系统主类""" + + def __init__(self): + self.project_root = Path(__file__).parent.parent + self.build_dir = self.project_root / "build" + self.intel_env = IntelEnvironment() + + # 设置控制台编码 + if sys.platform == "win32": + try: + import ctypes + # 设置控制台输出为UTF-8 + ctypes.windll.kernel32.SetConsoleOutputCP(65001) + except: + pass + + def print_header(self, text): + """打印标题""" + print(f"\n{'='*70}") + print(f" {text}") + print(f"{'='*70}\n") + + def print_step(self, step, total, message): + """打印步骤""" + print(f"[{step}/{total}] {message}...") + + def print_success(self, message): + """打印成功""" + print(f"\033[92m✓ {message}\033[0m") + + def print_error(self, message): + """打印错误""" + print(f"\033[91m✗ {message}\033[0m") + + def print_warning(self, message): + """打印警告""" + print(f"\033[93m! {message}\033[0m") + + def print_info(self, message): + """打印信息""" + print(f"\033[94mℹ {message}\033[0m") + + def check_prerequisites(self): + """检查前提条件""" + self.print_step(1, 6, "检查前提条件") + + # 检查Python版本 + python_version = sys.version.split()[0] + self.print_info(f"Python版本: {python_version}") + + # 检查平台 + self.print_info(f"平台: {platform.system()} {platform.release()}") + self.print_info(f"处理器核心数: {os.cpu_count()}") + + # 检查CMake + try: + result = subprocess.run( + ["cmake", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + version_line = result.stdout.split('\n')[0] + self.print_success(f"CMake: {version_line}") + else: + self.print_error("CMake未找到") + return False + except FileNotFoundError: + self.print_error("CMake未安装") + return False + + return True + + def setup_intel_environment(self, args): + """设置Intel环境""" + self.print_step(2, 6, "配置Intel oneAPI环境") + + if not self.intel_env.find_setvars(): + self.print_warning("未找到Intel oneAPI setvars.bat") + self.print_info("将尝试使用系统环境中的编译器") + + # 检查是否能直接访问编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + self.print_success("Intel编译器在系统PATH中找到") + return True + else: + self.print_warning("Intel编译器未在PATH中找到") + except: + self.print_warning("无法访问Intel编译器") + + return True # 继续,让CMake自己找编译器 + + # 设置环境 + if self.intel_env.setup_environment(): + compiler_info = self.intel_env.get_compiler_info() + + if compiler_info.get('ifx_version'): + self.print_success(f"Intel Fortran编译器: {compiler_info['ifx_version']}") + elif compiler_info.get('ifx_root'): + self.print_success(f"Intel编译器路径: {compiler_info['ifx_root']}") + else: + self.print_success("Intel oneAPI环境配置完成") + + return True + else: + self.print_warning("Intel环境配置失败,将继续使用系统环境") + return True + + def clean_build_directory(self, args): + """清理构建目录""" + if args.clean and self.build_dir.exists(): + self.print_info("清理构建目录...") + try: + shutil.rmtree(self.build_dir) + self.print_success("构建目录已清理") + except Exception as e: + self.print_error(f"清理失败: {e}") + if not args.force: + return False + return True + + def run_command(self, cmd, cwd=None, check=True, env=None): + """运行命令""" + if isinstance(cmd, list): + cmd_str = ' '.join(str(c) for c in cmd if c) + else: + cmd_str = str(cmd) + + print(f" \033[96m$\033[0m {cmd_str}") + + try: + # 合并环境变量 + exec_env = os.environ.copy() + if env: + exec_env.update(env) + if self.intel_env.env_vars: + exec_env.update(self.intel_env.env_vars) + + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=False, + env=exec_env + ) + + # 处理输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or '完成' in line or '生成' in line: + print(f" \033[92m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + if check and result.returncode != 0: + self.print_error(f"命令执行失败,退出码: {result.returncode}") + return False + + return True + + except Exception as e: + self.print_error(f"命令执行异常: {e}") + return False + + def configure_cmake(self, args): + """配置CMake""" + self.print_step(3, 6, "配置CMake项目") + + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + f"-DCMAKE_BUILD_TYPE={args.build_type}", + ] + + if args.compiler == "ifx": + cmake_cmd.extend(["-T", "fortran=ifx"]) + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + success = self.run_command(cmake_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("CMake配置完成") + else: + self.print_error("CMake配置失败") + + return success + + def build_project(self, args): + """构建项目""" + self.print_step(4, 6, "构建项目") + + build_cmd = [ + "cmake", + "--build", ".", + "--config", args.build_type, + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + success = self.run_command(build_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("项目构建完成") + else: + self.print_error("构建失败") + + return success + + def run_tests_with_environment(self, test_exe): + """运行单个测试,确保有Intel环境""" + try: + # 创建临时的批处理文件来运行测试 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'"{test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'"{test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + return result + + except Exception as e: + print(f"运行测试失败: {e}") + return None + + def run_tests(self, args): + """运行测试""" + self.print_step(5, 6, "运行测试") + + # 查找测试可执行文件 + test_dir = self.build_dir / "bin" / args.build_type + if not test_dir.exists(): + test_dir = self.build_dir / "bin" + if not test_dir.exists(): + test_dir = self.build_dir + + test_files = list(test_dir.glob("test_*.exe")) + + if not test_files: + self.print_warning("未找到测试程序") + return True + + all_passed = True + + for test_exe in sorted(test_files): + test_name = test_exe.stem + self.print_info(f"运行测试: {test_name}") + print(f" {'-'*50}") + + # 运行测试 + result = self.run_tests_with_environment(str(test_exe)) + + if result is None: + self.print_error(f" {test_name} 运行失败") + all_passed = False + continue + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + print(f" {line}") + + if result.returncode == 0: + self.print_success(f" {test_name} 通过") + else: + self.print_error(f" {test_name} 失败 (退出码: {result.returncode})") + all_passed = False + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + print() # 空行 + + return all_passed + + def create_test_runner(self, args): + """创建独立的测试运行器""" + self.print_step(6, 6, "创建测试运行器") + + runner_path = self.build_dir / "run_tests.bat" + + content = f'''@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Test Runner +echo ======================================== +echo. + +REM Setup Intel oneAPI environment +set "SETVARS_PATH={self.intel_env.setvars_path or ''}" +if exist "%SETVARS_PATH%" ( + call "%SETVARS_PATH%" >nul + echo [INFO] Intel environment configured +) else ( + echo [WARNING] Intel environment not found + echo [WARNING] Tests may fail without runtime libraries +) + +echo. + +REM Run all test executables +set "TEST_COUNT=0" +set "PASS_COUNT=0" + +for %%f in ("bin\\{args.build_type}\\test_*.exe") do ( + set /a TEST_COUNT+=1 + echo [TEST %%f] + echo {'-'*50} + + %%f + if errorlevel 1 ( + echo [FAILED] %%f + ) else ( + echo [PASSED] %%f + set /a PASS_COUNT+=1 + ) + echo. +) + +echo ======================================== +echo Tests: %PASS_COUNT%/%TEST_COUNT% passed +if %PASS_COUNT% equ %TEST_COUNT% ( + echo [SUCCESS] All tests passed! +) else ( + echo [FAILURE] Some tests failed +) +echo ======================================== + +pause +''' + + with open(runner_path, 'w', encoding='utf-8') as f: + f.write(content) + + self.print_success(f"测试运行器已创建: {runner_path}") + self.print_info(f"使用方法: cd build && run_tests.bat") + + return runner_path + + def generate_report(self, args, build_time, tests_passed): + """生成构建报告""" + self.print_header("构建完成") + + print(f"项目: {self.project_root.name}") + print(f"构建类型: {args.build_type}") + print(f"编译器: {args.compiler}") + print(f"并行作业: {args.jobs}") + print(f"总耗时: {build_time:.1f}秒") + print(f"测试结果: {'全部通过' if tests_passed else '有失败'}") + + # 显示生成的可执行文件 + bin_dir = self.build_dir / "bin" / args.build_type + if bin_dir.exists(): + print(f"\n生成的可执行文件:") + for exe in sorted(bin_dir.glob("*.exe")): + size_mb = exe.stat().st_size / (1024 * 1024) + print(f" • {exe.name} ({size_mb:.2f} MB)") + + # 显示测试运行器信息 + runner_path = self.build_dir / "run_tests.bat" + if runner_path.exists(): + print(f"\n独立测试运行器:") + print(f" • {runner_path.name}") + print(f" 在Intel oneAPI环境中运行所有测试") + + def run(self): + """运行构建系统""" + parser = argparse.ArgumentParser( + description="Fortran CFD项目构建工具", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认构建 + %(prog)s --clean # 清理后构建 + %(prog)s --build-type Release # Release构建 + %(prog)s --no-tests # 只构建,不运行测试 + %(prog)s -j8 --verbose # 8线程并行构建,详细输出 + """ + ) + + parser.add_argument("--build-type", choices=["Debug", "Release"], + default="Debug", help="构建类型") + parser.add_argument("--compiler", choices=["ifx", "ifort"], + default="ifx", help="Fortran编译器") + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-tests", action="store_true", + help="跳过测试") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + + args = parser.parse_args() + + # 开始构建 + start_time = time.time() + + self.print_header("Fortran CFD 项目构建系统") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 检查前提条件 + if not self.check_prerequisites(): + if not args.force: + return 1 + + # 2. 设置Intel环境 + if not self.setup_intel_environment(args): + if not args.force: + return 1 + + # 3. 清理目录 + if not self.clean_build_directory(args): + if not args.force: + return 1 + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 4. 配置CMake + if not self.configure_cmake(args): + if not args.force: + return 1 + + # 5. 构建项目 + if not self.build_project(args): + if not args.force: + return 1 + + # 6. 运行测试和创建测试运行器 + tests_passed = True + if not args.no_tests: + tests_passed = self.run_tests(args) + + # 创建测试运行器 + self.create_test_runner(args) # 传递 args 参数 + + # 7. 生成报告 + build_time = time.time() - start_time + self.generate_report(args, build_time, tests_passed) + + return 0 if tests_passed else 1 + + except KeyboardInterrupt: + self.print_error("\n构建被用户中断") + return 1 + except Exception as e: + self.print_error(f"构建过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + builder = BuildSystem() + return builder.run() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/scripts/requirements.txt b/example/1d-linear-convection/weno3/fortran/registry/03/scripts/requirements.txt new file mode 100644 index 000000000..d21a12b45 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/scripts/requirements.txt @@ -0,0 +1,2 @@ +# 构建脚本的Python依赖(可选) +# 目前没有特殊依赖,保持空文件或添加未来可能需要的包 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/scripts/run_all_steps.bat b/example/1d-linear-convection/weno3/fortran/registry/03/scripts/run_all_steps.bat new file mode 100644 index 000000000..d506149b2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/scripts/run_all_steps.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo CFD Project: All Steps +echo ======================================== +echo. + +echo [INFO] Starting Step 1: Physics Modules Test... +call run_step1.bat + +if errorlevel 1 ( + echo [ERROR] Step 1 failed + pause + exit /b 1 +) + +echo. +echo [INFO] Starting Step 2: Configuration Physics Update... +call run_step2.bat + +if errorlevel 1 ( + echo [ERROR] Step 2 failed + pause + exit /b 1 +) + +echo. +echo ======================================== +echo All Steps Completed Successfully! +echo ======================================== +echo. +echo [INFO] Next: Update component manager for physics support +echo [INFO] Run: run_step3.bat (to be created) +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/scripts/run_step1.bat b/example/1d-linear-convection/weno3/fortran/registry/03/scripts/run_step1.bat new file mode 100644 index 000000000..0b6b1f17e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/scripts/run_step1.bat @@ -0,0 +1,39 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Step 1: Physics Modules Test +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python step1 script with full Intel environment support... +echo. + +python run_step1.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Step 1 failed + pause + exit /b 1 +) + +echo. +echo [INFO] Step 1 completed successfully! +echo. +echo [INFO] Next step: Update config to include physics settings +echo [INFO] Run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/scripts/run_step1.py b/example/1d-linear-convection/weno3/fortran/registry/03/scripts/run_step1.py new file mode 100644 index 000000000..5e087a690 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/scripts/run_step1.py @@ -0,0 +1,319 @@ +#!/usr/bin/env python3 +""" +Step 1: Physics Modules Test +扩展build.py,专门用于测试物理模块 +""" + +import os +import sys +import subprocess +import time +from pathlib import Path + +# 添加当前目录到路径,以便导入build.py的类 +sys.path.insert(0, str(Path(__file__).parent)) + +try: + from build import IntelEnvironment, BuildSystem +except ImportError: + print("Error: Cannot import build.py. Make sure build.py is in the same directory.") + sys.exit(1) + +class Step1System(BuildSystem): + """Step 1 测试系统,继承自BuildSystem""" + + def __init__(self): + super().__init__() + self.test_name = "test_physics_minimal" + self.test_exe = None + + def find_test_executable(self): + """查找测试可执行文件""" + possible_paths = [ + self.build_dir / "bin" / "Debug" / f"{self.test_name}.exe", + self.build_dir / "Debug" / f"{self.test_name}.exe", + self.build_dir / f"{self.test_name}.exe", + self.build_dir / "bin" / f"{self.test_name}.exe", + ] + + for path in possible_paths: + if path.exists(): + self.test_exe = path + self.print_success(f"Found test executable: {path}") + return True + + # 如果没有找到,尝试搜索 + self.print_warning(f"Could not find {self.test_name}.exe") + self.print_info("Searching for test executables...") + + try: + result = subprocess.run( + ["dir", str(self.build_dir), "/s", "/b", "*.exe"], + capture_output=True, + text=True, + encoding='utf-8', + shell=True + ) + + if result.returncode == 0: + test_files = [line.strip() for line in result.stdout.split('\n') + if line and 'test_' in line.lower()] + + if test_files: + self.print_info("Found test files:") + for test_file in test_files: + self.print_info(f" {test_file}") + return False + except: + pass + + return False + + def run_test_with_intel_env(self): + """在Intel环境下运行测试""" + if not self.test_exe: + self.print_error("No test executable found") + return False + + self.print_step(1, 2, f"Running test: {self.test_exe.name}") + + try: + # 创建临时的批处理文件来运行测试(包含Intel环境) + import tempfile + + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + # 设置Intel环境 + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'echo [INFO] Intel environment configured\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'echo [WARNING] Intel environment not found\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + self.print_info(f"Command: {temp_bat}") + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower() or 'fail' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or 'pass' in line.lower() or '✓' in line: + print(f" \033[92m{line}\033[0m") + elif '=' in line or '---' in line: + print(f" \033[96m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + self.print_warning("Test stderr output:") + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + self.print_step(2, 2, "Test execution completed") + + if result.returncode == 0: + self.print_success("Test passed") + return True + else: + self.print_error(f"Test failed (exit code: {result.returncode})") + return False + + except Exception as e: + self.print_error(f"Failed to run test: {e}") + return False + + def build_project_if_needed(self, args): + """如果需要,构建项目""" + if args.no_build: + self.print_info("Skipping build (--no-build flag)") + return True + + self.print_step(1, 3, "Building project") + + # 调用父类的构建方法 + build_args = argparse.Namespace() + build_args.clean = args.clean + build_args.build_type = "Debug" + build_args.compiler = "ifx" + build_args.no_tests = True # 不运行所有测试 + build_args.jobs = os.cpu_count() + build_args.verbose = args.verbose + build_args.force = args.force + + # 清理构建目录 + if args.clean and self.build_dir.exists(): + self.print_info("Cleaning build directory...") + import shutil + try: + shutil.rmtree(self.build_dir) + self.print_success("Build directory cleaned") + except Exception as e: + self.print_error(f"Clean failed: {e}") + if not args.force: + return False + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 配置CMake + self.print_step(2, 3, "Configuring CMake") + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + "-DCMAKE_BUILD_TYPE=Debug", + "-T", "fortran=ifx", + ] + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + if not self.run_command(cmake_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + # 构建项目 + self.print_step(3, 3, "Building project") + build_cmd = [ + "cmake", + "--build", ".", + "--config", "Debug", + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + if not self.run_command(build_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + self.print_success("Build completed") + return True + + def run(self): + """运行Step 1测试""" + parser = argparse.ArgumentParser( + description="Step 1: Physics Modules Test", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认运行 + %(prog)s --clean # 清理后构建并测试 + %(prog)s --no-build # 只运行测试,不重新构建 + %(prog)s --verbose # 详细输出 + %(prog)s -j4 # 使用4个并行作业构建 + """ + ) + + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-build", action="store_true", + help="不重新构建,直接运行测试") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + + args = parser.parse_args() + + # 开始测试 + start_time = time.time() + + self.print_header("Step 1: Physics Modules Implementation Test") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 设置Intel环境 + self.print_step(1, 4, "Setting up Intel oneAPI environment") + if not self.setup_intel_environment(args): + if not args.force: + self.print_error("Intel environment setup failed") + return 1 + self.print_warning("Intel environment setup failed, continuing...") + + # 2. 构建项目(如果需要) + self.print_step(2, 4, "Building project if needed") + if not self.build_project_if_needed(args): + if not args.force: + return 1 + + # 3. 查找测试可执行文件 + self.print_step(3, 4, "Finding test executable") + if not self.find_test_executable(): + if not args.force: + return 1 + self.print_warning("Test executable not found, but continuing due to --force") + return 0 + + # 4. 运行测试 + self.print_step(4, 4, "Running physics module test") + test_passed = self.run_test_with_intel_env() + + # 生成报告 + test_time = time.time() - start_time + self.print_header("Step 1 Complete") + + print(f"测试: {'通过 ✓' if test_passed else '失败 ✗'}") + print(f"测试程序: {self.test_exe.name if self.test_exe else '未找到'}") + print(f"总耗时: {test_time:.1f}秒") + + if test_passed: + print(f"\n下一步: 更新配置以包含物理设置") + print(f"建议: 修改config.f90,添加physics相关字段") + return 0 + else: + if args.force: + self.print_warning("测试失败,但由于--force标志继续执行") + return 0 + return 1 + + except KeyboardInterrupt: + self.print_error("\n测试被用户中断") + return 1 + except Exception as e: + self.print_error(f"测试过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + system = Step1System() + return system.run() + +if __name__ == "__main__": + # 需要导入argparse + import argparse + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/scripts/run_step2.bat b/example/1d-linear-convection/weno3/fortran/registry/03/scripts/run_step2.bat new file mode 100644 index 000000000..9c1f62de4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/scripts/run_step2.bat @@ -0,0 +1,39 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Step 2: Configuration Physics Update +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python step2 script with full Intel environment support... +echo. + +python run_step2.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Step 2 failed + pause + exit /b 1 +) + +echo. +echo [INFO] Step 2 completed successfully! +echo. +echo [INFO] Next step: Update component manager to support physics +echo [INFO] Run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/scripts/run_step2.py b/example/1d-linear-convection/weno3/fortran/registry/03/scripts/run_step2.py new file mode 100644 index 000000000..c16b76088 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/scripts/run_step2.py @@ -0,0 +1,284 @@ +#!/usr/bin/env python3 +""" +Step 2: Configuration Physics Update +测试配置模块的物理功能更新 +""" + +import os +import sys +import subprocess +import time +from pathlib import Path + +# 添加当前目录到路径,以便导入build.py的类 +sys.path.insert(0, str(Path(__file__).parent)) + +try: + from build import IntelEnvironment, BuildSystem +except ImportError: + print("Error: Cannot import build.py. Make sure build.py is in the same directory.") + sys.exit(1) + +class Step2System(BuildSystem): + """Step 2 测试系统,继承自BuildSystem""" + + def __init__(self): + super().__init__() + self.test_name = "test_config_physics" + self.test_exe = None + + def find_test_executable(self): + """查找测试可执行文件""" + possible_paths = [ + self.build_dir / "bin" / "Debug" / f"{self.test_name}.exe", + self.build_dir / "Debug" / f"{self.test_name}.exe", + self.build_dir / f"{self.test_name}.exe", + self.build_dir / "bin" / f"{self.test_name}.exe", + ] + + for path in possible_paths: + if path.exists(): + self.test_exe = path + self.print_success(f"Found test executable: {path}") + return True + + return False + + def run_test_with_intel_env(self): + """在Intel环境下运行测试""" + if not self.test_exe: + self.print_error("No test executable found") + return False + + self.print_step(1, 2, f"Running test: {self.test_exe.name}") + + try: + # 创建临时的批处理文件来运行测试(包含Intel环境) + import tempfile + + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + # 设置Intel环境 + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'echo [INFO] Intel environment configured\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'echo [WARNING] Intel environment not found\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + self.print_info(f"Command: {temp_bat}") + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower() or 'fail' in line.lower() or '✗' in line: + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or 'pass' in line.lower() or '✓' in line: + print(f" \033[92m{line}\033[0m") + elif '=' in line or '---' in line or '===' in line: + print(f" \033[96m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + self.print_warning("Test stderr output:") + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + self.print_step(2, 2, "Test execution completed") + + if result.returncode == 0: + self.print_success("Test passed") + return True + else: + self.print_error(f"Test failed (exit code: {result.returncode})") + return False + + except Exception as e: + self.print_error(f"Failed to run test: {e}") + return False + + def build_project(self, args): + """构建项目""" + if args.no_build: + self.print_info("Skipping build (--no-build flag)") + return True + + self.print_step(1, 3, "Building project") + + # 清理构建目录 + if args.clean and self.build_dir.exists(): + self.print_info("Cleaning build directory...") + import shutil + try: + shutil.rmtree(self.build_dir) + self.print_success("Build directory cleaned") + except Exception as e: + self.print_error(f"Clean failed: {e}") + if not args.force: + return False + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 配置CMake + self.print_step(2, 3, "Configuring CMake") + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + "-DCMAKE_BUILD_TYPE=Debug", + "-T", "fortran=ifx", + ] + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + if not self.run_command(cmake_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + # 构建项目 + self.print_step(3, 3, "Building project") + build_cmd = [ + "cmake", + "--build", ".", + "--config", "Debug", + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + if not self.run_command(build_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + self.print_success("Build completed") + return True + + def run(self): + """运行Step 2测试""" + import argparse + + parser = argparse.ArgumentParser( + description="Step 2: Configuration Physics Update", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认运行 + %(prog)s --clean # 清理后构建并测试 + %(prog)s --no-build # 只运行测试,不重新构建 + %(prog)s --verbose # 详细输出 + """ + ) + + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-build", action="store_true", + help="不重新构建,直接运行测试") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + + args = parser.parse_args() + + # 开始测试 + start_time = time.time() + + self.print_header("Step 2: Configuration Physics Update") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 设置Intel环境 + self.print_step(1, 4, "Setting up Intel oneAPI environment") + if not self.setup_intel_environment(args): + if not args.force: + self.print_error("Intel environment setup failed") + return 1 + self.print_warning("Intel environment setup failed, continuing...") + + # 2. 构建项目(如果需要) + self.print_step(2, 4, "Building project if needed") + if not self.build_project(args): + if not args.force: + return 1 + + # 3. 查找测试可执行文件 + self.print_step(3, 4, "Finding test executable") + if not self.find_test_executable(): + self.print_error(f"Test executable {self.test_name}.exe not found") + if not args.force: + return 1 + self.print_warning("Test executable not found, but continuing due to --force") + return 0 + + # 4. 运行测试 + self.print_step(4, 4, "Running configuration physics test") + test_passed = self.run_test_with_intel_env() + + # 生成报告 + test_time = time.time() - start_time + self.print_header("Step 2 Complete") + + print(f"测试: {'通过 ✓' if test_passed else '失败 ✗'}") + print(f"测试程序: {self.test_exe.name if self.test_exe else '未找到'}") + print(f"总耗时: {test_time:.1f}秒") + + if test_passed: + print(f"\n下一步: 更新组件管理器以支持物理模块") + print(f"建议: 修改component_manager.f90,添加physics组件创建") + return 0 + else: + if args.force: + self.print_warning("测试失败,但由于--flag继续执行") + return 0 + return 1 + + except KeyboardInterrupt: + self.print_error("\n测试被用户中断") + return 1 + except Exception as e: + self.print_error(f"测试过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + system = Step2System() + return system.run() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03/src/CMakeLists.txt new file mode 100644 index 000000000..2aaee245a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/CMakeLists.txt @@ -0,0 +1,18 @@ +# ==================== src\CMakeLists.txt ==================== + +# src/CMakeLists.txt +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(base) +add_subdirectory(core) +add_subdirectory(infrastructure) +add_subdirectory(numerics) +add_subdirectory(physics) # ← 新增物理模块目录 +add_subdirectory(manager) +add_subdirectory(solver) + +message(STATUS "源代码目录配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/base/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03/src/base/CMakeLists.txt new file mode 100644 index 000000000..74f4aa65f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/base/CMakeLists.txt @@ -0,0 +1,16 @@ +# src/base/CMakeLists.txt +message(STATUS "Configuring base module...") + +add_library(base STATIC + modules.f90 + precision.f90 # 新增 +) + +set_target_properties(base PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Base module configured") + +# src/core/CMakeLists.txt +message(STATUS "Configuring core module...") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/base/modules.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/src/base/modules.f90 new file mode 100644 index 000000000..43aaee241 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/base/modules.f90 @@ -0,0 +1,36 @@ +! src/base/modules.f90 +module base_modules + use, intrinsic :: iso_fortran_env, only: real64, int32 + implicit none + + public :: wp, ip, max_name_len, string_len, cfd_config_base, component_info + + integer, parameter :: wp = real64 + integer, parameter :: ip = int32 + integer, parameter :: string_len = 100 + integer, parameter :: max_name_len = 32 + + ! 基础配置类型 + type :: cfd_config_base + character(len=max_name_len) :: ic_type = "step" + character(len=max_name_len) :: recon_scheme = "eno" + character(len=max_name_len) :: flux_type = "rusanov" + integer(ip) :: rk_order = 1 + real(wp) :: wave_speed = 1.0_wp + real(wp) :: final_time = 0.625_wp + real(wp) :: dt = 0.025_wp + character(len=max_name_len) :: boundary_type = "periodic" + integer(ip) :: spatial_order = 2 + character(len=max_name_len) :: equation_type = "linear_advection" + character(len=max_name_len) :: problem_type = "linear_advection" + logical :: verbose = .true. + end type cfd_config_base + + ! 组件信息类型 + type :: component_info + character(len=max_name_len) :: category = "" + character(len=max_name_len) :: name = "" + integer(ip) :: order = 0 + end type component_info + +end module base_modules \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/base/precision.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/src/base/precision.f90 new file mode 100644 index 000000000..4ac5fd7ef --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/base/precision.f90 @@ -0,0 +1,9 @@ +! src/base/precision.f90(简单版本) +module precision_module + use base_modules, only: wp, ip + implicit none + + ! 重新导出,确保兼容 + public :: wp, ip + +end module precision_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03/src/core/CMakeLists.txt new file mode 100644 index 000000000..d8b8df064 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/core/CMakeLists.txt @@ -0,0 +1,14 @@ +# src/core/CMakeLists.txt +message(STATUS "Configuring core module...") + +add_library(core STATIC + registry.f90 +) + +target_link_libraries(core PRIVATE base) + +set_target_properties(core PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Core module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/core/factory_base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/src/core/factory_base.f90 new file mode 100644 index 000000000..302418a15 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/core/factory_base.f90 @@ -0,0 +1,57 @@ +! src/core/factory_base.f90 +module factory_base_module + use base_modules, only: wp, ip + use registry_module, only: create_component, has_component + + implicit none + private + public :: wp, ip, factory_base, factory_create + + ! 工厂基类 + type :: factory_base + character(len=max_name_length) :: category = "" + contains + procedure :: create => factory_base_create + procedure :: get_available => factory_base_get_available + end type factory_base + + ! 便捷函数类型 + abstract interface + function factory_function_interface(category, name) result(instance) + import :: wp + character(len=*), intent(in) :: category, name + class(*), allocatable :: instance + end function factory_function_interface + end interface + +contains + + ! 创建工厂实例 + function factory_create(category) result(factory) + character(len=*), intent(in) :: category + type(factory_base) :: factory + factory%category = trim(category) + end function factory_create + + ! 工厂创建方法 + function factory_base_create(this, name) result(instance) + class(factory_base), intent(in) :: this + character(len=*), intent(in) :: name + class(*), allocatable :: instance + + instance = create_component(this%category, name) + end function factory_base_create + + ! 获取可用组件列表(简化版) + subroutine factory_base_get_available(this, names, count) + class(factory_base), intent(in) :: this + character(len=*), allocatable, intent(out) :: names(:) + integer(ip), intent(out) :: count + + ! 这里需要实现从注册表获取列表的逻辑 + ! 暂时返回空列表 + count = 0 + allocate(character(len=max_name_length) :: names(0)) + end subroutine factory_base_get_available + +end module factory_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..a960167c3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/core/factory_interfaces.f90 @@ -0,0 +1,17 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, factory_procedure + + ! Factory procedure interface + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/src/core/registry.f90 new file mode 100644 index 000000000..acc63edb4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/core/registry.f90 @@ -0,0 +1,203 @@ +! src/core/registry.f90 +module registry_module + use base_modules, only: wp, ip, max_name_len, component_info + + implicit none + private + + ! 明确公开所有需要的接口 + public :: wp, ip ! 类型参数 + public :: component_info ! 类型 + public :: registry_init, registry_cleanup ! 初始化/清理 + public :: register_component_simple ! 注册组件 + public :: has_component_simple ! 检查组件 + public :: list_components ! 列出组件 + public :: registry_is_initialized ! 检查初始化状态 ← 新增 + public :: registry_get_size ! 获取大小 ← 新增 + + ! 全局注册表 + type :: component_registry + type(component_info), allocatable :: components(:) + integer(ip) :: count = 0 + integer(ip) :: capacity = 100 + logical :: initialized = .false. + logical :: verbose = .true. + end type component_registry + + type(component_registry) :: registry + +contains + + ! ==================== 公共API ==================== + + subroutine registry_init(verbose) + logical, optional, intent(in) :: verbose + + if (registry%initialized) then + if (registry%verbose) then + print *, "[REGISTRY] Already initialized" + end if + return + end if + + if (present(verbose)) then + registry%verbose = verbose + end if + + allocate(registry%components(registry%capacity)) + registry%initialized = .true. + + if (registry%verbose) then + print *, "[REGISTRY] Initialized with capacity:", registry%capacity + end if + end subroutine registry_init + + subroutine registry_cleanup() + if (allocated(registry%components)) then + deallocate(registry%components) + end if + registry%initialized = .false. + registry%count = 0 + + if (registry%verbose) then + print *, "[REGISTRY] Cleaned up" + end if + end subroutine registry_cleanup + + subroutine register_component_simple(category, name, order) + character(len=*), intent(in) :: category, name + integer(ip), optional, intent(in) :: order + + integer(ip) :: i + type(component_info) :: info + + if (.not. registry%initialized) then + call registry_init() + end if + + ! 检查是否已存在 + do i = 1, registry%count + if (trim(registry%components(i)%category) == trim(category) .and. & + trim(registry%components(i)%name) == trim(name)) then + if (registry%verbose) then + print *, "[WARN] Overwriting component: ", trim(category), ".", trim(name) + end if + + ! 更新 + if (present(order)) then + registry%components(i)%order = order + else + registry%components(i)%order = 0 + end if + return + end if + end do + + ! 扩展数组 + if (registry%count >= registry%capacity) then + call expand_registry() + end if + + ! 添加新组件 + registry%count = registry%count + 1 + + info%category = trim(category) + info%name = trim(name) + info%order = 0 + if (present(order)) then + info%order = order + end if + + registry%components(registry%count) = info + + if (registry%verbose) then + print *, "[OK] Registered simple: ", trim(category), ".", trim(name) + end if + end subroutine register_component_simple + + logical function has_component_simple(category, name) + character(len=*), intent(in) :: category, name + + integer(ip) :: i + + has_component_simple = .false. + + if (.not. registry%initialized) return + + do i = 1, registry%count + if (trim(registry%components(i)%category) == trim(category) .and. & + trim(registry%components(i)%name) == trim(name)) then + has_component_simple = .true. + return + end if + end do + end function has_component_simple + + subroutine list_components(category) + character(len=*), optional, intent(in) :: category + + integer(ip) :: i, count + + if (.not. registry%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + if (registry%count == 0) then + print *, "[INFO] No components registered" + return + end if + + count = 0 + print *, "=== Registry Contents ===" + do i = 1, registry%count + if (.not. present(category) .or. & + trim(registry%components(i)%category) == trim(category)) then + call print_component_info(registry%components(i)) + count = count + 1 + end if + end do + + print *, "Total:", count, "components" + print *, "==========================" + end subroutine list_components + + ! ==================== 新增函数 ==================== + + logical function registry_is_initialized() + ! 检查注册表是否已初始化 + registry_is_initialized = registry%initialized + end function registry_is_initialized + + integer(ip) function registry_get_size() + ! 获取注册表中的组件数量 + registry_get_size = registry%count + end function registry_get_size + + ! ==================== 内部辅助函数 ==================== + + subroutine expand_registry() + type(component_info), allocatable :: temp(:) + + registry%capacity = registry%capacity * 2 + allocate(temp(registry%capacity)) + temp(1:registry%count) = registry%components(1:registry%count) + call move_alloc(temp, registry%components) + + if (registry%verbose) then + print *, "[INFO] Registry expanded to capacity:", registry%capacity + end if + end subroutine expand_registry + + subroutine print_component_info(info) + type(component_info), intent(in) :: info + + if (info%order > 0) then + print *, " [", trim(info%category), ".", trim(info%name), & + " (order:", info%order, ")]" + else + print *, " [", trim(info%category), ".", trim(info%name), "]" + end if + end subroutine print_component_info + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..70cbbd2f3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/infrastructure/CMakeLists.txt @@ -0,0 +1,17 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "Configuring infrastructure module...") + +add_library(infrastructure STATIC + config.f90 + mesh.f90 + domain.f90 # 新增 + solution.f90 # 新增 +) + +target_link_libraries(infrastructure PRIVATE base) + +set_target_properties(infrastructure PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Infrastructure module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/src/infrastructure/config.f90 new file mode 100644 index 000000000..7586a1a50 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/infrastructure/config.f90 @@ -0,0 +1,144 @@ +! src/infrastructure/config.f90 (修复版) +module config_module + use base_modules, only: wp, ip, max_name_len, cfd_config_base + + implicit none + public :: wp, ip, cfd_config, config_print, config_with_reconstruction + + ! 扩展配置类型 - 添加物理相关字段 + type, extends(cfd_config_base) :: cfd_config + ! 物理参数 + real(wp) :: left_boundary_value = 1.0_wp + real(wp) :: right_boundary_value = 2.0_wp + real(wp) :: domain_length = 2.0_wp + + ! 新增:物理模块相关配置 + real(wp) :: pulse_center = 0.5_wp ! 高斯脉冲中心 + real(wp) :: pulse_width = 0.1_wp ! 高斯脉冲宽度 + logical :: enable_physics = .true. ! 是否启用物理模块 + contains + ! 新增:物理相关配置方法 + procedure :: set_physics_parameters + procedure :: get_physics_info + end type cfd_config + +contains + + subroutine config_print(cfg) + type(cfd_config), intent(in) :: cfg + + print *, "=== CFD Configuration ===" + print *, "Initial condition: ", trim(cfg%ic_type) + print *, "Reconstruction: ", trim(cfg%recon_scheme), " (order:", cfg%spatial_order, ")" + print *, "Flux type: ", trim(cfg%flux_type) + print *, "Time integration: RK", cfg%rk_order + print *, "Wave speed: ", cfg%wave_speed + print *, "Final time: ", cfg%final_time + print *, "Time step: ", cfg%dt + print *, "Boundary: ", trim(cfg%boundary_type) + + ! 新增:物理配置信息 + print *, "--- Physics Configuration ---" + print *, "Equation type: ", trim(cfg%equation_type) + print *, "Problem type: ", trim(cfg%problem_type) + print *, "Domain length: ", cfg%domain_length + print *, "Physics enabled: ", cfg%enable_physics + + if (cfg%ic_type == "gaussian") then + print *, "Pulse center: ", cfg%pulse_center + print *, "Pulse width: ", cfg%pulse_width + end if + + print *, "===============================" + end subroutine config_print + + subroutine config_with_reconstruction(cfg, scheme, order) + type(cfd_config), intent(inout) :: cfg + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + integer :: i + + ! 转换为小写 + cfg%recon_scheme = scheme + do i = 1, len_trim(cfg%recon_scheme) + if (cfg%recon_scheme(i:i) >= 'A' .and. cfg%recon_scheme(i:i) <= 'Z') then + cfg%recon_scheme(i:i) = char(ichar(cfg%recon_scheme(i:i)) + 32) + end if + end do + + ! 设置阶数 + if (present(order)) then + cfg%spatial_order = order + else + if (index(cfg%recon_scheme, 'weno') > 0) then + cfg%spatial_order = 5 + else if (trim(cfg%recon_scheme) == 'eno') then + cfg%spatial_order = 3 + end if + end if + + if (cfg%verbose) then + print *, "[CONFIG] Reconstruction: ", trim(cfg%recon_scheme), & + " Order: ", cfg%spatial_order + end if + end subroutine config_with_reconstruction + + ! ========== 新增:物理参数设置方法 ========== + + subroutine set_physics_parameters(this, equation_type, problem_type, & + domain_length, enable_physics) + class(cfd_config), intent(inout) :: this + character(len=*), intent(in), optional :: equation_type, problem_type + real(wp), intent(in), optional :: domain_length + logical, intent(in), optional :: enable_physics + + if (present(equation_type)) then + this%equation_type = trim(equation_type) + if (this%verbose) then + print *, "[CONFIG] Set equation type: ", trim(this%equation_type) + end if + end if + + if (present(problem_type)) then + this%problem_type = trim(problem_type) + if (this%verbose) then + print *, "[CONFIG] Set problem type: ", trim(this%problem_type) + end if + end if + + if (present(domain_length)) then + this%domain_length = domain_length + if (this%verbose) then + print *, "[CONFIG] Set domain length: ", this%domain_length + end if + end if + + if (present(enable_physics)) then + this%enable_physics = enable_physics + if (this%verbose) then + print *, "[CONFIG] Physics module enabled: ", this%enable_physics + end if + end if + end subroutine set_physics_parameters + + subroutine get_physics_info(this) + class(cfd_config), intent(in) :: this + + print *, "=== Physics Configuration Info ===" + print *, "Equation type: ", trim(this%equation_type) + print *, "Problem type: ", trim(this%problem_type) + print *, "Domain length: ", this%domain_length + print *, "Wave speed: ", this%wave_speed + print *, "Physics enabled: ", this%enable_physics + + if (this%ic_type == "gaussian") then + print *, "Pulse parameters:" + print *, " Center: ", this%pulse_center + print *, " Width: ", this%pulse_width + end if + + print *, "==================================" + end subroutine get_physics_info + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/infrastructure/domain.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/src/infrastructure/domain.f90 new file mode 100644 index 000000000..c3662f039 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/infrastructure/domain.f90 @@ -0,0 +1,102 @@ +! src/infrastructure/domain.f90 +module domain_module + use base_modules, only: wp, ip, max_name_len + use config_module, only: cfd_config + use mesh_module, only: mesh_type + + implicit none + private + public :: wp, ip, domain_type, domain_create, is_physical_cell + + type :: domain_type + type(cfd_config), pointer :: config => null() + type(mesh_type), pointer :: mesh => null() + integer(ip) :: nghosts = 0 + integer(ip) :: ist = 1 ! 物理区域起始索引(1-based) + integer(ip) :: ied = 1 ! 物理区域结束索引(exclusive) + integer(ip) :: ntcells = 0 ! 总单元数(含ghost) + contains + procedure :: print_info => domain_print_info + procedure :: get_physical_indices => domain_get_physical_indices + end type domain_type + +contains + + function domain_create(config, mesh) result(domain) + type(cfd_config), target, intent(in) :: config + type(mesh_type), target, intent(in) :: mesh + type(domain_type) :: domain + + domain%config => config + domain%mesh => mesh + + ! 计算ghost层数(参考Julia的_calc_nghosts) + domain%nghosts = calc_nghosts(config) + domain%ist = domain%nghosts + 1 + domain%ied = domain%ist + mesh%ncells + domain%ntcells = mesh%ncells + 2 * domain%nghosts + + if (config%verbose) then + print *, "[DOMAIN] Created:" + print *, " Ghost layers: ", domain%nghosts + print *, " Physical cells: ", domain%ist, " to ", domain%ied - 1 + print *, " Total cells: ", domain%ntcells + end if + end function domain_create + + function calc_nghosts(config) result(nghosts) + type(cfd_config), intent(in) :: config + integer(ip) :: nghosts + + character(len=max_name_len) :: scheme + + scheme = config%recon_scheme + + if (scheme == "eno") then + nghosts = config%spatial_order + else if (index(scheme, "weno") > 0) then + nghosts = config%spatial_order / 2 + 1 + else + print *, "[WARNING] Unknown scheme, using default nghosts=2" + nghosts = 2 + end if + + if (nghosts <= 0) then + print *, "[ERROR] Invalid nghosts: ", nghosts + nghosts = 2 + end if + end function calc_nghosts + + logical function is_physical_cell(this, idx) + class(domain_type), intent(in) :: this + integer(ip), intent(in) :: idx + is_physical_cell = (idx >= this%ist .and. idx < this%ied) + end function is_physical_cell + + function domain_get_physical_indices(this) result(indices) + class(domain_type), intent(in) :: this + integer(ip), allocatable :: indices(:) + integer(ip) :: i, count + + count = this%ied - this%ist + allocate(indices(count)) + + do i = 1, count + indices(i) = this%ist + i - 1 + end do + end function domain_get_physical_indices + + subroutine domain_print_info(this) + class(domain_type), intent(in) :: this + + print *, "=== Domain Information ===" + print *, "Configuration: ", trim(this%config%recon_scheme), & + " order ", this%config%spatial_order + print *, "Ghost layers: ", this%nghosts + print *, "Physical cells: ", this%ist, " to ", this%ied - 1 + print *, "Total cells: ", this%ntcells + print *, "Mesh cells: ", this%mesh%ncells + print *, "==========================" + end subroutine domain_print_info + +end module domain_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..f810f3a1b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/infrastructure/mesh.f90 @@ -0,0 +1,73 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use base_modules, only: wp, ip + + implicit none + public :: wp, ip, mesh_type, mesh_init, mesh_print_info + + ! 网格类型 + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer(ip) :: ncells = 40 + integer(ip) :: nnodes + integer(ip) :: nx + real(wp), allocatable :: x(:) ! 节点坐标 + real(wp), allocatable :: xcc(:) ! 单元中心坐标 + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer(ip), optional, intent(in) :: ncells + + integer(ip) :: i + + ! 设置参数 + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! 计算 + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! 分配内存 + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! 生成节点坐标 + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! 生成单元中心坐标 + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== Mesh Information ===" + print *, "Domain: [", this%xmin, ", ", this%xmax, "]" + print *, "Cells: ", this%ncells + print *, "Nodes: ", this%nnodes + print *, "dx: ", this%dx + print *, "L: ", this%L + print *, "========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/infrastructure/solution.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/src/infrastructure/solution.f90 new file mode 100644 index 000000000..ce88fd8a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/infrastructure/solution.f90 @@ -0,0 +1,131 @@ +! src/infrastructure/solution.f90 +module solution_module + use base_modules, only: wp, ip + use domain_module, only: domain_type + + implicit none + private + public :: wp, ip, solution_type, solution_create, solution_reset + + type :: solution_type + type(domain_type), pointer :: domain => null() + real(wp), allocatable :: u(:) ! 当前解(含ghost) + real(wp), allocatable :: un(:) ! 旧解 + real(wp), allocatable :: q_face_left(:) ! 左界面值 + real(wp), allocatable :: q_face_right(:)! 右界面值 + real(wp), allocatable :: flux(:) ! 通量 + real(wp), allocatable :: res(:) ! 残差 + contains + procedure :: initialize => solution_initialize + procedure :: update_old_field => solution_update_old_field + procedure :: print_info => solution_print_info + procedure :: reset => solution_reset_instance + end type solution_type + +contains + + function solution_create(domain) result(solution) + type(domain_type), target, intent(in) :: domain + type(solution_type) :: solution + + integer(ip) :: ncells, nnodes, ntcells + + solution%domain => domain + + ncells = domain%mesh%ncells + nnodes = domain%mesh%nnodes + ntcells = domain%ntcells + + ! 分配数组(与Julia solution.jl一致) + allocate(solution%u(ntcells), source=0.0_wp) + allocate(solution%un(ntcells), source=0.0_wp) + allocate(solution%q_face_left(nnodes), source=0.0_wp) + allocate(solution%q_face_right(nnodes), source=0.0_wp) + allocate(solution%flux(nnodes), source=0.0_wp) + allocate(solution%res(ncells), source=0.0_wp) + + if (domain%config%verbose) then + print *, "[SOLUTION] Created:" + print *, " u size: ", size(solution%u), " (with ghosts)" + print *, " flux size: ", size(solution%flux) + print *, " res size: ", size(solution%res) + end if + end function solution_create + + subroutine solution_initialize(this, initial_values) + class(solution_type), intent(inout) :: this + real(wp), intent(in), optional :: initial_values(:) + + integer(ip) :: i, idx + type(domain_type), pointer :: domain + + domain => this%domain + + if (present(initial_values)) then + ! 应用初始值到物理区域 + do i = domain%ist, domain%ied - 1 + idx = i - domain%ist + 1 + if (idx <= size(initial_values)) then + this%u(i) = initial_values(idx) + end if + end do + else + ! 默认为0 + this%u = 0.0_wp + end if + + ! 同步旧场(与Julia的update_old_field一致) + call this%update_old_field() + + if (domain%config%verbose) then + print *, "[SOLUTION] Initialized" + print *, " u range: ", minval(this%u), " to ", maxval(this%u) + end if + end subroutine solution_initialize + + subroutine solution_update_old_field(this) + class(solution_type), intent(inout) :: this + this%un = this%u ! 与Julia的 un .= u 一致 + end subroutine solution_update_old_field + + subroutine solution_reset_instance(this) + class(solution_type), intent(inout) :: this + call solution_reset(this) + end subroutine solution_reset_instance + + subroutine solution_reset(solution) + type(solution_type), intent(inout) :: solution + + if (allocated(solution%u)) solution%u = 0.0_wp + if (allocated(solution%un)) solution%un = 0.0_wp + if (allocated(solution%q_face_left)) solution%q_face_left = 0.0_wp + if (allocated(solution%q_face_right)) solution%q_face_right = 0.0_wp + if (allocated(solution%flux)) solution%flux = 0.0_wp + if (allocated(solution%res)) solution%res = 0.0_wp + + if (associated(solution%domain) .and. solution%domain%config%verbose) then + print *, "[SOLUTION] Reset" + end if + end subroutine solution_reset + + subroutine solution_print_info(this) + class(solution_type), intent(in) :: this + + print *, "=== Solution Information ===" + print *, "Arrays:" + print *, " u: ", size(this%u), " elements" + print *, " un: ", size(this%un), " elements" + print *, " q_face_left: ", size(this%q_face_left), " elements" + print *, " q_face_right: ", size(this%q_face_right), " elements" + print *, " flux: ", size(this%flux), " elements" + print *, " res: ", size(this%res), " elements" + + if (allocated(this%u)) then + print *, "Values:" + print *, " u min/max: ", minval(this%u), maxval(this%u) + print *, " un min/max: ", minval(this%un), maxval(this%un) + end if + print *, "============================" + end subroutine solution_print_info + +end module solution_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/manager/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03/src/manager/CMakeLists.txt new file mode 100644 index 000000000..00c8bf49b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/manager/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/manager/CMakeLists.txt +message(STATUS "配置管理器模块...") + +# 创建管理器库 +add_library(manager STATIC + component_manager.f90 + component_factory.f90 +) + +# 明确依赖关系:管理器依赖所有其他模块 +target_link_libraries(manager + PRIVATE + core + infrastructure + reconstructor + flux +) + +# 设置模块输出目录 +set_target_properties(manager PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "管理器模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/manager/component_factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/src/manager/component_factory.f90 new file mode 100644 index 000000000..114fedea5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/manager/component_factory.f90 @@ -0,0 +1,127 @@ +! src/manager/component_factory.f90 +module component_factory_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use config_module, only: cfd_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + use eno_reconstructor_module, only: eno_reconstructor, create_eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor, create_weno3_reconstructor + use rusanov_flux_module, only: rusanov_flux, create_rusanov_flux + + implicit none + private + public :: wp, create_reconstructor, create_flux_calculator + + ! 错误代码 + integer, parameter :: CM_SUCCESS = 0 + integer, parameter :: CM_ERROR_UNKNOWN_SCHEME = 1 + integer, parameter :: CM_ERROR_UNKNOWN_FLUX = 2 + integer, parameter :: CM_ERROR_INVALID_ORDER = 3 + +contains + + ! ==================== 重构器创建 ==================== + + function create_reconstructor(config, status) result(recon) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(reconstructor_base), allocatable :: recon + + character(len=20) :: scheme + integer :: order, error_code + + scheme = trim(adjustl(config%recon_scheme)) + order = config%spatial_order + + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating reconstructor: ", scheme, " order=", order + end if + + select case(scheme) + case('eno') + allocate(eno_reconstructor :: recon) + select type(recon) + type is(eno_reconstructor) + recon = create_eno_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case('weno3') + allocate(weno3_reconstructor :: recon) + select type(recon) + type is(weno3_reconstructor) + recon = create_weno3_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case default + error_code = CM_ERROR_UNKNOWN_SCHEME + if (config%verbose) then + print *, "[ERROR] Unknown reconstructor scheme: ", scheme + print *, " Available: eno, weno3" + end if + end select + + ! 检查阶数有效性 + if (error_code == CM_SUCCESS) then + if (order < 1) then + error_code = CM_ERROR_INVALID_ORDER + if (config%verbose) then + print *, "[ERROR] Invalid spatial order: ", order + end if + end if + end if + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Reconstructor creation failed" + end if + end function create_reconstructor + + ! ==================== 通量计算器创建 ==================== + + function create_flux_calculator(config, status) result(flux) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(flux_calculator_base), allocatable :: flux + + character(len=20) :: flux_type + integer :: error_code + + flux_type = trim(adjustl(config%flux_type)) + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating flux calculator: ", flux_type + end if + + select case(flux_type) + case('rusanov') + allocate(rusanov_flux :: flux) + select type(flux) + type is(rusanov_flux) + flux = create_rusanov_flux() + flux%wave_speed_default = config%wave_speed + end select + + case default + error_code = CM_ERROR_UNKNOWN_FLUX + if (config%verbose) then + print *, "[ERROR] Unknown flux type: ", flux_type + print *, " Available: rusanov" + end if + end select + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Flux calculator creation failed" + end if + end function create_flux_calculator + +end module component_factory_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/manager/component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/src/manager/component_manager.f90 new file mode 100644 index 000000000..9e095c257 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/manager/component_manager.f90 @@ -0,0 +1,75 @@ +! src/manager/component_manager.f90 +module component_manager_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use config_module, only: cfd_config + use component_factory_module, only: create_reconstructor, create_flux_calculator + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + + implicit none + private + public :: wp, component_manager_info, validate_config + public :: create_reconstructor, create_flux_calculator + +contains + + ! ==================== 配置验证 ==================== + + function validate_config(config) result(is_valid) + type(cfd_config), intent(in) :: config + logical :: is_valid + + integer :: status + class(reconstructor_base), allocatable :: test_recon + class(flux_calculator_base), allocatable :: test_flux + + is_valid = .false. + + ! 测试创建重构器 + test_recon = create_reconstructor(config, status) + if (status /= 0) then + if (config%verbose) then + print *, "[CONFIG VALIDATION] Invalid reconstructor configuration" + end if + return + end if + + ! 测试创建通量计算器 + test_flux = create_flux_calculator(config, status) + if (status /= 0) then + if (config%verbose) then + print *, "[CONFIG VALIDATION] Invalid flux configuration" + end if + return + end if + + ! 清理测试组件 + if (allocated(test_recon)) deallocate(test_recon) + if (allocated(test_flux)) deallocate(test_flux) + + is_valid = .true. + + if (config%verbose) then + print *, "[CONFIG VALIDATION] Configuration is valid" + end if + end function validate_config + + ! ==================== 信息显示 ==================== + + subroutine component_manager_info() + print *, "=== Component Manager ===" + print *, "Available reconstructors:" + print *, " - eno (orders: 1-7)" + print *, " - weno3 (order: 3)" + print *, "" + print *, "Available flux calculators:" + print *, " - rusanov" + print *, "" + print *, "Features:" + print *, " - Configuration validation" + print *, " - Component creation from config" + print *, " - Error handling with status codes" + print *, "=========================" + end subroutine component_manager_info + +end module component_manager_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..66874a7ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/CMakeLists.txt @@ -0,0 +1,7 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..3fde7746b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,28 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +# Start with just the base module +add_library(flux STATIC + base.f90 + rusanov.f90 +) + +#target_link_libraries(flux PRIVATE core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 明确设置不使用 /Qm64 选项 +if(CMAKE_Fortran_COMPILER_ID MATCHES "IntelLLVM|Intel") + set_target_properties(flux PROPERTIES + Fortran_FLAGS "${CMAKE_Fortran_FLAGS} /Qm64" # 明确设置,避免继承问题 + ) +endif() + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/flux/base.f90 new file mode 100644 index 000000000..7080a7ae4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/flux/base.f90 @@ -0,0 +1,30 @@ +!src/numerics/flux/base.f90 +module flux_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, flux_calculator_base + + ! Base flux calculator type + type :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure :: info => flux_info + procedure :: print_basic_info => flux_print_basic ! 添加辅助方法 + end type flux_calculator_base + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "Flux calculator information:" + print *, " Name: ", trim(this%name) + end subroutine flux_info + + subroutine flux_print_basic(this) + class(flux_calculator_base), intent(in) :: this + print *, " Name: ", trim(this%name) + end subroutine flux_print_basic + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/flux/rusanov.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/flux/rusanov.f90 new file mode 100644 index 000000000..daa9e3bb5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/flux/rusanov.f90 @@ -0,0 +1,41 @@ +! src/numerics/flux/rusanov.f90 +module rusanov_flux_module + use, intrinsic :: iso_fortran_env, only: real64 + use flux_base_module, only: flux_calculator_base + implicit none + + private + public :: real64, rusanov_flux, create_rusanov_flux + + type, extends(flux_calculator_base) :: rusanov_flux + real(real64) :: wave_speed_default = 1.0_real64 + contains + procedure :: info => rusanov_info + end type rusanov_flux + + ! 构造函数接口 + interface rusanov_flux + module procedure create_rusanov_flux + end interface + +contains + + ! 构造函数 + type(rusanov_flux) function create_rusanov_flux() result(this) + this%name = "Rusanov" + this%wave_speed_default = 1.0_real64 + end function create_rusanov_flux + + subroutine rusanov_info(this) + class(rusanov_flux), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Flux calculator information:" + call this%print_basic_info() + + ! 添加Rusanov特有信息 + print *, " Type: Rusanov flux" + print *, " Default wave speed: ", this%wave_speed_default + end subroutine rusanov_info + +end module rusanov_flux_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..5e4b938d5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,22 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "Configuring reconstructor module...") + +# Start with just the base module +add_library(reconstructor STATIC + base.f90 + eno.f90 + weno3.f90 +) + +target_link_libraries(reconstructor PRIVATE core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Reconstructor module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..53798d021 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/reconstructor/base.f90 @@ -0,0 +1,33 @@ +!src/numerics/reconstructor/base.f90 +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, reconstructor_base + + ! Base reconstructor type + type :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + contains + procedure :: info => reconstructor_info + procedure :: print_basic_info => reconstructor_print_basic ! 添加一个辅助方法 + end type reconstructor_base + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + print *, "Reconstructor information:" + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_info + + subroutine reconstructor_print_basic(this) + class(reconstructor_base), intent(in) :: this + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_print_basic + +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/reconstructor/eno.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/reconstructor/eno.f90 new file mode 100644 index 000000000..f973e8b32 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/reconstructor/eno.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/eno.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, eno_reconstructor, create_eno_reconstructor ! ← 添加这个 + + type, extends(reconstructor_base) :: eno_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => eno_info + end type eno_reconstructor + + ! 构造函数接口 + interface eno_reconstructor + module procedure create_eno_reconstructor + end interface + +contains + + ! 构造函数 + type(eno_reconstructor) function create_eno_reconstructor() result(this) + this%name = "ENO" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_eno_reconstructor + + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加ENO特有信息 + print *, " Type: ENO reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/reconstructor/weno3.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/reconstructor/weno3.f90 new file mode 100644 index 000000000..d5b7a7477 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/numerics/reconstructor/weno3.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/weno3.f90 +module weno3_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno3_reconstructor, create_weno3_reconstructor + + type, extends(reconstructor_base) :: weno3_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno3_info + end type weno3_reconstructor + + ! 构造函数接口 + interface weno3_reconstructor + module procedure create_weno3_reconstructor + end interface + +contains + + ! 构造函数 + type(weno3_reconstructor) function create_weno3_reconstructor() result(this) + this%name = "WENO3" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_weno3_reconstructor + + subroutine weno3_info(this) + class(weno3_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加WENO3特有信息 + print *, " Type: WENO-3 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno3_info + +end module weno3_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/physics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03/src/physics/CMakeLists.txt new file mode 100644 index 000000000..cc4e233ab --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/physics/CMakeLists.txt @@ -0,0 +1,19 @@ +# src/physics/CMakeLists.txt +message(STATUS "配置物理模块...") + +# 创建物理模块库 +add_library(physics STATIC + physics_interface.f90 + equations/linear_convection.f90 + problems/linear_convection_problem.f90 +) + +# 链接依赖 +target_link_libraries(physics PRIVATE base) + +# 设置模块输出目录 +set_target_properties(physics PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "物理模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/physics/equations/linear_convection.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/src/physics/equations/linear_convection.f90 new file mode 100644 index 000000000..595a21107 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/physics/equations/linear_convection.f90 @@ -0,0 +1,83 @@ +! src/physics/equations/linear_convection.f90 +module linear_convection_equation + use precision_module, only: wp, ip + use physics_interface, only: physics_equation + implicit none + private + + public :: linear_convection_eq, create_linear_convection_eq + public :: linear_convection_flux, linear_convection_speed + + ! 具体方程类型 + type, extends(physics_equation) :: linear_convection_eq + real(wp) :: wave_speed = 1.0_wp + contains + procedure :: flux => lc_flux + procedure :: speed => lc_speed + end type linear_convection_eq + + ! 独立函数(供外部调用) + interface linear_convection_flux + module procedure :: lc_flux_func + end interface + + interface linear_convection_speed + module procedure :: lc_speed_func + end interface + +contains + + ! 构造函数 + function create_linear_convection_eq(wave_speed) result(eq) + real(wp), intent(in), optional :: wave_speed + type(linear_convection_eq) :: eq + + eq%name = "Linear Convection" + if (present(wave_speed)) then + eq%wave_speed = wave_speed + else + eq%wave_speed = 1.0_wp + end if + end function create_linear_convection_eq + + ! 方法实现 + pure function lc_flux(this, u) result(f) + class(linear_convection_eq), intent(in) :: this + real(wp), intent(in) :: u + real(wp) :: f + f = this%wave_speed * u + end function lc_flux + + pure function lc_speed(this) result(a) + class(linear_convection_eq), intent(in) :: this + real(wp) :: a + a = this%wave_speed + end function lc_speed + + ! 独立函数 + pure function lc_flux_func(u, wave_speed) result(f) + real(wp), intent(in) :: u + real(wp), intent(in), optional :: wave_speed + real(wp) :: f + real(wp) :: a + + if (present(wave_speed)) then + a = wave_speed + else + a = 1.0_wp + end if + f = a * u + end function lc_flux_func + + pure function lc_speed_func(wave_speed) result(a) + real(wp), intent(in), optional :: wave_speed + real(wp) :: a + + if (present(wave_speed)) then + a = wave_speed + else + a = 1.0_wp + end if + end function lc_speed_func + +end module linear_convection_equation \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/physics/physics_interface.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/src/physics/physics_interface.f90 new file mode 100644 index 000000000..55662e5c2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/physics/physics_interface.f90 @@ -0,0 +1,64 @@ +! src/physics/physics_interface.f90 +module physics_interface + use precision_module, only: wp, ip + implicit none + private + + ! 定义抽象基类型 + type, abstract :: physics_equation + character(len=:), allocatable :: name + contains + procedure(eq_flux_abs), deferred :: flux + procedure(eq_speed_abs), deferred :: speed + end type physics_equation + + type, abstract :: physics_problem + character(len=:), allocatable :: name + contains + procedure(prob_ic_abs), deferred :: initial_condition + procedure(prob_bc_abs), deferred :: boundary_condition + procedure(prob_exact_abs), deferred :: exact_solution + end type physics_problem + + ! 抽象接口定义 + abstract interface + pure function eq_flux_abs(this, u) result(f) + import :: physics_equation, wp + class(physics_equation), intent(in) :: this + real(wp), intent(in) :: u + real(wp) :: f + end function eq_flux_abs + + pure function eq_speed_abs(this) result(a) + import :: physics_equation, wp + class(physics_equation), intent(in) :: this + real(wp) :: a + end function eq_speed_abs + + subroutine prob_ic_abs(this, x, u) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(in) :: x(:) + real(wp), intent(out) :: u(:) + end subroutine prob_ic_abs + + subroutine prob_bc_abs(this, u, t) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(inout) :: u(:) + real(wp), intent(in), optional :: t + end subroutine prob_bc_abs + + function prob_exact_abs(this, x, t) result(u) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(in) :: x(:), t + real(wp), dimension(size(x)) :: u + end function prob_exact_abs + end interface + + ! 公开接口 + public :: physics_equation, physics_problem + public :: wp, ip + +end module physics_interface \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/physics/problems/linear_convection_problem.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/src/physics/problems/linear_convection_problem.f90 new file mode 100644 index 000000000..312107105 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/physics/problems/linear_convection_problem.f90 @@ -0,0 +1,116 @@ +! src/physics/problems/linear_convection_problem.f90 +module linear_convection_problem + use precision_module, only: wp, ip + use physics_interface, only: physics_problem + implicit none + private + + public :: linear_convection_prob, create_linear_convection_prob + + ! 具体问题类型 + type, extends(physics_problem) :: linear_convection_prob + real(wp) :: wave_speed = 1.0_wp + real(wp) :: domain_length = 2.0_wp + character(len=20) :: ic_type = "step" + character(len=20) :: boundary_type = "periodic" + contains + procedure :: initial_condition => lc_initial_condition + procedure :: boundary_condition => lc_boundary_condition + procedure :: exact_solution => lc_exact_solution + end type linear_convection_prob + +contains + + ! 构造函数 + function create_linear_convection_prob(wave_speed, domain_length, & + ic_type, boundary_type) result(prob) + real(wp), intent(in), optional :: wave_speed, domain_length + character(len=*), intent(in), optional :: ic_type, boundary_type + type(linear_convection_prob) :: prob + + prob%name = "Linear Convection Problem" + + if (present(wave_speed)) prob%wave_speed = wave_speed + if (present(domain_length)) prob%domain_length = domain_length + if (present(ic_type)) prob%ic_type = ic_type + if (present(boundary_type)) prob%boundary_type = boundary_type + end function create_linear_convection_prob + + ! 初始条件 + subroutine lc_initial_condition(this, x, u) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(in) :: x(:) + real(wp), intent(out) :: u(:) + + integer :: i + + select case (trim(this%ic_type)) + case ("step") + do i = 1, size(x) + if (x(i) >= 0.5_wp .and. x(i) <= 1.0_wp) then + u(i) = 2.0_wp + else + u(i) = 1.0_wp + end if + end do + + case ("sin", "sine") + do i = 1, size(x) + u(i) = sin(2.0_wp * 3.141592653589793_wp * x(i) / this%domain_length) + end do + + case ("gaussian") + do i = 1, size(x) + u(i) = exp(-((x(i) - 0.5_wp) / 0.1_wp)**2) + end do + + case default + ! 默认阶跃函数 + do i = 1, size(x) + if (x(i) >= 0.5_wp .and. x(i) <= 1.0_wp) then + u(i) = 2.0_wp + else + u(i) = 1.0_wp + end if + end do + end select + end subroutine lc_initial_condition + + ! 边界条件(虚拟实现,实际在boundary模块) + subroutine lc_boundary_condition(this, u, t) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(inout) :: u(:) + real(wp), intent(in), optional :: t + + ! 边界条件将在独立模块实现 + print *, "[PROBLEM] Boundary condition placeholder" + if (present(t)) then + print *, " Time = ", t + end if + end subroutine lc_boundary_condition + + ! 精确解(周期性平移) + function lc_exact_solution(this, x, t) result(u) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(in) :: x(:), t + real(wp), dimension(size(x)) :: u + real(wp), dimension(size(x)) :: x_shifted + integer :: i + + ! 周期性平移 + do i = 1, size(x) + x_shifted(i) = x(i) - this%wave_speed * t + ! 确保在 [0, domain_length) 范围内 + do while (x_shifted(i) < 0.0_wp) + x_shifted(i) = x_shifted(i) + this%domain_length + end do + do while (x_shifted(i) >= this%domain_length) + x_shifted(i) = x_shifted(i) - this%domain_length + end do + end do + + ! 重用初始条件函数 + call this%initial_condition(x_shifted, u) + end function lc_exact_solution + +end module linear_convection_problem \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/solver/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03/src/solver/CMakeLists.txt new file mode 100644 index 000000000..3f320bcae --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/solver/CMakeLists.txt @@ -0,0 +1,18 @@ +# src/solver/CMakeLists.txt +message(STATUS "配置求解器模块...") + +add_library(solver STATIC + base.f90 +) + +target_link_libraries(solver + PRIVATE + infrastructure + core +) + +set_target_properties(solver PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "求解器模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/src/solver/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/src/solver/base.f90 new file mode 100644 index 000000000..1881ba089 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/src/solver/base.f90 @@ -0,0 +1,258 @@ +! src/solver/base.f90 +module solver_base_module + use base_modules, only: wp => wp, ip => ip ! 重命名以避免冲突 + + use config_module, only: cfd_config + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create + + implicit none + private + + ! 明确导出列表 + public :: wp, ip ! 类型参数 + public :: solver_base, create_solver_base ! 类型和构造函数 + public :: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, SOLVER_RUNNING + public :: SOLVER_COMPLETED, SOLVER_ERROR ! 状态常量 + + ! 求解器状态枚举 + integer, parameter :: SOLVER_UNINITIALIZED = 0 + integer, parameter :: SOLVER_INITIALIZED = 1 + integer, parameter :: SOLVER_RUNNING = 2 + integer, parameter :: SOLVER_COMPLETED = 3 + integer, parameter :: SOLVER_ERROR = 4 + + ! 求解器基类 + type :: solver_base + ! 基本组件 + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + + ! 状态管理 + integer :: state = SOLVER_UNINITIALIZED + character(len=100) :: error_message = "" + real(wp) :: current_time = 0.0_wp + integer(ip) :: current_step = 0 + + ! 时间控制 + real(wp) :: dt_original = 0.0_wp + contains + procedure :: initialize => solver_base_initialize + procedure :: step => solver_base_step + procedure :: run_to_time => solver_base_run_to_time + procedure :: cleanup => solver_base_cleanup + procedure :: get_state => solver_base_get_state + procedure :: get_error => solver_base_get_error + procedure :: print_info => solver_base_print_info + end type solver_base + + ! 构造函数接口 + interface solver_base + module procedure create_solver_base + end interface + +contains + + ! ==================== 构造函数 ==================== + + function create_solver_base(config, mesh) result(solver) + type(cfd_config), intent(in) :: config + type(mesh_type), intent(in) :: mesh + type(solver_base) :: solver + + solver%config = config + solver%mesh = mesh + + ! 创建域 + solver%domain = domain_create(config, mesh) + + ! 创建解 + solver%solution = solution_create(solver%domain) + + ! 保存原始时间步长 + solver%dt_original = config%dt + + if (config%verbose) then + print *, "[SOLVER] Base solver created" + print *, " Mesh cells: ", mesh%ncells + print *, " Domain total cells: ", solver%domain%ntcells + end if + end function create_solver_base + + ! ==================== 初始化 ==================== + + subroutine solver_base_initialize(this) + class(solver_base), intent(inout) :: this + + if (this%state == SOLVER_INITIALIZED) then + if (this%config%verbose) then + print *, "[SOLVER] Already initialized" + end if + return + end if + + ! 初始化解(通过配置) + ! 这里暂时简化,实际需要调用初始条件工厂 + print *, "[INFO] Base solver initialized (simplified)" + + ! 更新状态 + this%state = SOLVER_INITIALIZED + this%current_time = 0.0_wp + this%current_step = 0 + + if (this%config%verbose) then + print *, "[SOLVER] Initialized at t = ", this%current_time + end if + end subroutine solver_base_initialize + + ! ==================== 单步计算(虚方法) ==================== + + subroutine solver_base_step(this, dt) + class(solver_base), intent(inout) :: this + real(wp), intent(in) :: dt + + ! 基类中这只是虚方法,需要在子类中实现 + print *, "[INFO] Base solver step (virtual method)" + print *, " dt = ", dt + print *, " t = ", this%current_time + + ! 更新时间 + this%current_time = this%current_time + dt + this%current_step = this%current_step + 1 + + ! 简单模拟:只是更新状态 + if (this%config%verbose) then + print *, "[SOLVER] Step completed: t = ", this%current_time, & + ", step = ", this%current_step + end if + end subroutine solver_base_step + + ! ==================== 运行到指定时间 ==================== + + subroutine solver_base_run_to_time(this, final_time) + class(solver_base), intent(inout) :: this + real(wp), intent(in) :: final_time + + real(wp) :: dt, t_remaining + integer :: step_count + + if (this%state /= SOLVER_INITIALIZED) then + this%error_message = "Solver not initialized" + this%state = SOLVER_ERROR + return + end if + + this%state = SOLVER_RUNNING + step_count = 0 + + if (this%config%verbose) then + print *, "[SOLVER] Running from t = ", this%current_time, & + " to t = ", final_time + end if + + do while (this%current_time < final_time) + ! 计算时间步长 + dt = min(this%config%dt, final_time - this%current_time) + + ! 执行时间步 + call this%step(dt) + + step_count = step_count + 1 + + ! 每10步输出一次进度 + if (mod(step_count, 10) == 0 .and. this%config%verbose) then + print *, "[SOLVER] Progress: t = ", this%current_time, & + " / ", final_time + end if + end do + + ! 恢复原始时间步长 + this%config%dt = this%dt_original + + ! 更新状态 + this%state = SOLVER_COMPLETED + + if (this%config%verbose) then + print *, "[SOLVER] Run completed:" + print *, " Final time: ", this%current_time + print *, " Total steps: ", this%current_step + end if + end subroutine solver_base_run_to_time + + ! ==================== 清理 ==================== + + subroutine solver_base_cleanup(this) + class(solver_base), intent(inout) :: this + + ! 重置状态 + this%state = SOLVER_UNINITIALIZED + this%current_time = 0.0_wp + this%current_step = 0 + this%error_message = "" + + if (this%config%verbose) then + print *, "[SOLVER] Cleaned up" + end if + end subroutine solver_base_cleanup + + ! ==================== 状态查询 ==================== + + function solver_base_get_state(this) result(state) + class(solver_base), intent(in) :: this + integer :: state + state = this%state + end function solver_base_get_state + + function solver_base_get_error(this) result(error_msg) + class(solver_base), intent(in) :: this + character(len=100) :: error_msg + error_msg = trim(this%error_message) + end function solver_base_get_error + + ! ==================== 信息打印 ==================== + + subroutine solver_base_print_info(this) + class(solver_base), intent(in) :: this + + character(len=20) :: state_str + + ! 状态字符串 + select case (this%state) + case (SOLVER_UNINITIALIZED) + state_str = "Uninitialized" + case (SOLVER_INITIALIZED) + state_str = "Initialized" + case (SOLVER_RUNNING) + state_str = "Running" + case (SOLVER_COMPLETED) + state_str = "Completed" + case (SOLVER_ERROR) + state_str = "Error" + case default + state_str = "Unknown" + end select + + print *, "=== Solver Information ===" + print *, "State: ", trim(state_str) + print *, "Current time: ", this%current_time + print *, "Current step: ", this%current_step + print *, "Error message: '", trim(this%error_message), "'" + + ! 配置信息 + print *, "Configuration:" + print *, " Scheme: ", trim(this%config%recon_scheme) + print *, " Order: ", this%config%spatial_order + print *, " dt: ", this%config%dt + + ! 域信息 + print *, "Domain:" + print *, " Ghost layers: ", this%domain%nghosts + print *, " Physical cells: ", this%domain%ist, " to ", this%domain%ied - 1 + + print *, "=========================" + end subroutine solver_base_print_info + +end module solver_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03/tests/CMakeLists.txt new file mode 100644 index 000000000..8dd2f902a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/tests/CMakeLists.txt @@ -0,0 +1,74 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +# +#message(STATUS "CMAKE_Fortran_MODULE_DIRECTORY=${CMAKE_Fortran_MODULE_DIRECTORY}") +# +add_executable(test_minimal_simple test_minimal_simple.f90) +target_link_libraries(test_minimal_simple + PRIVATE + core # 必须链接core库 + infrastructure +) + + +add_executable(test_simple_link test_simple_link.f90) +target_link_libraries(test_simple_link + PRIVATE + reconstructor + flux +) + + +add_executable(test_factory_simple test_factory_simple.f90) +target_link_libraries(test_factory_simple + PRIVATE + core + infrastructure + reconstructor + flux +) + + +add_executable(test_component_manager test_component_manager.f90) + +target_link_libraries(test_component_manager + PRIVATE + manager # ← 链接到新的管理器库 + infrastructure +) +add_executable(test_basic_only test_basic_only.f90) +target_link_libraries(test_basic_only + PRIVATE + infrastructure + core +) + +add_executable(test_physics_minimal test_physics_minimal.f90) +target_link_libraries(test_physics_minimal + PRIVATE + physics + base +) + +add_executable(test_domain_solution test_domain_solution.f90) +target_link_libraries(test_domain_solution + PRIVATE + infrastructure + core +) + +add_executable(test_config_physics test_config_physics.f90) +target_link_libraries(test_config_physics + PRIVATE + infrastructure + core +) + +add_executable(test_solver_base test_solver_base.f90) +target_link_libraries(test_solver_base + PRIVATE + solver + infrastructure + core +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_architecture.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_architecture.f90 new file mode 100644 index 000000000..1c40d4677 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_architecture.f90 @@ -0,0 +1,117 @@ +! tests/test_architecture.f90 +program test_architecture + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module + use config_module + use mesh_module + use component_manager_module + + implicit none + + print *, "=== CFD架构测试 ===" + print *, "" + + ! 测试1: 基本系统 + call test_basic_systems() + + ! 测试2: 组件注册 + call test_registry_integration() + + ! 测试3: 配置验证 + call test_config_validation() + + ! 测试4: 完整流程 + call test_full_workflow() + + print *, "" + print *, "=== 架构测试完成 ===" + +contains + + subroutine test_basic_systems() + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "1. 测试基本系统..." + print *, "-------------------" + + ! 测试配置 + call config_print(config) + print *, "✓ 配置创建成功" + + ! 测试网格 + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "✓ 网格创建成功" + print *, "" + end subroutine test_basic_systems + + subroutine test_registry_integration() + print *, "2. 测试注册系统集成..." + print *, "------------------------" + + call initialize_registry(verbose=.true.) + + ! 注册核心组件 + print *, "注册核心组件:" + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + ! 显示注册内容 + call component_registry%list_simple() + print *, "注册表大小: ", registry_get_size() + + call cleanup_registry() + print *, "✓ 注册系统测试完成" + print *, "" + end subroutine test_registry_integration + + subroutine test_config_validation() + type(cfd_config) :: config + logical :: is_valid + + print *, "3. 测试配置验证..." + print *, "-------------------" + + ! 测试有效配置 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + print *, "测试配置:" + print *, " - 重构器: ", trim(config%recon_scheme), " 阶数: ", config%spatial_order + print *, " - 通量: ", trim(config%flux_type) + print *, " - 波速: ", config%wave_speed + + ! 这里调用验证函数(需要先实现) + ! is_valid = validate_config(config) + print *, "✓ 配置验证接口准备就绪" + print *, "" + end subroutine test_config_validation + + subroutine test_full_workflow() + print *, "4. 测试完整流程..." + print *, "-------------------" + + print *, "步骤1: 初始化系统 ✓" + print *, "步骤2: 创建网格 ✓" + print *, "步骤3: 设置配置 ✓" + print *, "步骤4: 创建组件 (待实现)" + print *, "步骤5: 初始化解 (待实现)" + print *, "步骤6: 时间推进 (待实现)" + print *, "步骤7: 输出结果 (待实现)" + print *, "" + + print *, "✓ 流程框架定义完成" + end subroutine test_full_workflow + +end program test_architecture \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_basic_only.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_basic_only.f90 new file mode 100644 index 000000000..20901ddcc --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_basic_only.f90 @@ -0,0 +1,56 @@ +! tests/test_basic_only.f90 +program test_basic_only + ! 只测试最基本的功能,不依赖复杂模块 + use config_module, only: cfd_config, config_print, wp + use mesh_module, only: mesh_type + use registry_module, only: registry_init, registry_cleanup, & + register_component_simple, list_components + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "=== BASIC TEST - Minimal Functionality ===" + print *, "" + + ! 测试1: 配置 + print *, "1. Testing configuration..." + print *, "----------------------------" + call config_print(config) + print *, "" + + ! 测试2: 网格 + print *, "2. Testing mesh..." + print *, "------------------" + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=5) + print *, "Mesh initialized:" + print *, " Cells: ", mesh%ncells + print *, " Nodes: ", mesh%nnodes + print *, " dx: ", mesh%dx + print *, "" + + ! 测试3: 注册系统 + print *, "3. Testing registry..." + print *, "----------------------" + + call registry_init() + + ! 注册组件(使用简化版本) + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("flux", "rusanov") + + ! 列出组件 + call list_components() + print *, "" + + ! 清理 + call registry_cleanup() + + print *, "=== TEST PASSED ===" + print *, "✓ Configuration works" + print *, "✓ Mesh works" + print *, "✓ Registry works" + +end program test_basic_only \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_cfd_architecture.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_cfd_architecture.f90 new file mode 100644 index 000000000..1c40d4677 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_cfd_architecture.f90 @@ -0,0 +1,117 @@ +! tests/test_architecture.f90 +program test_architecture + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module + use config_module + use mesh_module + use component_manager_module + + implicit none + + print *, "=== CFD架构测试 ===" + print *, "" + + ! 测试1: 基本系统 + call test_basic_systems() + + ! 测试2: 组件注册 + call test_registry_integration() + + ! 测试3: 配置验证 + call test_config_validation() + + ! 测试4: 完整流程 + call test_full_workflow() + + print *, "" + print *, "=== 架构测试完成 ===" + +contains + + subroutine test_basic_systems() + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "1. 测试基本系统..." + print *, "-------------------" + + ! 测试配置 + call config_print(config) + print *, "✓ 配置创建成功" + + ! 测试网格 + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "✓ 网格创建成功" + print *, "" + end subroutine test_basic_systems + + subroutine test_registry_integration() + print *, "2. 测试注册系统集成..." + print *, "------------------------" + + call initialize_registry(verbose=.true.) + + ! 注册核心组件 + print *, "注册核心组件:" + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + ! 显示注册内容 + call component_registry%list_simple() + print *, "注册表大小: ", registry_get_size() + + call cleanup_registry() + print *, "✓ 注册系统测试完成" + print *, "" + end subroutine test_registry_integration + + subroutine test_config_validation() + type(cfd_config) :: config + logical :: is_valid + + print *, "3. 测试配置验证..." + print *, "-------------------" + + ! 测试有效配置 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + print *, "测试配置:" + print *, " - 重构器: ", trim(config%recon_scheme), " 阶数: ", config%spatial_order + print *, " - 通量: ", trim(config%flux_type) + print *, " - 波速: ", config%wave_speed + + ! 这里调用验证函数(需要先实现) + ! is_valid = validate_config(config) + print *, "✓ 配置验证接口准备就绪" + print *, "" + end subroutine test_config_validation + + subroutine test_full_workflow() + print *, "4. 测试完整流程..." + print *, "-------------------" + + print *, "步骤1: 初始化系统 ✓" + print *, "步骤2: 创建网格 ✓" + print *, "步骤3: 设置配置 ✓" + print *, "步骤4: 创建组件 (待实现)" + print *, "步骤5: 初始化解 (待实现)" + print *, "步骤6: 时间推进 (待实现)" + print *, "步骤7: 输出结果 (待实现)" + print *, "" + + print *, "✓ 流程框架定义完成" + end subroutine test_full_workflow + +end program test_architecture \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_component_manager.f90 new file mode 100644 index 000000000..f60c35056 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_component_manager.f90 @@ -0,0 +1,111 @@ +! tests/test_component_manager.f90 +program test_component_manager + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use component_manager_module, only: create_reconstructor, create_flux_calculator + use component_manager_module, only: component_manager_info, validate_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + implicit none + + type(cfd_config) :: config + class(reconstructor_base), allocatable :: recon + class(flux_calculator_base), allocatable :: flux + integer :: status + logical :: is_valid + + print *, "=== Component Manager Test ===" + print *, "" + + ! 显示组件管理器信息 + call component_manager_info() + print *, "" + + ! 测试1: 基本配置 + print *, "1. Testing basic ENO3 + Rusanov configuration..." + print *, "-----------------------------------------------" + + config%verbose = .true. + call config_print(config) + + ! 配置ENO3重构 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + call config_print(config) + print *, "" + + ! 验证配置 + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Configuration is valid" + else + print *, "[ERROR] Configuration is invalid" + end if + print *, "" + + ! 测试2: 创建组件 + print *, "2. Testing component creation..." + print *, "--------------------------------" + + ! 创建重构器(带状态检查) + recon = create_reconstructor(config, status) + if (status == 0) then + print *, "[OK] Reconstructor created successfully" + call recon%info() + else + print *, "[ERROR] Failed to create reconstructor, code:", status + end if + print *, "" + + ! 创建通量计算器 + flux = create_flux_calculator(config, status) + if (status == 0) then + print *, "[OK] Flux calculator created successfully" + call flux%info() + else + print *, "[ERROR] Failed to create flux calculator, code:", status + end if + print *, "" + + ! 测试3: WENO3重构测试 + print *, "3. Testing WENO3 configuration..." + print *, "---------------------------------" + + call config_with_reconstruction(config, "weno3", 3) + + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] WENO3 configuration is valid" + + ! 创建WENO3重构器 + recon = create_reconstructor(config) + call recon%info() + else + print *, "[ERROR] WENO3 configuration is invalid" + end if + print *, "" + + ! 测试4: 错误配置测试 + print *, "4. Testing invalid configuration..." + print *, "-----------------------------------" + + config%recon_scheme = "unknown_scheme" + config%flux_type = "unknown_flux" + + is_valid = validate_config(config) + if (.not. is_valid) then + print *, "[OK] Invalid configuration correctly rejected" + else + print *, "[ERROR] Invalid configuration should have been rejected" + end if + + ! 清理 + if (allocated(recon)) deallocate(recon) + if (allocated(flux)) deallocate(flux) + + print *, "" + print *, "=== Component manager test completed successfully ===" + +end program test_component_manager \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_config_physics.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_config_physics.f90 new file mode 100644 index 000000000..c6fef5c0b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_config_physics.f90 @@ -0,0 +1,141 @@ +! tests/test_config_physics.f90 (修复版) +program test_config_physics + use base_modules, only: wp + use config_module, only: cfd_config, config_print, config_with_reconstruction + + implicit none + + type(cfd_config) :: config + + print *, "=== Configuration Physics Test (Simplified) ===" + print *, "" + + ! 测试1: 默认配置 + print *, "1. Testing default configuration..." + print *, "-----------------------------------" + call config_print(config) + print *, "" + + ! 测试2: 验证基础物理字段 + print *, "2. Testing basic physics fields..." + print *, "----------------------------------" + + print *, "Verifying default physics fields:" + + if (trim(config%equation_type) == "linear_advection") then + print *, " ✓ Default equation type: linear_advection" + else + print *, " ✗ Unexpected equation type: ", trim(config%equation_type) + end if + + if (trim(config%problem_type) == "linear_advection") then + print *, " ✓ Default problem type: linear_advection" + else + print *, " ✗ Unexpected problem type: ", trim(config%problem_type) + end if + + if (abs(config%domain_length - 2.0_wp) < 1e-10_wp) then + print *, " ✓ Default domain length: 2.0" + else + print *, " ✗ Unexpected domain length: ", config%domain_length + end if + + if (config%enable_physics) then + print *, " ✓ Physics enabled by default" + else + print *, " ✗ Physics not enabled by default" + end if + + print *, "" + + ! 测试3: 使用类型绑定的方法(正确的方法名) + print *, "3. Testing type-bound procedures..." + print *, "--------------------------------------" + + call config%set_physics_parameters( & + equation_type="burgers_equation", & + problem_type="sod_shock_tube", & + domain_length=3.0_wp, & + enable_physics=.false.) + + print *, "After set_physics_parameters:" + print *, " Equation type: ", trim(config%equation_type) + print *, " Problem type: ", trim(config%problem_type) + print *, " Domain length: ", config%domain_length + print *, " Physics enabled: ", config%enable_physics + + if (trim(config%equation_type) == "burgers_equation") then + print *, " ✓ Equation type modified successfully via set_physics_parameters" + end if + + if (trim(config%problem_type) == "sod_shock_tube") then + print *, " ✓ Problem type modified successfully via set_physics_parameters" + end if + + if (abs(config%domain_length - 3.0_wp) < 1e-10_wp) then + print *, " ✓ Domain length modified successfully via set_physics_parameters" + end if + + if (.not. config%enable_physics) then + print *, " ✓ Physics disabled successfully via set_physics_parameters" + end if + + print *, "" + + ! 测试4: 调用get_physics_info方法 + print *, "4. Testing get_physics_info method..." + print *, "--------------------------------------" + call config%get_physics_info() + print *, "" + + ! 测试5: 高斯脉冲配置 + print *, "5. Testing Gaussian pulse configuration..." + print *, "-----------------------------------------" + + config%ic_type = "gaussian" + config%pulse_center = 0.6_wp + config%pulse_width = 0.15_wp + + print *, "Gaussian pulse parameters:" + print *, " IC type: ", trim(config%ic_type) + print *, " Center: ", config%pulse_center + print *, " Width: ", config%pulse_width + + if (trim(config%ic_type) == "gaussian") then + print *, " ✓ Gaussian IC type set" + end if + + if (abs(config%pulse_center - 0.6_wp) < 1e-10_wp) then + print *, " ✓ Pulse center set" + end if + + if (abs(config%pulse_width - 0.15_wp) < 1e-10_wp) then + print *, " ✓ Pulse width set" + end if + + print *, "" + + ! 测试6: 重构配置 + print *, "6. Testing reconstruction configuration..." + print *, "------------------------------------------" + + call config_with_reconstruction(config, "weno", 5) + + print *, "Reconstruction configuration:" + print *, " Scheme: ", trim(config%recon_scheme) + print *, " Order: ", config%spatial_order + + if (trim(config%recon_scheme) == "weno" .and. config%spatial_order == 5) then + print *, " ✓ WENO5 configuration successful" + else + print *, " ✗ Reconstruction configuration failed" + end if + + print *, "" + + print *, "=== Configuration Physics Test Complete ===" + print *, "✓ Config module updated with physics support" + print *, "✓ Fields can be directly accessed and modified" + print *, "✓ Type-bound procedures work correctly" + +end program test_config_physics \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_domain_solution.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_domain_solution.f90 new file mode 100644 index 000000000..ff659bac2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_domain_solution.f90 @@ -0,0 +1,102 @@ +! tests/test_domain_solution.f90 +program test_domain_solution + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create, solution_reset + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + real(wp), allocatable :: initial_values(:) + integer :: i + + print *, "=== Domain and Solution Test ===" + print *, "" + + ! 测试1: 不同重构方案的ghost层计算 + print *, "1. Testing ghost layer calculation..." + print *, "--------------------------------------" + + ! ENO3 + call config_with_reconstruction(config, "eno", 3) + config%verbose = .false. + call mesh%init(ncells=10) + domain = domain_create(config, mesh) + print *, "ENO3: nghosts = ", domain%nghosts, " (expected: 3)" + + ! WENO3 + call config_with_reconstruction(config, "weno3", 3) + domain = domain_create(config, mesh) + print *, "WENO3: nghosts = ", domain%nghosts, " (expected: 2)" + + ! WENO5 + call config_with_reconstruction(config, "weno", 5) + domain = domain_create(config, mesh) + print *, "WENO5: nghosts = ", domain%nghosts, " (expected: 3)" + print *, "" + + ! 测试2: Solution数组 + print *, "2. Testing solution arrays..." + print *, "------------------------------" + + call config_with_reconstruction(config, "eno", 3) + config%verbose = .true. + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=10) + + domain = domain_create(config, mesh) + call domain%print_info() + print *, "" + + solution = solution_create(domain) + call solution%print_info() + print *, "" + + ! 测试3: 初始化和更新 + print *, "3. Testing initialization and update..." + print *, "----------------------------------------" + + allocate(initial_values(mesh%ncells)) + do i = 1, mesh%ncells + initial_values(i) = sin(2.0_wp * 3.14159265358979_wp * mesh%xcc(i) / mesh%L) + end do + + call solution%initialize(initial_values) + print *, "After initialization:" + print *, " u range: ", minval(solution%u), " to ", maxval(solution%u) + print *, " un range: ", minval(solution%un), " to ", maxval(solution%un) + + ! 修改当前解,测试更新 + solution%u = solution%u * 2.0_wp + call solution%update_old_field() + print *, "After update: max|u - un| = ", maxval(abs(solution%u - solution%un)) + print *, "" + + ! 测试4: 重置 + print *, "4. Testing reset..." + print *, "-------------------" + + call solution_reset(solution) + print *, "After reset:" + print *, " u max: ", maxval(abs(solution%u)) + print *, " un max: ", maxval(abs(solution%un)) + print *, " flux max: ", maxval(abs(solution%flux)) + print *, "" + + deallocate(initial_values) + + print *, "=== Test Summary ===" + print *, "✓ Ghost layer calculation works" + print *, "✓ Domain creation works" + print *, "✓ Solution arrays work" + print *, "✓ Initialization works" + print *, "✓ Field update works" + print *, "✓ Reset works" + print *, "" + print *, "Ready for next step: Implementing Physics modules" + +end program test_domain_solution \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_factory_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_factory_simple.f90 new file mode 100644 index 000000000..db65da7cf --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_factory_simple.f90 @@ -0,0 +1,58 @@ +! tests/test_factory_simple.f90 (修复版) +program test_factory_simple + use base_modules, only: wp ! ← 添加这行 + use config_module, only: cfd_config, config_print + use mesh_module, only: mesh_type + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(eno_reconstructor) :: eno + type(weno3_reconstructor) :: weno3 + type(rusanov_flux) :: rusanov + + print *, "=== Factory Pattern Simple Test ===" + print *, "" + + ! Test 1: Basic systems + print *, "1. Testing basic systems..." + print *, "-----------------------------" + call config_print(config) + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 2: Creating reconstructors + print *, "2. Testing reconstructors..." + print *, "------------------------------" + + ! 创建并测试ENO重构器 + print *, "Creating ENO reconstructor..." + eno = eno_reconstructor() ! 使用构造函数 + call eno%info() ! 必须调用info方法 + + print *, "" + print *, "Creating WENO3 reconstructor..." + weno3 = weno3_reconstructor() ! 使用构造函数 + call weno3%info() ! 必须调用info方法 + print *, "" + + ! Test 3: Creating flux calculator + print *, "3. Testing flux calculator..." + print *, "-------------------------------" + + print *, "Creating Rusanov flux calculator..." + rusanov = rusanov_flux() ! 使用构造函数 + call rusanov%info() ! 必须调用info方法 + print *, "" + + print *, "=== Factory pattern simple test completed successfully ===" + +end program test_factory_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_minimal_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_minimal_simple.f90 new file mode 100644 index 000000000..ec03ccf89 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_minimal_simple.f90 @@ -0,0 +1,87 @@ +! tests/test_minimal_simple.f90 +program test_minimal_simple + use base_modules, only: wp ! ← 添加这行 + use registry_module + use config_module + use mesh_module + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Simple Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call registry_init() + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call list_components() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + + if (has_component_simple("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component_simple("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components (simple version) + print *, "5. Testing registry functionality" + print *, "---------------------------------" + print *, "Registry initialized: ", registry_is_initialized() + print *, "Number of components: ", registry_get_size() + print *, "" + + ! Cleanup + call registry_cleanup() + + print *, "=== Simple test completed successfully ===" + +end program test_minimal_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_physics_minimal.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_physics_minimal.f90 new file mode 100644 index 000000000..cf2a28f19 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_physics_minimal.f90 @@ -0,0 +1,91 @@ +! tests/test_physics_minimal.f90 +program test_physics_minimal + use precision_module, only: wp, ip + use linear_convection_equation, only: linear_convection_eq, create_linear_convection_eq + use linear_convection_problem, only: linear_convection_prob, create_linear_convection_prob + + implicit none + + type(linear_convection_eq) :: eq + type(linear_convection_prob) :: prob + real(wp) :: u, f, a + real(wp), allocatable :: x(:), u_ic(:), u_exact(:) + integer :: i, nx = 10 + + print *, "=== 最小物理模块测试 ===" + print *, "" + + ! 测试1: 方程功能 + print *, "1. 测试方程功能..." + print *, "-------------------" + + eq = create_linear_convection_eq(wave_speed=2.0_wp) + print *, "方程: ", eq%name + print *, "波速: ", eq%wave_speed + + u = 1.5_wp + f = eq%flux(u) + a = eq%speed() + + print *, "u = ", u + print *, "F(u) = ", f, " (期望: 3.0)" + print *, "波速 a = ", a, " (期望: 2.0)" + + if (abs(f - 3.0_wp) < 1e-10_wp .and. abs(a - 2.0_wp) < 1e-10_wp) then + print *, "✓ 方程功能正常" + else + print *, "✗ 方程功能异常" + end if + print *, "" + + ! 测试2: 问题功能 + print *, "2. 测试问题功能..." + print *, "-------------------" + + prob = create_linear_convection_prob(ic_type="step", domain_length=2.0_wp) + print *, "问题: ", prob%name + print *, "IC类型: ", trim(prob%ic_type) + print *, "域长度: ", prob%domain_length + + allocate(x(nx), u_ic(nx), u_exact(nx)) + do i = 1, nx + x(i) = 0.0_wp + (i-1) * 0.2_wp + end do + + ! 测试初始条件 + call prob%initial_condition(x, u_ic) + print *, "初始条件范围: ", minval(u_ic), " 到 ", maxval(u_ic) + + ! 测试精确解 + u_exact = prob%exact_solution(x, 0.0_wp) + print *, "t=0时精确解范围: ", minval(u_exact), " 到 ", maxval(u_exact) + + ! 检查阶跃函数 + if (abs(u_ic(1) - 1.0_wp) < 1e-10_wp .and. & + abs(u_ic(6) - 2.0_wp) < 1e-10_wp) then + print *, "✓ 阶跃初始条件正确" + else + print *, "✗ 阶跃初始条件错误" + end if + + ! 检查精确解与初始条件一致 + if (maxval(abs(u_ic - u_exact)) < 1e-10_wp) then + print *, "✓ t=0时精确解与初始条件一致" + else + print *, "✗ 精确解计算错误" + end if + print *, "" + + ! 测试3: 边界条件接口 + print *, "3. 测试边界条件接口..." + print *, "----------------------" + call prob%boundary_condition(u_ic, 0.0_wp) + print *, "✓ 边界条件接口正常" + print *, "" + + deallocate(x, u_ic, u_exact) + + print *, "=== 物理模块最小测试完成 ===" + print *, "下一步: 将物理模块集成到现有系统中" + +end program test_physics_minimal \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_simple_link.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_simple_link.f90 new file mode 100644 index 000000000..71cc614e8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_simple_link.f90 @@ -0,0 +1,78 @@ +! tests/test_simple_link.f90 +program test_simple_link + use base_modules, only: wp ! ← 添加这行 + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call registry_init() + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call list_components() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component_simple("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component_simple("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Cleanup + call registry_cleanup() + + print *, "=== Minimal test completed successfully ===" + +end program test_simple_link \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_solver_base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_solver_base.f90 new file mode 100644 index 000000000..6cfe47e41 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_solver_base.f90 @@ -0,0 +1,99 @@ +! tests/test_solver_base.f90 (修复版) +program test_solver_base + ! 所有 USE 语句必须在程序开始处 + use base_modules, only: wp + use config_module, only: cfd_config, config_print, config_with_reconstruction + use mesh_module, only: mesh_type + use solver_base_module, only: solver_base, SOLVER_UNINITIALIZED, & + SOLVER_INITIALIZED, SOLVER_COMPLETED, SOLVER_ERROR + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(solver_base) :: solver + integer :: state + + print *, "=== Solver Base Test ===" + print *, "" + + ! 测试1: 创建求解器 + print *, "1. Creating solver..." + print *, "----------------------" + + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%dt = 0.01_wp + + call config_print(config) + + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=10) + + solver = solver_base(config, mesh) + call solver%print_info() + print *, "" + + ! 测试2: 初始化 + print *, "2. Initializing solver..." + print *, "-------------------------" + + call solver%initialize() + state = solver%get_state() + print *, "State after initialization: ", state + print *, "Expected: ", SOLVER_INITIALIZED + print *, "Match? ", state == SOLVER_INITIALIZED + print *, "Error message: '", trim(solver%get_error()), "'" + print *, "" + + ! 测试3: 运行求解器 + print *, "3. Running solver..." + print *, "--------------------" + + call solver%run_to_time(0.05_wp) + state = solver%get_state() + print *, "State after run: ", state + print *, "Expected: ", SOLVER_COMPLETED + print *, "Match? ", state == SOLVER_COMPLETED + print *, "Current time: ", solver%current_time + print *, "Current step: ", solver%current_step + print *, "" + + ! 测试4: 再次运行(从已完成状态) + print *, "4. Running again from completed state..." + print *, "----------------------------------------" + + ! 需要先清理才能重新运行 + call solver%cleanup() + call solver%initialize() + call solver%run_to_time(0.1_wp) + + call solver%print_info() + print *, "" + + ! 测试5: 错误处理 + print *, "5. Testing error states..." + print *, "--------------------------" + + ! 创建一个未初始化的求解器 + call solver%cleanup() + state = solver%get_state() + print *, "Uninitialized state: ", state + print *, "Expected: ", SOLVER_UNINITIALIZED + print *, "Match? ", state == SOLVER_UNINITIALIZED + + ! 尝试运行未初始化的求解器 + call solver%run_to_time(0.01_wp) + state = solver%get_state() + print *, "State after error: ", state + print *, "Expected: ", SOLVER_ERROR + print *, "Match? ", state == SOLVER_ERROR + print *, "Error message: '", trim(solver%get_error()), "'" + print *, "" + + print *, "=== Solver Base Test Complete ===" + print *, "✓ Solver base class works" + print *, "✓ State management works" + print *, "✓ Time stepping framework works" + print *, "✓ Error handling works" + +end program test_solver_base \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_solver_framework.f90 b/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_solver_framework.f90 new file mode 100644 index 000000000..6754323d0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03/tests/test_solver_framework.f90 @@ -0,0 +1,91 @@ +! tests/test_solver_framework.f90 +program test_solver_framework + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use mesh_module, only: mesh_type + use solver_module, only: cfd_solver, solver_create, solver_run, solver_cleanup + use solver_module, only: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, & + SOLVER_COMPLETED, SOLVER_ERROR + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(cfd_solver) :: solver + + print *, "=== 求解器框架测试 ===" + print *, "" + + ! 测试1: 基本创建 + print *, "1. 测试求解器创建..." + print *, "----------------------" + + ! 创建配置 + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.0_real64 + config%dt = 0.01_real64 + + call config_print(config) + print *, "" + + ! 创建网格 + call mesh%init(xmin=0.0_real64, xmax=2.0_real64, ncells=20) + call mesh%print_info() + print *, "" + + ! 创建求解器 + solver = solver_create(config, mesh) + print *, "✓ 求解器创建成功" + print *, " 状态: ", solver%get_state() + print *, "" + + ! 测试2: 求解器初始化 + print *, "2. 测试求解器初始化..." + print *, "------------------------" + + call solver%initialize() + print *, "✓ 求解器初始化完成" + print *, " 状态: ", solver%get_state() + print *, " 错误信息: '", trim(solver%get_error()), "'" + print *, "" + + ! 测试3: 简单运行 + print *, "3. 测试求解器运行..." + print *, "----------------------" + + call solver_run(solver, 0.05_real64) ! 运行到0.05秒 + print *, "✓ 求解器运行完成" + print *, " 最终状态: ", solver%get_state() + print *, "" + + ! 测试4: 清理 + print *, "4. 测试求解器清理..." + print *, "----------------------" + + call solver_cleanup(solver) + print *, "✓ 求解器清理完成" + print *, " 状态: ", solver%get_state() + print *, "" + + ! 测试5: 错误处理 + print *, "5. 测试错误处理..." + print *, "-------------------" + + ! 尝试重复初始化 + call solver%initialize() + print *, " 重复初始化状态: ", solver%get_state() + print *, " 错误信息: '", trim(solver%get_error()), "'" + + call solver_cleanup(solver) + print *, "" + + print *, "=== 框架测试总结 ===" + print *, "✓ 求解器创建/初始化/运行/清理流程验证完成" + print *, "✓ 状态管理正常工作" + print *, "✓ 错误处理机制就绪" + print *, "" + print *, "下一步: 添加实际数值计算功能" + +end program test_solver_framework \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03a/CMakeLists.txt new file mode 100644 index 000000000..ef66d5848 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/README.md b/example/1d-linear-convection/weno3/fortran/registry/03a/README.md new file mode 100644 index 000000000..c4889757b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/README.md @@ -0,0 +1,221 @@ +# Fortran CFD Project + +基于工厂模式和注册系统的CFD求解器框架。 + +## 项目结构 + +``` +fortran_cfd/ +├── CMakeLists.txt # 根CMake配置 +├── scripts/ # 构建脚本 +│ ├── build.py # Python构建脚本(推荐) +│ ├── build.bat # Batch包装脚本 +│ ├── build.ps1 # PowerShell包装脚本 +│ └── requirements.txt # Python依赖 +├── src/ # 源代码 +│ ├── CMakeLists.txt +│ ├── core/ # 核心模块(注册系统、工厂接口) +│ ├── infrastructure/ # 基础设施(配置、网格) +│ └── numerics/ # 数值方法 +├── tests/ # 测试 +│ ├── CMakeLists.txt +│ ├── test_minimal_simple.f90 # 简单测试 +│ └── test_factory.f90 # 工厂模式测试 +└── README.md # 项目说明(本文件) +``` + +## 快速开始 + +### 使用Python脚本(推荐) + +```bash +# 进入脚本目录 +cd scripts + +# 基本构建 +python build.py + +# 清理并构建 +python build.py --clean + +# 发布构建 +python build.py --build-type Release --clean + +# 并行构建(使用所有核心) +python build.py -j + +# 不运行测试 +python build.py --no-tests + +# 查看帮助 +python build.py --help +``` + +### 使用批处理脚本 + +```bash +cd scripts +.\build.bat +``` + +### 使用PowerShell脚本 + +```powershell +cd scripts +.\build.ps1 +``` + +## 手动构建 + +```bash +# 设置Intel环境 +cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && powershell' + +# 配置项目 +mkdir build +cd build +cmake -G "Visual Studio 17 2022" -A x64 -T fortran=ifx .. + +# 构建项目 +cmake --build . --config Debug + +# 运行测试 +Debug\test_simple.exe +Debug\test_factory.exe +``` + +## 功能特性 + +✅ 组件注册系统 +✅ 工厂模式 +✅ ENO/WENO重构器 +✅ Rusanov通量计算器 +✅ 可扩展架构 +✅ 自动化测试 + +## 依赖 + +- Intel oneAPI(包含ifx编译器) +- CMake 3.12+ +- Python 3.6+(仅用于构建脚本) + +## 源代码文件 + +### 核心模块 +- `src/core/registry.f90` - 组件注册系统 +- `src/core/factory_interfaces.f90` - 工厂接口 + +### 基础设施 +- `src/infrastructure/config.f90` - 配置管理 +- `src/infrastructure/mesh.f90` - 网格生成 + +### 数值方法 +- `src/numerics/reconstructor/base.f90` - 重构器基类 +- `src/numerics/reconstructor/eno.f90` - ENO重构器 +- `src/numerics/reconstructor/weno3.f90` - WENO-3重构器 +- `src/numerics/flux/base.f90` - 通量计算器基类 +- `src/numerics/flux/rusanov.f90` - Rusanov通量 + +### 测试 +- `tests/test_minimal_simple.f90` - 简单功能测试 +- `tests/test_factory.f90` - 工厂模式测试 + +## 构建脚本 + +### Python脚本 (build.py) +主构建脚本,支持: +- 自动设置Intel oneAPI环境 +- 参数化构建选项 +- 并行编译 +- 自动化测试 +- 彩色输出 + +### Batch脚本 (build.bat) +Windows批处理包装器,调用Python脚本。 + +### PowerShell脚本 (build.ps1) +PowerShell包装器,调用Python脚本。 + +## 示例输出 + +成功构建后,会看到类似输出: + +``` +============================================================ + Fortran CFD Project Builder +============================================================ + +[1/4] Setting up Intel Fortran compiler... +✓ Intel oneAPI environment configured + +[2/4] Preparing build directory... +✓ Cleaned build directory + +[3/4] Configuring project... + $ cmake -G Visual Studio 17 2022 -A x64 -T fortran=ifx -DCMAKE_BUILD_TYPE=Debug .. +✓ CMake configuration successful + +[4/4] Building project... + $ cmake --build . --config Debug +✓ Build completed in 12.3 seconds + +============================================================ + Running Tests +============================================================ + +[TEST] Simple functionality test... +✓ Simple test passed + +[TEST] Factory pattern test... +✓ Factory test passed + +============================================================ + Build Summary +============================================================ +✓ Build directory: D:\path\to\build +✓ Build type: Debug +✓ Total time: 15.2s +``` + +## 开发指南 + +### 添加新组件 + +1. 在相应模块中创建Fortran源文件 +2. 实现工厂函数 +3. 使用`register_component_with_factory`注册组件 +4. 添加测试 + +### 扩展架构 + +项目采用模块化设计: +1. **注册系统** - 管理所有可用的组件 +2. **工厂模式** - 动态创建组件实例 +3. **抽象接口** - 确保组件兼容性 +4. **配置驱动** - 通过配置文件控制行为 + +## 故障排除 + +### 常见问题 + +1. **Intel环境未找到** + - 检查Intel oneAPI安装路径 + - 手动运行`setvars.bat` + +2. **CMake配置失败** + - 确保使用正确的生成器:`Visual Studio 17 2022` + - 检查Fortran编译器是否可用 + +3. **构建失败** + - 检查依赖项是否完整 + - 查看详细的错误信息 + +### 调试建议 + +- 使用`--verbose`参数获取详细输出 +- 检查`build/CMakeCache.txt`了解配置详情 +- 查看编译器错误日志 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/scripts/build.bat b/example/1d-linear-convection/weno3/fortran/registry/03a/scripts/build.bat new file mode 100644 index 000000000..6fd6dc031 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/scripts/build.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Project Builder +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python build script with full Intel environment support... +echo. + +python build.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Build failed + pause + exit /b 1 +) + +echo. +echo [INFO] Build completed successfully! +echo. +echo [INFO] To run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/scripts/build.py b/example/1d-linear-convection/weno3/fortran/registry/03a/scripts/build.py new file mode 100644 index 000000000..3bf6d5375 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/scripts/build.py @@ -0,0 +1,629 @@ +#!/usr/bin/env python3 +""" +Fortran CFD Project Builder - 完整Python解决方案 +在Python内部处理Intel oneAPI环境配置 +""" + +import os +import sys +import subprocess +import shutil +import argparse +import time +import platform +import tempfile +from pathlib import Path + +class IntelEnvironment: + """Intel oneAPI环境管理器""" + + def __init__(self): + self.setvars_path = None + self.env_vars = {} + + def find_setvars(self): + """查找setvars.bat文件""" + possible_paths = [ + r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat", + r"C:\Program Files\Intel\oneAPI\setvars.bat", + r"C:\Program Files (x86)\Intel\oneAPI\compiler\latest\env\vars.bat", + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\setvars.bat"), + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\compiler\latest\env\vars.bat"), + ] + + for path in possible_paths: + if os.path.exists(path): + self.setvars_path = path + return True + + return False + + def setup_environment(self): + """设置Intel环境""" + if not self.find_setvars(): + return False + + try: + # 创建临时的批处理文件来捕获环境变量 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + f.write(f'@echo off\n') + f.write(f'call "{self.setvars_path}" >nul 2>&1\n') + f.write(f'set\n') # 输出所有环境变量 + temp_bat = f.name + + # 运行批处理文件并捕获输出 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + shell=True + ) + + # 解析环境变量 + for line in result.stdout.split('\n'): + line = line.strip() + if '=' in line: + key, value = line.split('=', 1) + self.env_vars[key.strip()] = value.strip() + + # 清理临时文件 + os.unlink(temp_bat) + + # 更新当前进程的环境变量 + os.environ.update(self.env_vars) + + return True + + except Exception as e: + print(f"设置Intel环境失败: {e}") + return False + + def get_compiler_info(self): + """获取编译器信息""" + info = {} + + # 检查ifx编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + env={**os.environ, **self.env_vars} if self.env_vars else os.environ + ) + + if result.returncode == 0: + for line in result.stdout.split('\n'): + if 'Version' in line or '版本' in line: + info['ifx_version'] = line.strip() + break + except: + pass + + # 检查环境变量 + info['ifx_root'] = self.env_vars.get('IFX_ROOT', '') + info['compiler_root'] = self.env_vars.get('ONEAPI_ROOT', '') + + return info + +class BuildSystem: + """构建系统主类""" + + def __init__(self): + self.project_root = Path(__file__).parent.parent + self.build_dir = self.project_root / "build" + self.intel_env = IntelEnvironment() + + # 设置控制台编码 + if sys.platform == "win32": + try: + import ctypes + # 设置控制台输出为UTF-8 + ctypes.windll.kernel32.SetConsoleOutputCP(65001) + except: + pass + + def print_header(self, text): + """打印标题""" + print(f"\n{'='*70}") + print(f" {text}") + print(f"{'='*70}\n") + + def print_step(self, step, total, message): + """打印步骤""" + print(f"[{step}/{total}] {message}...") + + def print_success(self, message): + """打印成功""" + print(f"\033[92m✓ {message}\033[0m") + + def print_error(self, message): + """打印错误""" + print(f"\033[91m✗ {message}\033[0m") + + def print_warning(self, message): + """打印警告""" + print(f"\033[93m! {message}\033[0m") + + def print_info(self, message): + """打印信息""" + print(f"\033[94mℹ {message}\033[0m") + + def check_prerequisites(self): + """检查前提条件""" + self.print_step(1, 6, "检查前提条件") + + # 检查Python版本 + python_version = sys.version.split()[0] + self.print_info(f"Python版本: {python_version}") + + # 检查平台 + self.print_info(f"平台: {platform.system()} {platform.release()}") + self.print_info(f"处理器核心数: {os.cpu_count()}") + + # 检查CMake + try: + result = subprocess.run( + ["cmake", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + version_line = result.stdout.split('\n')[0] + self.print_success(f"CMake: {version_line}") + else: + self.print_error("CMake未找到") + return False + except FileNotFoundError: + self.print_error("CMake未安装") + return False + + return True + + def setup_intel_environment(self, args): + """设置Intel环境""" + self.print_step(2, 6, "配置Intel oneAPI环境") + + if not self.intel_env.find_setvars(): + self.print_warning("未找到Intel oneAPI setvars.bat") + self.print_info("将尝试使用系统环境中的编译器") + + # 检查是否能直接访问编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + self.print_success("Intel编译器在系统PATH中找到") + return True + else: + self.print_warning("Intel编译器未在PATH中找到") + except: + self.print_warning("无法访问Intel编译器") + + return True # 继续,让CMake自己找编译器 + + # 设置环境 + if self.intel_env.setup_environment(): + compiler_info = self.intel_env.get_compiler_info() + + if compiler_info.get('ifx_version'): + self.print_success(f"Intel Fortran编译器: {compiler_info['ifx_version']}") + elif compiler_info.get('ifx_root'): + self.print_success(f"Intel编译器路径: {compiler_info['ifx_root']}") + else: + self.print_success("Intel oneAPI环境配置完成") + + return True + else: + self.print_warning("Intel环境配置失败,将继续使用系统环境") + return True + + def clean_build_directory(self, args): + """清理构建目录""" + if args.clean and self.build_dir.exists(): + self.print_info("清理构建目录...") + try: + shutil.rmtree(self.build_dir) + self.print_success("构建目录已清理") + except Exception as e: + self.print_error(f"清理失败: {e}") + if not args.force: + return False + return True + + def run_command(self, cmd, cwd=None, check=True, env=None): + """运行命令""" + if isinstance(cmd, list): + cmd_str = ' '.join(str(c) for c in cmd if c) + else: + cmd_str = str(cmd) + + print(f" \033[96m$\033[0m {cmd_str}") + + try: + # 合并环境变量 + exec_env = os.environ.copy() + if env: + exec_env.update(env) + if self.intel_env.env_vars: + exec_env.update(self.intel_env.env_vars) + + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=False, + env=exec_env + ) + + # 处理输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or '完成' in line or '生成' in line: + print(f" \033[92m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + if check and result.returncode != 0: + self.print_error(f"命令执行失败,退出码: {result.returncode}") + return False + + return True + + except Exception as e: + self.print_error(f"命令执行异常: {e}") + return False + + def configure_cmake(self, args): + """配置CMake""" + self.print_step(3, 6, "配置CMake项目") + + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + f"-DCMAKE_BUILD_TYPE={args.build_type}", + ] + + if args.compiler == "ifx": + cmake_cmd.extend(["-T", "fortran=ifx"]) + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + success = self.run_command(cmake_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("CMake配置完成") + else: + self.print_error("CMake配置失败") + + return success + + def build_project(self, args): + """构建项目""" + self.print_step(4, 6, "构建项目") + + build_cmd = [ + "cmake", + "--build", ".", + "--config", args.build_type, + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + success = self.run_command(build_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("项目构建完成") + else: + self.print_error("构建失败") + + return success + + def run_tests_with_environment(self, test_exe): + """运行单个测试,确保有Intel环境""" + try: + # 创建临时的批处理文件来运行测试 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'"{test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'"{test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + return result + + except Exception as e: + print(f"运行测试失败: {e}") + return None + + def run_tests(self, args): + """运行测试""" + self.print_step(5, 6, "运行测试") + + # 查找测试可执行文件 + test_dir = self.build_dir / "bin" / args.build_type + if not test_dir.exists(): + test_dir = self.build_dir / "bin" + if not test_dir.exists(): + test_dir = self.build_dir + + test_files = list(test_dir.glob("test_*.exe")) + + if not test_files: + self.print_warning("未找到测试程序") + return True + + all_passed = True + + for test_exe in sorted(test_files): + test_name = test_exe.stem + self.print_info(f"运行测试: {test_name}") + print(f" {'-'*50}") + + # 运行测试 + result = self.run_tests_with_environment(str(test_exe)) + + if result is None: + self.print_error(f" {test_name} 运行失败") + all_passed = False + continue + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + print(f" {line}") + + if result.returncode == 0: + self.print_success(f" {test_name} 通过") + else: + self.print_error(f" {test_name} 失败 (退出码: {result.returncode})") + all_passed = False + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + print() # 空行 + + return all_passed + + def create_test_runner(self, args): + """创建独立的测试运行器""" + self.print_step(6, 6, "创建测试运行器") + + runner_path = self.build_dir / "run_tests.bat" + + content = f'''@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Test Runner +echo ======================================== +echo. + +REM Setup Intel oneAPI environment +set "SETVARS_PATH={self.intel_env.setvars_path or ''}" +if exist "%SETVARS_PATH%" ( + call "%SETVARS_PATH%" >nul + echo [INFO] Intel environment configured +) else ( + echo [WARNING] Intel environment not found + echo [WARNING] Tests may fail without runtime libraries +) + +echo. + +REM Run all test executables +set "TEST_COUNT=0" +set "PASS_COUNT=0" + +for %%f in ("bin\\{args.build_type}\\test_*.exe") do ( + set /a TEST_COUNT+=1 + echo [TEST %%f] + echo {'-'*50} + + %%f + if errorlevel 1 ( + echo [FAILED] %%f + ) else ( + echo [PASSED] %%f + set /a PASS_COUNT+=1 + ) + echo. +) + +echo ======================================== +echo Tests: %PASS_COUNT%/%TEST_COUNT% passed +if %PASS_COUNT% equ %TEST_COUNT% ( + echo [SUCCESS] All tests passed! +) else ( + echo [FAILURE] Some tests failed +) +echo ======================================== + +pause +''' + + with open(runner_path, 'w', encoding='utf-8') as f: + f.write(content) + + self.print_success(f"测试运行器已创建: {runner_path}") + self.print_info(f"使用方法: cd build && run_tests.bat") + + return runner_path + + def generate_report(self, args, build_time, tests_passed): + """生成构建报告""" + self.print_header("构建完成") + + print(f"项目: {self.project_root.name}") + print(f"构建类型: {args.build_type}") + print(f"编译器: {args.compiler}") + print(f"并行作业: {args.jobs}") + print(f"总耗时: {build_time:.1f}秒") + print(f"测试结果: {'全部通过' if tests_passed else '有失败'}") + + # 显示生成的可执行文件 + bin_dir = self.build_dir / "bin" / args.build_type + if bin_dir.exists(): + print(f"\n生成的可执行文件:") + for exe in sorted(bin_dir.glob("*.exe")): + size_mb = exe.stat().st_size / (1024 * 1024) + print(f" • {exe.name} ({size_mb:.2f} MB)") + + # 显示测试运行器信息 + runner_path = self.build_dir / "run_tests.bat" + if runner_path.exists(): + print(f"\n独立测试运行器:") + print(f" • {runner_path.name}") + print(f" 在Intel oneAPI环境中运行所有测试") + + def run(self): + """运行构建系统""" + parser = argparse.ArgumentParser( + description="Fortran CFD项目构建工具", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认构建 + %(prog)s --clean # 清理后构建 + %(prog)s --build-type Release # Release构建 + %(prog)s --no-tests # 只构建,不运行测试 + %(prog)s -j8 --verbose # 8线程并行构建,详细输出 + """ + ) + + parser.add_argument("--build-type", choices=["Debug", "Release"], + default="Debug", help="构建类型") + parser.add_argument("--compiler", choices=["ifx", "ifort"], + default="ifx", help="Fortran编译器") + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-tests", action="store_true", + help="跳过测试") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + + args = parser.parse_args() + + # 开始构建 + start_time = time.time() + + self.print_header("Fortran CFD 项目构建系统") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 检查前提条件 + if not self.check_prerequisites(): + if not args.force: + return 1 + + # 2. 设置Intel环境 + if not self.setup_intel_environment(args): + if not args.force: + return 1 + + # 3. 清理目录 + if not self.clean_build_directory(args): + if not args.force: + return 1 + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 4. 配置CMake + if not self.configure_cmake(args): + if not args.force: + return 1 + + # 5. 构建项目 + if not self.build_project(args): + if not args.force: + return 1 + + # 6. 运行测试和创建测试运行器 + tests_passed = True + if not args.no_tests: + tests_passed = self.run_tests(args) + + # 创建测试运行器 + self.create_test_runner(args) # 传递 args 参数 + + # 7. 生成报告 + build_time = time.time() - start_time + self.generate_report(args, build_time, tests_passed) + + return 0 if tests_passed else 1 + + except KeyboardInterrupt: + self.print_error("\n构建被用户中断") + return 1 + except Exception as e: + self.print_error(f"构建过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + builder = BuildSystem() + return builder.run() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/scripts/requirements.txt b/example/1d-linear-convection/weno3/fortran/registry/03a/scripts/requirements.txt new file mode 100644 index 000000000..d21a12b45 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/scripts/requirements.txt @@ -0,0 +1,2 @@ +# 构建脚本的Python依赖(可选) +# 目前没有特殊依赖,保持空文件或添加未来可能需要的包 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/scripts/run_all_steps.bat b/example/1d-linear-convection/weno3/fortran/registry/03a/scripts/run_all_steps.bat new file mode 100644 index 000000000..d506149b2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/scripts/run_all_steps.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo CFD Project: All Steps +echo ======================================== +echo. + +echo [INFO] Starting Step 1: Physics Modules Test... +call run_step1.bat + +if errorlevel 1 ( + echo [ERROR] Step 1 failed + pause + exit /b 1 +) + +echo. +echo [INFO] Starting Step 2: Configuration Physics Update... +call run_step2.bat + +if errorlevel 1 ( + echo [ERROR] Step 2 failed + pause + exit /b 1 +) + +echo. +echo ======================================== +echo All Steps Completed Successfully! +echo ======================================== +echo. +echo [INFO] Next: Update component manager for physics support +echo [INFO] Run: run_step3.bat (to be created) +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/scripts/run_step1.bat b/example/1d-linear-convection/weno3/fortran/registry/03a/scripts/run_step1.bat new file mode 100644 index 000000000..0b6b1f17e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/scripts/run_step1.bat @@ -0,0 +1,39 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Step 1: Physics Modules Test +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python step1 script with full Intel environment support... +echo. + +python run_step1.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Step 1 failed + pause + exit /b 1 +) + +echo. +echo [INFO] Step 1 completed successfully! +echo. +echo [INFO] Next step: Update config to include physics settings +echo [INFO] Run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/scripts/run_step1.py b/example/1d-linear-convection/weno3/fortran/registry/03a/scripts/run_step1.py new file mode 100644 index 000000000..5e087a690 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/scripts/run_step1.py @@ -0,0 +1,319 @@ +#!/usr/bin/env python3 +""" +Step 1: Physics Modules Test +扩展build.py,专门用于测试物理模块 +""" + +import os +import sys +import subprocess +import time +from pathlib import Path + +# 添加当前目录到路径,以便导入build.py的类 +sys.path.insert(0, str(Path(__file__).parent)) + +try: + from build import IntelEnvironment, BuildSystem +except ImportError: + print("Error: Cannot import build.py. Make sure build.py is in the same directory.") + sys.exit(1) + +class Step1System(BuildSystem): + """Step 1 测试系统,继承自BuildSystem""" + + def __init__(self): + super().__init__() + self.test_name = "test_physics_minimal" + self.test_exe = None + + def find_test_executable(self): + """查找测试可执行文件""" + possible_paths = [ + self.build_dir / "bin" / "Debug" / f"{self.test_name}.exe", + self.build_dir / "Debug" / f"{self.test_name}.exe", + self.build_dir / f"{self.test_name}.exe", + self.build_dir / "bin" / f"{self.test_name}.exe", + ] + + for path in possible_paths: + if path.exists(): + self.test_exe = path + self.print_success(f"Found test executable: {path}") + return True + + # 如果没有找到,尝试搜索 + self.print_warning(f"Could not find {self.test_name}.exe") + self.print_info("Searching for test executables...") + + try: + result = subprocess.run( + ["dir", str(self.build_dir), "/s", "/b", "*.exe"], + capture_output=True, + text=True, + encoding='utf-8', + shell=True + ) + + if result.returncode == 0: + test_files = [line.strip() for line in result.stdout.split('\n') + if line and 'test_' in line.lower()] + + if test_files: + self.print_info("Found test files:") + for test_file in test_files: + self.print_info(f" {test_file}") + return False + except: + pass + + return False + + def run_test_with_intel_env(self): + """在Intel环境下运行测试""" + if not self.test_exe: + self.print_error("No test executable found") + return False + + self.print_step(1, 2, f"Running test: {self.test_exe.name}") + + try: + # 创建临时的批处理文件来运行测试(包含Intel环境) + import tempfile + + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + # 设置Intel环境 + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'echo [INFO] Intel environment configured\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'echo [WARNING] Intel environment not found\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + self.print_info(f"Command: {temp_bat}") + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower() or 'fail' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or 'pass' in line.lower() or '✓' in line: + print(f" \033[92m{line}\033[0m") + elif '=' in line or '---' in line: + print(f" \033[96m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + self.print_warning("Test stderr output:") + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + self.print_step(2, 2, "Test execution completed") + + if result.returncode == 0: + self.print_success("Test passed") + return True + else: + self.print_error(f"Test failed (exit code: {result.returncode})") + return False + + except Exception as e: + self.print_error(f"Failed to run test: {e}") + return False + + def build_project_if_needed(self, args): + """如果需要,构建项目""" + if args.no_build: + self.print_info("Skipping build (--no-build flag)") + return True + + self.print_step(1, 3, "Building project") + + # 调用父类的构建方法 + build_args = argparse.Namespace() + build_args.clean = args.clean + build_args.build_type = "Debug" + build_args.compiler = "ifx" + build_args.no_tests = True # 不运行所有测试 + build_args.jobs = os.cpu_count() + build_args.verbose = args.verbose + build_args.force = args.force + + # 清理构建目录 + if args.clean and self.build_dir.exists(): + self.print_info("Cleaning build directory...") + import shutil + try: + shutil.rmtree(self.build_dir) + self.print_success("Build directory cleaned") + except Exception as e: + self.print_error(f"Clean failed: {e}") + if not args.force: + return False + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 配置CMake + self.print_step(2, 3, "Configuring CMake") + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + "-DCMAKE_BUILD_TYPE=Debug", + "-T", "fortran=ifx", + ] + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + if not self.run_command(cmake_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + # 构建项目 + self.print_step(3, 3, "Building project") + build_cmd = [ + "cmake", + "--build", ".", + "--config", "Debug", + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + if not self.run_command(build_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + self.print_success("Build completed") + return True + + def run(self): + """运行Step 1测试""" + parser = argparse.ArgumentParser( + description="Step 1: Physics Modules Test", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认运行 + %(prog)s --clean # 清理后构建并测试 + %(prog)s --no-build # 只运行测试,不重新构建 + %(prog)s --verbose # 详细输出 + %(prog)s -j4 # 使用4个并行作业构建 + """ + ) + + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-build", action="store_true", + help="不重新构建,直接运行测试") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + + args = parser.parse_args() + + # 开始测试 + start_time = time.time() + + self.print_header("Step 1: Physics Modules Implementation Test") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 设置Intel环境 + self.print_step(1, 4, "Setting up Intel oneAPI environment") + if not self.setup_intel_environment(args): + if not args.force: + self.print_error("Intel environment setup failed") + return 1 + self.print_warning("Intel environment setup failed, continuing...") + + # 2. 构建项目(如果需要) + self.print_step(2, 4, "Building project if needed") + if not self.build_project_if_needed(args): + if not args.force: + return 1 + + # 3. 查找测试可执行文件 + self.print_step(3, 4, "Finding test executable") + if not self.find_test_executable(): + if not args.force: + return 1 + self.print_warning("Test executable not found, but continuing due to --force") + return 0 + + # 4. 运行测试 + self.print_step(4, 4, "Running physics module test") + test_passed = self.run_test_with_intel_env() + + # 生成报告 + test_time = time.time() - start_time + self.print_header("Step 1 Complete") + + print(f"测试: {'通过 ✓' if test_passed else '失败 ✗'}") + print(f"测试程序: {self.test_exe.name if self.test_exe else '未找到'}") + print(f"总耗时: {test_time:.1f}秒") + + if test_passed: + print(f"\n下一步: 更新配置以包含物理设置") + print(f"建议: 修改config.f90,添加physics相关字段") + return 0 + else: + if args.force: + self.print_warning("测试失败,但由于--force标志继续执行") + return 0 + return 1 + + except KeyboardInterrupt: + self.print_error("\n测试被用户中断") + return 1 + except Exception as e: + self.print_error(f"测试过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + system = Step1System() + return system.run() + +if __name__ == "__main__": + # 需要导入argparse + import argparse + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/scripts/run_step2.bat b/example/1d-linear-convection/weno3/fortran/registry/03a/scripts/run_step2.bat new file mode 100644 index 000000000..9c1f62de4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/scripts/run_step2.bat @@ -0,0 +1,39 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Step 2: Configuration Physics Update +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python step2 script with full Intel environment support... +echo. + +python run_step2.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Step 2 failed + pause + exit /b 1 +) + +echo. +echo [INFO] Step 2 completed successfully! +echo. +echo [INFO] Next step: Update component manager to support physics +echo [INFO] Run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/scripts/run_step2.py b/example/1d-linear-convection/weno3/fortran/registry/03a/scripts/run_step2.py new file mode 100644 index 000000000..c16b76088 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/scripts/run_step2.py @@ -0,0 +1,284 @@ +#!/usr/bin/env python3 +""" +Step 2: Configuration Physics Update +测试配置模块的物理功能更新 +""" + +import os +import sys +import subprocess +import time +from pathlib import Path + +# 添加当前目录到路径,以便导入build.py的类 +sys.path.insert(0, str(Path(__file__).parent)) + +try: + from build import IntelEnvironment, BuildSystem +except ImportError: + print("Error: Cannot import build.py. Make sure build.py is in the same directory.") + sys.exit(1) + +class Step2System(BuildSystem): + """Step 2 测试系统,继承自BuildSystem""" + + def __init__(self): + super().__init__() + self.test_name = "test_config_physics" + self.test_exe = None + + def find_test_executable(self): + """查找测试可执行文件""" + possible_paths = [ + self.build_dir / "bin" / "Debug" / f"{self.test_name}.exe", + self.build_dir / "Debug" / f"{self.test_name}.exe", + self.build_dir / f"{self.test_name}.exe", + self.build_dir / "bin" / f"{self.test_name}.exe", + ] + + for path in possible_paths: + if path.exists(): + self.test_exe = path + self.print_success(f"Found test executable: {path}") + return True + + return False + + def run_test_with_intel_env(self): + """在Intel环境下运行测试""" + if not self.test_exe: + self.print_error("No test executable found") + return False + + self.print_step(1, 2, f"Running test: {self.test_exe.name}") + + try: + # 创建临时的批处理文件来运行测试(包含Intel环境) + import tempfile + + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + # 设置Intel环境 + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'echo [INFO] Intel environment configured\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'echo [WARNING] Intel environment not found\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + self.print_info(f"Command: {temp_bat}") + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower() or 'fail' in line.lower() or '✗' in line: + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or 'pass' in line.lower() or '✓' in line: + print(f" \033[92m{line}\033[0m") + elif '=' in line or '---' in line or '===' in line: + print(f" \033[96m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + self.print_warning("Test stderr output:") + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + self.print_step(2, 2, "Test execution completed") + + if result.returncode == 0: + self.print_success("Test passed") + return True + else: + self.print_error(f"Test failed (exit code: {result.returncode})") + return False + + except Exception as e: + self.print_error(f"Failed to run test: {e}") + return False + + def build_project(self, args): + """构建项目""" + if args.no_build: + self.print_info("Skipping build (--no-build flag)") + return True + + self.print_step(1, 3, "Building project") + + # 清理构建目录 + if args.clean and self.build_dir.exists(): + self.print_info("Cleaning build directory...") + import shutil + try: + shutil.rmtree(self.build_dir) + self.print_success("Build directory cleaned") + except Exception as e: + self.print_error(f"Clean failed: {e}") + if not args.force: + return False + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 配置CMake + self.print_step(2, 3, "Configuring CMake") + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + "-DCMAKE_BUILD_TYPE=Debug", + "-T", "fortran=ifx", + ] + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + if not self.run_command(cmake_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + # 构建项目 + self.print_step(3, 3, "Building project") + build_cmd = [ + "cmake", + "--build", ".", + "--config", "Debug", + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + if not self.run_command(build_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + self.print_success("Build completed") + return True + + def run(self): + """运行Step 2测试""" + import argparse + + parser = argparse.ArgumentParser( + description="Step 2: Configuration Physics Update", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认运行 + %(prog)s --clean # 清理后构建并测试 + %(prog)s --no-build # 只运行测试,不重新构建 + %(prog)s --verbose # 详细输出 + """ + ) + + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-build", action="store_true", + help="不重新构建,直接运行测试") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + + args = parser.parse_args() + + # 开始测试 + start_time = time.time() + + self.print_header("Step 2: Configuration Physics Update") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 设置Intel环境 + self.print_step(1, 4, "Setting up Intel oneAPI environment") + if not self.setup_intel_environment(args): + if not args.force: + self.print_error("Intel environment setup failed") + return 1 + self.print_warning("Intel environment setup failed, continuing...") + + # 2. 构建项目(如果需要) + self.print_step(2, 4, "Building project if needed") + if not self.build_project(args): + if not args.force: + return 1 + + # 3. 查找测试可执行文件 + self.print_step(3, 4, "Finding test executable") + if not self.find_test_executable(): + self.print_error(f"Test executable {self.test_name}.exe not found") + if not args.force: + return 1 + self.print_warning("Test executable not found, but continuing due to --force") + return 0 + + # 4. 运行测试 + self.print_step(4, 4, "Running configuration physics test") + test_passed = self.run_test_with_intel_env() + + # 生成报告 + test_time = time.time() - start_time + self.print_header("Step 2 Complete") + + print(f"测试: {'通过 ✓' if test_passed else '失败 ✗'}") + print(f"测试程序: {self.test_exe.name if self.test_exe else '未找到'}") + print(f"总耗时: {test_time:.1f}秒") + + if test_passed: + print(f"\n下一步: 更新组件管理器以支持物理模块") + print(f"建议: 修改component_manager.f90,添加physics组件创建") + return 0 + else: + if args.force: + self.print_warning("测试失败,但由于--flag继续执行") + return 0 + return 1 + + except KeyboardInterrupt: + self.print_error("\n测试被用户中断") + return 1 + except Exception as e: + self.print_error(f"测试过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + system = Step2System() + return system.run() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03a/src/CMakeLists.txt new file mode 100644 index 000000000..2aaee245a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/CMakeLists.txt @@ -0,0 +1,18 @@ +# ==================== src\CMakeLists.txt ==================== + +# src/CMakeLists.txt +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(base) +add_subdirectory(core) +add_subdirectory(infrastructure) +add_subdirectory(numerics) +add_subdirectory(physics) # ← 新增物理模块目录 +add_subdirectory(manager) +add_subdirectory(solver) + +message(STATUS "源代码目录配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/base/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03a/src/base/CMakeLists.txt new file mode 100644 index 000000000..74f4aa65f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/base/CMakeLists.txt @@ -0,0 +1,16 @@ +# src/base/CMakeLists.txt +message(STATUS "Configuring base module...") + +add_library(base STATIC + modules.f90 + precision.f90 # 新增 +) + +set_target_properties(base PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Base module configured") + +# src/core/CMakeLists.txt +message(STATUS "Configuring core module...") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/base/modules.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/src/base/modules.f90 new file mode 100644 index 000000000..43aaee241 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/base/modules.f90 @@ -0,0 +1,36 @@ +! src/base/modules.f90 +module base_modules + use, intrinsic :: iso_fortran_env, only: real64, int32 + implicit none + + public :: wp, ip, max_name_len, string_len, cfd_config_base, component_info + + integer, parameter :: wp = real64 + integer, parameter :: ip = int32 + integer, parameter :: string_len = 100 + integer, parameter :: max_name_len = 32 + + ! 基础配置类型 + type :: cfd_config_base + character(len=max_name_len) :: ic_type = "step" + character(len=max_name_len) :: recon_scheme = "eno" + character(len=max_name_len) :: flux_type = "rusanov" + integer(ip) :: rk_order = 1 + real(wp) :: wave_speed = 1.0_wp + real(wp) :: final_time = 0.625_wp + real(wp) :: dt = 0.025_wp + character(len=max_name_len) :: boundary_type = "periodic" + integer(ip) :: spatial_order = 2 + character(len=max_name_len) :: equation_type = "linear_advection" + character(len=max_name_len) :: problem_type = "linear_advection" + logical :: verbose = .true. + end type cfd_config_base + + ! 组件信息类型 + type :: component_info + character(len=max_name_len) :: category = "" + character(len=max_name_len) :: name = "" + integer(ip) :: order = 0 + end type component_info + +end module base_modules \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/base/precision.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/src/base/precision.f90 new file mode 100644 index 000000000..4ac5fd7ef --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/base/precision.f90 @@ -0,0 +1,9 @@ +! src/base/precision.f90(简单版本) +module precision_module + use base_modules, only: wp, ip + implicit none + + ! 重新导出,确保兼容 + public :: wp, ip + +end module precision_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03a/src/core/CMakeLists.txt new file mode 100644 index 000000000..d8b8df064 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/core/CMakeLists.txt @@ -0,0 +1,14 @@ +# src/core/CMakeLists.txt +message(STATUS "Configuring core module...") + +add_library(core STATIC + registry.f90 +) + +target_link_libraries(core PRIVATE base) + +set_target_properties(core PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Core module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/core/factory_base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/src/core/factory_base.f90 new file mode 100644 index 000000000..302418a15 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/core/factory_base.f90 @@ -0,0 +1,57 @@ +! src/core/factory_base.f90 +module factory_base_module + use base_modules, only: wp, ip + use registry_module, only: create_component, has_component + + implicit none + private + public :: wp, ip, factory_base, factory_create + + ! 工厂基类 + type :: factory_base + character(len=max_name_length) :: category = "" + contains + procedure :: create => factory_base_create + procedure :: get_available => factory_base_get_available + end type factory_base + + ! 便捷函数类型 + abstract interface + function factory_function_interface(category, name) result(instance) + import :: wp + character(len=*), intent(in) :: category, name + class(*), allocatable :: instance + end function factory_function_interface + end interface + +contains + + ! 创建工厂实例 + function factory_create(category) result(factory) + character(len=*), intent(in) :: category + type(factory_base) :: factory + factory%category = trim(category) + end function factory_create + + ! 工厂创建方法 + function factory_base_create(this, name) result(instance) + class(factory_base), intent(in) :: this + character(len=*), intent(in) :: name + class(*), allocatable :: instance + + instance = create_component(this%category, name) + end function factory_base_create + + ! 获取可用组件列表(简化版) + subroutine factory_base_get_available(this, names, count) + class(factory_base), intent(in) :: this + character(len=*), allocatable, intent(out) :: names(:) + integer(ip), intent(out) :: count + + ! 这里需要实现从注册表获取列表的逻辑 + ! 暂时返回空列表 + count = 0 + allocate(character(len=max_name_length) :: names(0)) + end subroutine factory_base_get_available + +end module factory_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..a960167c3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/core/factory_interfaces.f90 @@ -0,0 +1,17 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, factory_procedure + + ! Factory procedure interface + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/src/core/registry.f90 new file mode 100644 index 000000000..acc63edb4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/core/registry.f90 @@ -0,0 +1,203 @@ +! src/core/registry.f90 +module registry_module + use base_modules, only: wp, ip, max_name_len, component_info + + implicit none + private + + ! 明确公开所有需要的接口 + public :: wp, ip ! 类型参数 + public :: component_info ! 类型 + public :: registry_init, registry_cleanup ! 初始化/清理 + public :: register_component_simple ! 注册组件 + public :: has_component_simple ! 检查组件 + public :: list_components ! 列出组件 + public :: registry_is_initialized ! 检查初始化状态 ← 新增 + public :: registry_get_size ! 获取大小 ← 新增 + + ! 全局注册表 + type :: component_registry + type(component_info), allocatable :: components(:) + integer(ip) :: count = 0 + integer(ip) :: capacity = 100 + logical :: initialized = .false. + logical :: verbose = .true. + end type component_registry + + type(component_registry) :: registry + +contains + + ! ==================== 公共API ==================== + + subroutine registry_init(verbose) + logical, optional, intent(in) :: verbose + + if (registry%initialized) then + if (registry%verbose) then + print *, "[REGISTRY] Already initialized" + end if + return + end if + + if (present(verbose)) then + registry%verbose = verbose + end if + + allocate(registry%components(registry%capacity)) + registry%initialized = .true. + + if (registry%verbose) then + print *, "[REGISTRY] Initialized with capacity:", registry%capacity + end if + end subroutine registry_init + + subroutine registry_cleanup() + if (allocated(registry%components)) then + deallocate(registry%components) + end if + registry%initialized = .false. + registry%count = 0 + + if (registry%verbose) then + print *, "[REGISTRY] Cleaned up" + end if + end subroutine registry_cleanup + + subroutine register_component_simple(category, name, order) + character(len=*), intent(in) :: category, name + integer(ip), optional, intent(in) :: order + + integer(ip) :: i + type(component_info) :: info + + if (.not. registry%initialized) then + call registry_init() + end if + + ! 检查是否已存在 + do i = 1, registry%count + if (trim(registry%components(i)%category) == trim(category) .and. & + trim(registry%components(i)%name) == trim(name)) then + if (registry%verbose) then + print *, "[WARN] Overwriting component: ", trim(category), ".", trim(name) + end if + + ! 更新 + if (present(order)) then + registry%components(i)%order = order + else + registry%components(i)%order = 0 + end if + return + end if + end do + + ! 扩展数组 + if (registry%count >= registry%capacity) then + call expand_registry() + end if + + ! 添加新组件 + registry%count = registry%count + 1 + + info%category = trim(category) + info%name = trim(name) + info%order = 0 + if (present(order)) then + info%order = order + end if + + registry%components(registry%count) = info + + if (registry%verbose) then + print *, "[OK] Registered simple: ", trim(category), ".", trim(name) + end if + end subroutine register_component_simple + + logical function has_component_simple(category, name) + character(len=*), intent(in) :: category, name + + integer(ip) :: i + + has_component_simple = .false. + + if (.not. registry%initialized) return + + do i = 1, registry%count + if (trim(registry%components(i)%category) == trim(category) .and. & + trim(registry%components(i)%name) == trim(name)) then + has_component_simple = .true. + return + end if + end do + end function has_component_simple + + subroutine list_components(category) + character(len=*), optional, intent(in) :: category + + integer(ip) :: i, count + + if (.not. registry%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + if (registry%count == 0) then + print *, "[INFO] No components registered" + return + end if + + count = 0 + print *, "=== Registry Contents ===" + do i = 1, registry%count + if (.not. present(category) .or. & + trim(registry%components(i)%category) == trim(category)) then + call print_component_info(registry%components(i)) + count = count + 1 + end if + end do + + print *, "Total:", count, "components" + print *, "==========================" + end subroutine list_components + + ! ==================== 新增函数 ==================== + + logical function registry_is_initialized() + ! 检查注册表是否已初始化 + registry_is_initialized = registry%initialized + end function registry_is_initialized + + integer(ip) function registry_get_size() + ! 获取注册表中的组件数量 + registry_get_size = registry%count + end function registry_get_size + + ! ==================== 内部辅助函数 ==================== + + subroutine expand_registry() + type(component_info), allocatable :: temp(:) + + registry%capacity = registry%capacity * 2 + allocate(temp(registry%capacity)) + temp(1:registry%count) = registry%components(1:registry%count) + call move_alloc(temp, registry%components) + + if (registry%verbose) then + print *, "[INFO] Registry expanded to capacity:", registry%capacity + end if + end subroutine expand_registry + + subroutine print_component_info(info) + type(component_info), intent(in) :: info + + if (info%order > 0) then + print *, " [", trim(info%category), ".", trim(info%name), & + " (order:", info%order, ")]" + else + print *, " [", trim(info%category), ".", trim(info%name), "]" + end if + end subroutine print_component_info + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03a/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..70cbbd2f3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/infrastructure/CMakeLists.txt @@ -0,0 +1,17 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "Configuring infrastructure module...") + +add_library(infrastructure STATIC + config.f90 + mesh.f90 + domain.f90 # 新增 + solution.f90 # 新增 +) + +target_link_libraries(infrastructure PRIVATE base) + +set_target_properties(infrastructure PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Infrastructure module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/src/infrastructure/config.f90 new file mode 100644 index 000000000..7586a1a50 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/infrastructure/config.f90 @@ -0,0 +1,144 @@ +! src/infrastructure/config.f90 (修复版) +module config_module + use base_modules, only: wp, ip, max_name_len, cfd_config_base + + implicit none + public :: wp, ip, cfd_config, config_print, config_with_reconstruction + + ! 扩展配置类型 - 添加物理相关字段 + type, extends(cfd_config_base) :: cfd_config + ! 物理参数 + real(wp) :: left_boundary_value = 1.0_wp + real(wp) :: right_boundary_value = 2.0_wp + real(wp) :: domain_length = 2.0_wp + + ! 新增:物理模块相关配置 + real(wp) :: pulse_center = 0.5_wp ! 高斯脉冲中心 + real(wp) :: pulse_width = 0.1_wp ! 高斯脉冲宽度 + logical :: enable_physics = .true. ! 是否启用物理模块 + contains + ! 新增:物理相关配置方法 + procedure :: set_physics_parameters + procedure :: get_physics_info + end type cfd_config + +contains + + subroutine config_print(cfg) + type(cfd_config), intent(in) :: cfg + + print *, "=== CFD Configuration ===" + print *, "Initial condition: ", trim(cfg%ic_type) + print *, "Reconstruction: ", trim(cfg%recon_scheme), " (order:", cfg%spatial_order, ")" + print *, "Flux type: ", trim(cfg%flux_type) + print *, "Time integration: RK", cfg%rk_order + print *, "Wave speed: ", cfg%wave_speed + print *, "Final time: ", cfg%final_time + print *, "Time step: ", cfg%dt + print *, "Boundary: ", trim(cfg%boundary_type) + + ! 新增:物理配置信息 + print *, "--- Physics Configuration ---" + print *, "Equation type: ", trim(cfg%equation_type) + print *, "Problem type: ", trim(cfg%problem_type) + print *, "Domain length: ", cfg%domain_length + print *, "Physics enabled: ", cfg%enable_physics + + if (cfg%ic_type == "gaussian") then + print *, "Pulse center: ", cfg%pulse_center + print *, "Pulse width: ", cfg%pulse_width + end if + + print *, "===============================" + end subroutine config_print + + subroutine config_with_reconstruction(cfg, scheme, order) + type(cfd_config), intent(inout) :: cfg + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + integer :: i + + ! 转换为小写 + cfg%recon_scheme = scheme + do i = 1, len_trim(cfg%recon_scheme) + if (cfg%recon_scheme(i:i) >= 'A' .and. cfg%recon_scheme(i:i) <= 'Z') then + cfg%recon_scheme(i:i) = char(ichar(cfg%recon_scheme(i:i)) + 32) + end if + end do + + ! 设置阶数 + if (present(order)) then + cfg%spatial_order = order + else + if (index(cfg%recon_scheme, 'weno') > 0) then + cfg%spatial_order = 5 + else if (trim(cfg%recon_scheme) == 'eno') then + cfg%spatial_order = 3 + end if + end if + + if (cfg%verbose) then + print *, "[CONFIG] Reconstruction: ", trim(cfg%recon_scheme), & + " Order: ", cfg%spatial_order + end if + end subroutine config_with_reconstruction + + ! ========== 新增:物理参数设置方法 ========== + + subroutine set_physics_parameters(this, equation_type, problem_type, & + domain_length, enable_physics) + class(cfd_config), intent(inout) :: this + character(len=*), intent(in), optional :: equation_type, problem_type + real(wp), intent(in), optional :: domain_length + logical, intent(in), optional :: enable_physics + + if (present(equation_type)) then + this%equation_type = trim(equation_type) + if (this%verbose) then + print *, "[CONFIG] Set equation type: ", trim(this%equation_type) + end if + end if + + if (present(problem_type)) then + this%problem_type = trim(problem_type) + if (this%verbose) then + print *, "[CONFIG] Set problem type: ", trim(this%problem_type) + end if + end if + + if (present(domain_length)) then + this%domain_length = domain_length + if (this%verbose) then + print *, "[CONFIG] Set domain length: ", this%domain_length + end if + end if + + if (present(enable_physics)) then + this%enable_physics = enable_physics + if (this%verbose) then + print *, "[CONFIG] Physics module enabled: ", this%enable_physics + end if + end if + end subroutine set_physics_parameters + + subroutine get_physics_info(this) + class(cfd_config), intent(in) :: this + + print *, "=== Physics Configuration Info ===" + print *, "Equation type: ", trim(this%equation_type) + print *, "Problem type: ", trim(this%problem_type) + print *, "Domain length: ", this%domain_length + print *, "Wave speed: ", this%wave_speed + print *, "Physics enabled: ", this%enable_physics + + if (this%ic_type == "gaussian") then + print *, "Pulse parameters:" + print *, " Center: ", this%pulse_center + print *, " Width: ", this%pulse_width + end if + + print *, "==================================" + end subroutine get_physics_info + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/infrastructure/domain.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/src/infrastructure/domain.f90 new file mode 100644 index 000000000..c3662f039 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/infrastructure/domain.f90 @@ -0,0 +1,102 @@ +! src/infrastructure/domain.f90 +module domain_module + use base_modules, only: wp, ip, max_name_len + use config_module, only: cfd_config + use mesh_module, only: mesh_type + + implicit none + private + public :: wp, ip, domain_type, domain_create, is_physical_cell + + type :: domain_type + type(cfd_config), pointer :: config => null() + type(mesh_type), pointer :: mesh => null() + integer(ip) :: nghosts = 0 + integer(ip) :: ist = 1 ! 物理区域起始索引(1-based) + integer(ip) :: ied = 1 ! 物理区域结束索引(exclusive) + integer(ip) :: ntcells = 0 ! 总单元数(含ghost) + contains + procedure :: print_info => domain_print_info + procedure :: get_physical_indices => domain_get_physical_indices + end type domain_type + +contains + + function domain_create(config, mesh) result(domain) + type(cfd_config), target, intent(in) :: config + type(mesh_type), target, intent(in) :: mesh + type(domain_type) :: domain + + domain%config => config + domain%mesh => mesh + + ! 计算ghost层数(参考Julia的_calc_nghosts) + domain%nghosts = calc_nghosts(config) + domain%ist = domain%nghosts + 1 + domain%ied = domain%ist + mesh%ncells + domain%ntcells = mesh%ncells + 2 * domain%nghosts + + if (config%verbose) then + print *, "[DOMAIN] Created:" + print *, " Ghost layers: ", domain%nghosts + print *, " Physical cells: ", domain%ist, " to ", domain%ied - 1 + print *, " Total cells: ", domain%ntcells + end if + end function domain_create + + function calc_nghosts(config) result(nghosts) + type(cfd_config), intent(in) :: config + integer(ip) :: nghosts + + character(len=max_name_len) :: scheme + + scheme = config%recon_scheme + + if (scheme == "eno") then + nghosts = config%spatial_order + else if (index(scheme, "weno") > 0) then + nghosts = config%spatial_order / 2 + 1 + else + print *, "[WARNING] Unknown scheme, using default nghosts=2" + nghosts = 2 + end if + + if (nghosts <= 0) then + print *, "[ERROR] Invalid nghosts: ", nghosts + nghosts = 2 + end if + end function calc_nghosts + + logical function is_physical_cell(this, idx) + class(domain_type), intent(in) :: this + integer(ip), intent(in) :: idx + is_physical_cell = (idx >= this%ist .and. idx < this%ied) + end function is_physical_cell + + function domain_get_physical_indices(this) result(indices) + class(domain_type), intent(in) :: this + integer(ip), allocatable :: indices(:) + integer(ip) :: i, count + + count = this%ied - this%ist + allocate(indices(count)) + + do i = 1, count + indices(i) = this%ist + i - 1 + end do + end function domain_get_physical_indices + + subroutine domain_print_info(this) + class(domain_type), intent(in) :: this + + print *, "=== Domain Information ===" + print *, "Configuration: ", trim(this%config%recon_scheme), & + " order ", this%config%spatial_order + print *, "Ghost layers: ", this%nghosts + print *, "Physical cells: ", this%ist, " to ", this%ied - 1 + print *, "Total cells: ", this%ntcells + print *, "Mesh cells: ", this%mesh%ncells + print *, "==========================" + end subroutine domain_print_info + +end module domain_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..f810f3a1b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/infrastructure/mesh.f90 @@ -0,0 +1,73 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use base_modules, only: wp, ip + + implicit none + public :: wp, ip, mesh_type, mesh_init, mesh_print_info + + ! 网格类型 + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer(ip) :: ncells = 40 + integer(ip) :: nnodes + integer(ip) :: nx + real(wp), allocatable :: x(:) ! 节点坐标 + real(wp), allocatable :: xcc(:) ! 单元中心坐标 + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer(ip), optional, intent(in) :: ncells + + integer(ip) :: i + + ! 设置参数 + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! 计算 + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! 分配内存 + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! 生成节点坐标 + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! 生成单元中心坐标 + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== Mesh Information ===" + print *, "Domain: [", this%xmin, ", ", this%xmax, "]" + print *, "Cells: ", this%ncells + print *, "Nodes: ", this%nnodes + print *, "dx: ", this%dx + print *, "L: ", this%L + print *, "========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/infrastructure/solution.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/src/infrastructure/solution.f90 new file mode 100644 index 000000000..ce88fd8a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/infrastructure/solution.f90 @@ -0,0 +1,131 @@ +! src/infrastructure/solution.f90 +module solution_module + use base_modules, only: wp, ip + use domain_module, only: domain_type + + implicit none + private + public :: wp, ip, solution_type, solution_create, solution_reset + + type :: solution_type + type(domain_type), pointer :: domain => null() + real(wp), allocatable :: u(:) ! 当前解(含ghost) + real(wp), allocatable :: un(:) ! 旧解 + real(wp), allocatable :: q_face_left(:) ! 左界面值 + real(wp), allocatable :: q_face_right(:)! 右界面值 + real(wp), allocatable :: flux(:) ! 通量 + real(wp), allocatable :: res(:) ! 残差 + contains + procedure :: initialize => solution_initialize + procedure :: update_old_field => solution_update_old_field + procedure :: print_info => solution_print_info + procedure :: reset => solution_reset_instance + end type solution_type + +contains + + function solution_create(domain) result(solution) + type(domain_type), target, intent(in) :: domain + type(solution_type) :: solution + + integer(ip) :: ncells, nnodes, ntcells + + solution%domain => domain + + ncells = domain%mesh%ncells + nnodes = domain%mesh%nnodes + ntcells = domain%ntcells + + ! 分配数组(与Julia solution.jl一致) + allocate(solution%u(ntcells), source=0.0_wp) + allocate(solution%un(ntcells), source=0.0_wp) + allocate(solution%q_face_left(nnodes), source=0.0_wp) + allocate(solution%q_face_right(nnodes), source=0.0_wp) + allocate(solution%flux(nnodes), source=0.0_wp) + allocate(solution%res(ncells), source=0.0_wp) + + if (domain%config%verbose) then + print *, "[SOLUTION] Created:" + print *, " u size: ", size(solution%u), " (with ghosts)" + print *, " flux size: ", size(solution%flux) + print *, " res size: ", size(solution%res) + end if + end function solution_create + + subroutine solution_initialize(this, initial_values) + class(solution_type), intent(inout) :: this + real(wp), intent(in), optional :: initial_values(:) + + integer(ip) :: i, idx + type(domain_type), pointer :: domain + + domain => this%domain + + if (present(initial_values)) then + ! 应用初始值到物理区域 + do i = domain%ist, domain%ied - 1 + idx = i - domain%ist + 1 + if (idx <= size(initial_values)) then + this%u(i) = initial_values(idx) + end if + end do + else + ! 默认为0 + this%u = 0.0_wp + end if + + ! 同步旧场(与Julia的update_old_field一致) + call this%update_old_field() + + if (domain%config%verbose) then + print *, "[SOLUTION] Initialized" + print *, " u range: ", minval(this%u), " to ", maxval(this%u) + end if + end subroutine solution_initialize + + subroutine solution_update_old_field(this) + class(solution_type), intent(inout) :: this + this%un = this%u ! 与Julia的 un .= u 一致 + end subroutine solution_update_old_field + + subroutine solution_reset_instance(this) + class(solution_type), intent(inout) :: this + call solution_reset(this) + end subroutine solution_reset_instance + + subroutine solution_reset(solution) + type(solution_type), intent(inout) :: solution + + if (allocated(solution%u)) solution%u = 0.0_wp + if (allocated(solution%un)) solution%un = 0.0_wp + if (allocated(solution%q_face_left)) solution%q_face_left = 0.0_wp + if (allocated(solution%q_face_right)) solution%q_face_right = 0.0_wp + if (allocated(solution%flux)) solution%flux = 0.0_wp + if (allocated(solution%res)) solution%res = 0.0_wp + + if (associated(solution%domain) .and. solution%domain%config%verbose) then + print *, "[SOLUTION] Reset" + end if + end subroutine solution_reset + + subroutine solution_print_info(this) + class(solution_type), intent(in) :: this + + print *, "=== Solution Information ===" + print *, "Arrays:" + print *, " u: ", size(this%u), " elements" + print *, " un: ", size(this%un), " elements" + print *, " q_face_left: ", size(this%q_face_left), " elements" + print *, " q_face_right: ", size(this%q_face_right), " elements" + print *, " flux: ", size(this%flux), " elements" + print *, " res: ", size(this%res), " elements" + + if (allocated(this%u)) then + print *, "Values:" + print *, " u min/max: ", minval(this%u), maxval(this%u) + print *, " un min/max: ", minval(this%un), maxval(this%un) + end if + print *, "============================" + end subroutine solution_print_info + +end module solution_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/manager/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03a/src/manager/CMakeLists.txt new file mode 100644 index 000000000..00c8bf49b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/manager/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/manager/CMakeLists.txt +message(STATUS "配置管理器模块...") + +# 创建管理器库 +add_library(manager STATIC + component_manager.f90 + component_factory.f90 +) + +# 明确依赖关系:管理器依赖所有其他模块 +target_link_libraries(manager + PRIVATE + core + infrastructure + reconstructor + flux +) + +# 设置模块输出目录 +set_target_properties(manager PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "管理器模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/manager/component_factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/src/manager/component_factory.f90 new file mode 100644 index 000000000..dda16d3e8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/manager/component_factory.f90 @@ -0,0 +1,128 @@ +! src/manager/component_factory.f90 (简化版 - 只包含基本功能) +module component_factory_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use config_module, only: cfd_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + + use eno_reconstructor_module, only: eno_reconstructor, create_eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor, create_weno3_reconstructor + use rusanov_flux_module, only: rusanov_flux, create_rusanov_flux + + implicit none + private + public :: wp, create_reconstructor, create_flux_calculator + + ! 错误代码 + integer, parameter :: CM_SUCCESS = 0 + integer, parameter :: CM_ERROR_UNKNOWN_SCHEME = 1 + integer, parameter :: CM_ERROR_UNKNOWN_FLUX = 2 + integer, parameter :: CM_ERROR_INVALID_ORDER = 3 + +contains + + ! ==================== 重构器创建 ==================== + + function create_reconstructor(config, status) result(recon) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(reconstructor_base), allocatable :: recon + + character(len=20) :: scheme + integer :: order, error_code + + scheme = trim(adjustl(config%recon_scheme)) + order = config%spatial_order + + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating reconstructor: ", scheme, " order=", order + end if + + select case(scheme) + case('eno') + allocate(eno_reconstructor :: recon) + select type(recon) + type is(eno_reconstructor) + recon = create_eno_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case('weno3') + allocate(weno3_reconstructor :: recon) + select type(recon) + type is(weno3_reconstructor) + recon = create_weno3_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case default + error_code = CM_ERROR_UNKNOWN_SCHEME + if (config%verbose) then + print *, "[ERROR] Unknown reconstructor scheme: ", scheme + print *, " Available: eno, weno3" + end if + end select + + ! 检查阶数有效性 + if (error_code == CM_SUCCESS) then + if (order < 1) then + error_code = CM_ERROR_INVALID_ORDER + if (config%verbose) then + print *, "[ERROR] Invalid spatial order: ", order + end if + end if + end if + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Reconstructor creation failed" + end if + end function create_reconstructor + + ! ==================== 通量计算器创建 ==================== + + function create_flux_calculator(config, status) result(flux) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(flux_calculator_base), allocatable :: flux + + character(len=20) :: flux_type + integer :: error_code + + flux_type = trim(adjustl(config%flux_type)) + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating flux calculator: ", flux_type + end if + + select case(flux_type) + case('rusanov') + allocate(rusanov_flux :: flux) + select type(flux) + type is(rusanov_flux) + flux = create_rusanov_flux() + flux%wave_speed_default = config%wave_speed + end select + + case default + error_code = CM_ERROR_UNKNOWN_FLUX + if (config%verbose) then + print *, "[ERROR] Unknown flux type: ", flux_type + print *, " Available: rusanov" + end if + end select + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Flux calculator creation failed" + end if + end function create_flux_calculator + +end module component_factory_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/manager/component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/src/manager/component_manager.f90 new file mode 100644 index 000000000..e043d99fa --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/manager/component_manager.f90 @@ -0,0 +1,75 @@ +! src/manager/component_manager.f90 (简化版) +module component_manager_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use config_module, only: cfd_config + use component_factory_module, only: create_reconstructor, create_flux_calculator + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + + implicit none + private + public :: wp, component_manager_info, validate_config + public :: create_reconstructor, create_flux_calculator + +contains + + ! ==================== 配置验证 ==================== + + function validate_config(config) result(is_valid) + type(cfd_config), intent(in) :: config + logical :: is_valid + + integer :: status + class(reconstructor_base), allocatable :: test_recon + class(flux_calculator_base), allocatable :: test_flux + + is_valid = .false. + + ! 测试创建重构器 + test_recon = create_reconstructor(config, status) + if (status /= 0) then + if (config%verbose) then + print *, "[CONFIG VALIDATION] Invalid reconstructor configuration" + end if + return + end if + + ! 测试创建通量计算器 + test_flux = create_flux_calculator(config, status) + if (status /= 0) then + if (config%verbose) then + print *, "[CONFIG VALIDATION] Invalid flux configuration" + end if + return + end if + + ! 清理测试组件 + if (allocated(test_recon)) deallocate(test_recon) + if (allocated(test_flux)) deallocate(test_flux) + + is_valid = .true. + + if (config%verbose) then + print *, "[CONFIG VALIDATION] Configuration is valid" + end if + end function validate_config + + ! ==================== 信息显示 ==================== + + subroutine component_manager_info() + print *, "=== Component Manager ===" + print *, "Available reconstructors:" + print *, " - eno (orders: 1-7)" + print *, " - weno3 (order: 3)" + print *, "" + print *, "Available flux calculators:" + print *, " - rusanov" + print *, "" + print *, "Features:" + print *, " - Configuration validation" + print *, " - Component creation from config" + print *, " - Error handling with status codes" + print *, "=========================" + end subroutine component_manager_info + +end module component_manager_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..66874a7ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/CMakeLists.txt @@ -0,0 +1,7 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..3fde7746b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,28 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +# Start with just the base module +add_library(flux STATIC + base.f90 + rusanov.f90 +) + +#target_link_libraries(flux PRIVATE core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 明确设置不使用 /Qm64 选项 +if(CMAKE_Fortran_COMPILER_ID MATCHES "IntelLLVM|Intel") + set_target_properties(flux PROPERTIES + Fortran_FLAGS "${CMAKE_Fortran_FLAGS} /Qm64" # 明确设置,避免继承问题 + ) +endif() + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/flux/base.f90 new file mode 100644 index 000000000..7080a7ae4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/flux/base.f90 @@ -0,0 +1,30 @@ +!src/numerics/flux/base.f90 +module flux_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, flux_calculator_base + + ! Base flux calculator type + type :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure :: info => flux_info + procedure :: print_basic_info => flux_print_basic ! 添加辅助方法 + end type flux_calculator_base + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "Flux calculator information:" + print *, " Name: ", trim(this%name) + end subroutine flux_info + + subroutine flux_print_basic(this) + class(flux_calculator_base), intent(in) :: this + print *, " Name: ", trim(this%name) + end subroutine flux_print_basic + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/flux/rusanov.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/flux/rusanov.f90 new file mode 100644 index 000000000..daa9e3bb5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/flux/rusanov.f90 @@ -0,0 +1,41 @@ +! src/numerics/flux/rusanov.f90 +module rusanov_flux_module + use, intrinsic :: iso_fortran_env, only: real64 + use flux_base_module, only: flux_calculator_base + implicit none + + private + public :: real64, rusanov_flux, create_rusanov_flux + + type, extends(flux_calculator_base) :: rusanov_flux + real(real64) :: wave_speed_default = 1.0_real64 + contains + procedure :: info => rusanov_info + end type rusanov_flux + + ! 构造函数接口 + interface rusanov_flux + module procedure create_rusanov_flux + end interface + +contains + + ! 构造函数 + type(rusanov_flux) function create_rusanov_flux() result(this) + this%name = "Rusanov" + this%wave_speed_default = 1.0_real64 + end function create_rusanov_flux + + subroutine rusanov_info(this) + class(rusanov_flux), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Flux calculator information:" + call this%print_basic_info() + + ! 添加Rusanov特有信息 + print *, " Type: Rusanov flux" + print *, " Default wave speed: ", this%wave_speed_default + end subroutine rusanov_info + +end module rusanov_flux_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..5e4b938d5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,22 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "Configuring reconstructor module...") + +# Start with just the base module +add_library(reconstructor STATIC + base.f90 + eno.f90 + weno3.f90 +) + +target_link_libraries(reconstructor PRIVATE core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Reconstructor module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..53798d021 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/reconstructor/base.f90 @@ -0,0 +1,33 @@ +!src/numerics/reconstructor/base.f90 +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, reconstructor_base + + ! Base reconstructor type + type :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + contains + procedure :: info => reconstructor_info + procedure :: print_basic_info => reconstructor_print_basic ! 添加一个辅助方法 + end type reconstructor_base + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + print *, "Reconstructor information:" + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_info + + subroutine reconstructor_print_basic(this) + class(reconstructor_base), intent(in) :: this + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_print_basic + +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/reconstructor/eno.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/reconstructor/eno.f90 new file mode 100644 index 000000000..f973e8b32 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/reconstructor/eno.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/eno.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, eno_reconstructor, create_eno_reconstructor ! ← 添加这个 + + type, extends(reconstructor_base) :: eno_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => eno_info + end type eno_reconstructor + + ! 构造函数接口 + interface eno_reconstructor + module procedure create_eno_reconstructor + end interface + +contains + + ! 构造函数 + type(eno_reconstructor) function create_eno_reconstructor() result(this) + this%name = "ENO" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_eno_reconstructor + + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加ENO特有信息 + print *, " Type: ENO reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/reconstructor/weno3.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/reconstructor/weno3.f90 new file mode 100644 index 000000000..d5b7a7477 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/numerics/reconstructor/weno3.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/weno3.f90 +module weno3_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno3_reconstructor, create_weno3_reconstructor + + type, extends(reconstructor_base) :: weno3_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno3_info + end type weno3_reconstructor + + ! 构造函数接口 + interface weno3_reconstructor + module procedure create_weno3_reconstructor + end interface + +contains + + ! 构造函数 + type(weno3_reconstructor) function create_weno3_reconstructor() result(this) + this%name = "WENO3" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_weno3_reconstructor + + subroutine weno3_info(this) + class(weno3_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加WENO3特有信息 + print *, " Type: WENO-3 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno3_info + +end module weno3_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/physics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03a/src/physics/CMakeLists.txt new file mode 100644 index 000000000..cc4e233ab --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/physics/CMakeLists.txt @@ -0,0 +1,19 @@ +# src/physics/CMakeLists.txt +message(STATUS "配置物理模块...") + +# 创建物理模块库 +add_library(physics STATIC + physics_interface.f90 + equations/linear_convection.f90 + problems/linear_convection_problem.f90 +) + +# 链接依赖 +target_link_libraries(physics PRIVATE base) + +# 设置模块输出目录 +set_target_properties(physics PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "物理模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/physics/equations/linear_convection.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/src/physics/equations/linear_convection.f90 new file mode 100644 index 000000000..fff7be55d --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/physics/equations/linear_convection.f90 @@ -0,0 +1,49 @@ +! src/physics/equations/linear_convection.f90 +module linear_convection_equation + use precision_module, only: wp, ip + use physics_interface, only: physics_equation + implicit none + private + + ! 具体方程类型 - 先声明 + type, extends(physics_equation) :: linear_convection_eq + real(wp) :: wave_speed = 1.0_wp + contains + procedure :: flux => lc_flux + procedure :: speed => lc_speed + end type linear_convection_eq + + ! 公开接口 + public :: wp, ip + public :: linear_convection_eq, create_linear_convection_eq + +contains + + ! 构造函数 + function create_linear_convection_eq(wave_speed) result(eq) + real(wp), intent(in), optional :: wave_speed + type(linear_convection_eq) :: eq + + eq%name = "Linear Convection" + if (present(wave_speed)) then + eq%wave_speed = wave_speed + else + eq%wave_speed = 1.0_wp + end if + end function create_linear_convection_eq + + ! 方法实现 + pure function lc_flux(this, u) result(f) + class(linear_convection_eq), intent(in) :: this + real(wp), intent(in) :: u + real(wp) :: f + f = this%wave_speed * u + end function lc_flux + + pure function lc_speed(this) result(a) + class(linear_convection_eq), intent(in) :: this + real(wp) :: a + a = this%wave_speed + end function lc_speed + +end module linear_convection_equation \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/physics/physics_interface.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/src/physics/physics_interface.f90 new file mode 100644 index 000000000..45002da6c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/physics/physics_interface.f90 @@ -0,0 +1,64 @@ +! src/physics/physics_interface.f90 +module physics_interface + use precision_module, only: wp, ip + implicit none + private + + ! 定义抽象基类型 - 先声明为私有,然后在公开部分导出 + type, abstract :: physics_equation + character(len=:), allocatable :: name + contains + procedure(eq_flux_abs), deferred :: flux + procedure(eq_speed_abs), deferred :: speed + end type physics_equation + + type, abstract :: physics_problem + character(len=:), allocatable :: name + contains + procedure(prob_ic_abs), deferred :: initial_condition + procedure(prob_bc_abs), deferred :: boundary_condition + procedure(prob_exact_abs), deferred :: exact_solution + end type physics_problem + + ! 抽象接口定义 + abstract interface + pure function eq_flux_abs(this, u) result(f) + import :: physics_equation, wp + class(physics_equation), intent(in) :: this + real(wp), intent(in) :: u + real(wp) :: f + end function eq_flux_abs + + pure function eq_speed_abs(this) result(a) + import :: physics_equation, wp + class(physics_equation), intent(in) :: this + real(wp) :: a + end function eq_speed_abs + + subroutine prob_ic_abs(this, x, u) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(in) :: x(:) + real(wp), intent(out) :: u(:) + end subroutine prob_ic_abs + + subroutine prob_bc_abs(this, u, t) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(inout) :: u(:) + real(wp), intent(in), optional :: t + end subroutine prob_bc_abs + + function prob_exact_abs(this, x, t) result(u) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(in) :: x(:), t + real(wp), dimension(size(x)) :: u + end function prob_exact_abs + end interface + + ! 公开接口 - 使用独立的public语句 + public :: wp, ip + public :: physics_equation, physics_problem + +end module physics_interface \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/physics/problems/linear_convection_problem.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/src/physics/problems/linear_convection_problem.f90 new file mode 100644 index 000000000..06226ed13 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/physics/problems/linear_convection_problem.f90 @@ -0,0 +1,118 @@ +! src/physics/problems/linear_convection_problem.f90 +module linear_convection_problem + use precision_module, only: wp, ip + use physics_interface, only: physics_problem + implicit none + private + + ! 具体问题类型 - 先声明 + type, extends(physics_problem) :: linear_convection_prob + real(wp) :: wave_speed = 1.0_wp + real(wp) :: domain_length = 2.0_wp + character(len=20) :: ic_type = "step" + character(len=20) :: boundary_type = "periodic" + contains + procedure :: initial_condition => lc_initial_condition + procedure :: boundary_condition => lc_boundary_condition + procedure :: exact_solution => lc_exact_solution + end type linear_convection_prob + + ! 公开接口 + public :: wp, ip + public :: linear_convection_prob, create_linear_convection_prob + +contains + + ! 构造函数 + function create_linear_convection_prob(wave_speed, domain_length, & + ic_type, boundary_type) result(prob) + real(wp), intent(in), optional :: wave_speed, domain_length + character(len=*), intent(in), optional :: ic_type, boundary_type + type(linear_convection_prob) :: prob + + prob%name = "Linear Convection Problem" + + if (present(wave_speed)) prob%wave_speed = wave_speed + if (present(domain_length)) prob%domain_length = domain_length + if (present(ic_type)) prob%ic_type = ic_type + if (present(boundary_type)) prob%boundary_type = boundary_type + end function create_linear_convection_prob + + ! 初始条件 + subroutine lc_initial_condition(this, x, u) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(in) :: x(:) + real(wp), intent(out) :: u(:) + + integer :: i + + select case (trim(this%ic_type)) + case ("step") + do i = 1, size(x) + if (x(i) >= 0.5_wp .and. x(i) <= 1.0_wp) then + u(i) = 2.0_wp + else + u(i) = 1.0_wp + end if + end do + + case ("sin", "sine") + do i = 1, size(x) + u(i) = sin(2.0_wp * 3.141592653589793_wp * x(i) / this%domain_length) + end do + + case ("gaussian") + do i = 1, size(x) + u(i) = exp(-((x(i) - 0.5_wp) / 0.1_wp)**2) + end do + + case default + ! 默认阶跃函数 + do i = 1, size(x) + if (x(i) >= 0.5_wp .and. x(i) <= 1.0_wp) then + u(i) = 2.0_wp + else + u(i) = 1.0_wp + end if + end do + end select + end subroutine lc_initial_condition + + ! 边界条件(虚拟实现,实际在boundary模块) + subroutine lc_boundary_condition(this, u, t) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(inout) :: u(:) + real(wp), intent(in), optional :: t + + ! 边界条件将在独立模块实现 + print *, "[PROBLEM] Boundary condition placeholder" + if (present(t)) then + print *, " Time = ", t + end if + end subroutine lc_boundary_condition + + ! 精确解(周期性平移) + function lc_exact_solution(this, x, t) result(u) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(in) :: x(:), t + real(wp), dimension(size(x)) :: u + real(wp), dimension(size(x)) :: x_shifted + integer :: i + + ! 周期性平移 + do i = 1, size(x) + x_shifted(i) = x(i) - this%wave_speed * t + ! 确保在 [0, domain_length) 范围内 + do while (x_shifted(i) < 0.0_wp) + x_shifted(i) = x_shifted(i) + this%domain_length + end do + do while (x_shifted(i) >= this%domain_length) + x_shifted(i) = x_shifted(i) - this%domain_length + end do + end do + + ! 重用初始条件函数 + call this%initial_condition(x_shifted, u) + end function lc_exact_solution + +end module linear_convection_problem \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/solver/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03a/src/solver/CMakeLists.txt new file mode 100644 index 000000000..3f320bcae --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/solver/CMakeLists.txt @@ -0,0 +1,18 @@ +# src/solver/CMakeLists.txt +message(STATUS "配置求解器模块...") + +add_library(solver STATIC + base.f90 +) + +target_link_libraries(solver + PRIVATE + infrastructure + core +) + +set_target_properties(solver PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "求解器模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/src/solver/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/src/solver/base.f90 new file mode 100644 index 000000000..1881ba089 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/src/solver/base.f90 @@ -0,0 +1,258 @@ +! src/solver/base.f90 +module solver_base_module + use base_modules, only: wp => wp, ip => ip ! 重命名以避免冲突 + + use config_module, only: cfd_config + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create + + implicit none + private + + ! 明确导出列表 + public :: wp, ip ! 类型参数 + public :: solver_base, create_solver_base ! 类型和构造函数 + public :: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, SOLVER_RUNNING + public :: SOLVER_COMPLETED, SOLVER_ERROR ! 状态常量 + + ! 求解器状态枚举 + integer, parameter :: SOLVER_UNINITIALIZED = 0 + integer, parameter :: SOLVER_INITIALIZED = 1 + integer, parameter :: SOLVER_RUNNING = 2 + integer, parameter :: SOLVER_COMPLETED = 3 + integer, parameter :: SOLVER_ERROR = 4 + + ! 求解器基类 + type :: solver_base + ! 基本组件 + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + + ! 状态管理 + integer :: state = SOLVER_UNINITIALIZED + character(len=100) :: error_message = "" + real(wp) :: current_time = 0.0_wp + integer(ip) :: current_step = 0 + + ! 时间控制 + real(wp) :: dt_original = 0.0_wp + contains + procedure :: initialize => solver_base_initialize + procedure :: step => solver_base_step + procedure :: run_to_time => solver_base_run_to_time + procedure :: cleanup => solver_base_cleanup + procedure :: get_state => solver_base_get_state + procedure :: get_error => solver_base_get_error + procedure :: print_info => solver_base_print_info + end type solver_base + + ! 构造函数接口 + interface solver_base + module procedure create_solver_base + end interface + +contains + + ! ==================== 构造函数 ==================== + + function create_solver_base(config, mesh) result(solver) + type(cfd_config), intent(in) :: config + type(mesh_type), intent(in) :: mesh + type(solver_base) :: solver + + solver%config = config + solver%mesh = mesh + + ! 创建域 + solver%domain = domain_create(config, mesh) + + ! 创建解 + solver%solution = solution_create(solver%domain) + + ! 保存原始时间步长 + solver%dt_original = config%dt + + if (config%verbose) then + print *, "[SOLVER] Base solver created" + print *, " Mesh cells: ", mesh%ncells + print *, " Domain total cells: ", solver%domain%ntcells + end if + end function create_solver_base + + ! ==================== 初始化 ==================== + + subroutine solver_base_initialize(this) + class(solver_base), intent(inout) :: this + + if (this%state == SOLVER_INITIALIZED) then + if (this%config%verbose) then + print *, "[SOLVER] Already initialized" + end if + return + end if + + ! 初始化解(通过配置) + ! 这里暂时简化,实际需要调用初始条件工厂 + print *, "[INFO] Base solver initialized (simplified)" + + ! 更新状态 + this%state = SOLVER_INITIALIZED + this%current_time = 0.0_wp + this%current_step = 0 + + if (this%config%verbose) then + print *, "[SOLVER] Initialized at t = ", this%current_time + end if + end subroutine solver_base_initialize + + ! ==================== 单步计算(虚方法) ==================== + + subroutine solver_base_step(this, dt) + class(solver_base), intent(inout) :: this + real(wp), intent(in) :: dt + + ! 基类中这只是虚方法,需要在子类中实现 + print *, "[INFO] Base solver step (virtual method)" + print *, " dt = ", dt + print *, " t = ", this%current_time + + ! 更新时间 + this%current_time = this%current_time + dt + this%current_step = this%current_step + 1 + + ! 简单模拟:只是更新状态 + if (this%config%verbose) then + print *, "[SOLVER] Step completed: t = ", this%current_time, & + ", step = ", this%current_step + end if + end subroutine solver_base_step + + ! ==================== 运行到指定时间 ==================== + + subroutine solver_base_run_to_time(this, final_time) + class(solver_base), intent(inout) :: this + real(wp), intent(in) :: final_time + + real(wp) :: dt, t_remaining + integer :: step_count + + if (this%state /= SOLVER_INITIALIZED) then + this%error_message = "Solver not initialized" + this%state = SOLVER_ERROR + return + end if + + this%state = SOLVER_RUNNING + step_count = 0 + + if (this%config%verbose) then + print *, "[SOLVER] Running from t = ", this%current_time, & + " to t = ", final_time + end if + + do while (this%current_time < final_time) + ! 计算时间步长 + dt = min(this%config%dt, final_time - this%current_time) + + ! 执行时间步 + call this%step(dt) + + step_count = step_count + 1 + + ! 每10步输出一次进度 + if (mod(step_count, 10) == 0 .and. this%config%verbose) then + print *, "[SOLVER] Progress: t = ", this%current_time, & + " / ", final_time + end if + end do + + ! 恢复原始时间步长 + this%config%dt = this%dt_original + + ! 更新状态 + this%state = SOLVER_COMPLETED + + if (this%config%verbose) then + print *, "[SOLVER] Run completed:" + print *, " Final time: ", this%current_time + print *, " Total steps: ", this%current_step + end if + end subroutine solver_base_run_to_time + + ! ==================== 清理 ==================== + + subroutine solver_base_cleanup(this) + class(solver_base), intent(inout) :: this + + ! 重置状态 + this%state = SOLVER_UNINITIALIZED + this%current_time = 0.0_wp + this%current_step = 0 + this%error_message = "" + + if (this%config%verbose) then + print *, "[SOLVER] Cleaned up" + end if + end subroutine solver_base_cleanup + + ! ==================== 状态查询 ==================== + + function solver_base_get_state(this) result(state) + class(solver_base), intent(in) :: this + integer :: state + state = this%state + end function solver_base_get_state + + function solver_base_get_error(this) result(error_msg) + class(solver_base), intent(in) :: this + character(len=100) :: error_msg + error_msg = trim(this%error_message) + end function solver_base_get_error + + ! ==================== 信息打印 ==================== + + subroutine solver_base_print_info(this) + class(solver_base), intent(in) :: this + + character(len=20) :: state_str + + ! 状态字符串 + select case (this%state) + case (SOLVER_UNINITIALIZED) + state_str = "Uninitialized" + case (SOLVER_INITIALIZED) + state_str = "Initialized" + case (SOLVER_RUNNING) + state_str = "Running" + case (SOLVER_COMPLETED) + state_str = "Completed" + case (SOLVER_ERROR) + state_str = "Error" + case default + state_str = "Unknown" + end select + + print *, "=== Solver Information ===" + print *, "State: ", trim(state_str) + print *, "Current time: ", this%current_time + print *, "Current step: ", this%current_step + print *, "Error message: '", trim(this%error_message), "'" + + ! 配置信息 + print *, "Configuration:" + print *, " Scheme: ", trim(this%config%recon_scheme) + print *, " Order: ", this%config%spatial_order + print *, " dt: ", this%config%dt + + ! 域信息 + print *, "Domain:" + print *, " Ghost layers: ", this%domain%nghosts + print *, " Physical cells: ", this%domain%ist, " to ", this%domain%ied - 1 + + print *, "=========================" + end subroutine solver_base_print_info + +end module solver_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03a/tests/CMakeLists.txt new file mode 100644 index 000000000..bb152a955 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/tests/CMakeLists.txt @@ -0,0 +1,83 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +# +#message(STATUS "CMAKE_Fortran_MODULE_DIRECTORY=${CMAKE_Fortran_MODULE_DIRECTORY}") +# +add_executable(test_minimal_simple test_minimal_simple.f90) +target_link_libraries(test_minimal_simple + PRIVATE + core # 必须链接core库 + infrastructure +) + + +add_executable(test_simple_link test_simple_link.f90) +target_link_libraries(test_simple_link + PRIVATE + reconstructor + flux +) + + +add_executable(test_factory_simple test_factory_simple.f90) +target_link_libraries(test_factory_simple + PRIVATE + core + infrastructure + reconstructor + flux +) + + +add_executable(test_component_manager test_component_manager.f90) + +target_link_libraries(test_component_manager + PRIVATE + manager # ← 链接到新的管理器库 + infrastructure +) +add_executable(test_basic_only test_basic_only.f90) +target_link_libraries(test_basic_only + PRIVATE + infrastructure + core +) + +add_executable(test_physics_minimal test_physics_minimal.f90) +target_link_libraries(test_physics_minimal + PRIVATE + physics + base +) + +add_executable(test_domain_solution test_domain_solution.f90) +target_link_libraries(test_domain_solution + PRIVATE + infrastructure + core +) + +add_executable(test_config_physics test_config_physics.f90) +target_link_libraries(test_config_physics + PRIVATE + infrastructure + core +) + +add_executable(test_solver_base test_solver_base.f90) +target_link_libraries(test_solver_base + PRIVATE + solver + infrastructure + core +) + +add_executable(test_component_manager_physics test_component_manager_physics.f90) +target_link_libraries(test_component_manager_physics + PRIVATE + manager + infrastructure + physics + core +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_architecture.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_architecture.f90 new file mode 100644 index 000000000..1c40d4677 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_architecture.f90 @@ -0,0 +1,117 @@ +! tests/test_architecture.f90 +program test_architecture + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module + use config_module + use mesh_module + use component_manager_module + + implicit none + + print *, "=== CFD架构测试 ===" + print *, "" + + ! 测试1: 基本系统 + call test_basic_systems() + + ! 测试2: 组件注册 + call test_registry_integration() + + ! 测试3: 配置验证 + call test_config_validation() + + ! 测试4: 完整流程 + call test_full_workflow() + + print *, "" + print *, "=== 架构测试完成 ===" + +contains + + subroutine test_basic_systems() + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "1. 测试基本系统..." + print *, "-------------------" + + ! 测试配置 + call config_print(config) + print *, "✓ 配置创建成功" + + ! 测试网格 + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "✓ 网格创建成功" + print *, "" + end subroutine test_basic_systems + + subroutine test_registry_integration() + print *, "2. 测试注册系统集成..." + print *, "------------------------" + + call initialize_registry(verbose=.true.) + + ! 注册核心组件 + print *, "注册核心组件:" + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + ! 显示注册内容 + call component_registry%list_simple() + print *, "注册表大小: ", registry_get_size() + + call cleanup_registry() + print *, "✓ 注册系统测试完成" + print *, "" + end subroutine test_registry_integration + + subroutine test_config_validation() + type(cfd_config) :: config + logical :: is_valid + + print *, "3. 测试配置验证..." + print *, "-------------------" + + ! 测试有效配置 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + print *, "测试配置:" + print *, " - 重构器: ", trim(config%recon_scheme), " 阶数: ", config%spatial_order + print *, " - 通量: ", trim(config%flux_type) + print *, " - 波速: ", config%wave_speed + + ! 这里调用验证函数(需要先实现) + ! is_valid = validate_config(config) + print *, "✓ 配置验证接口准备就绪" + print *, "" + end subroutine test_config_validation + + subroutine test_full_workflow() + print *, "4. 测试完整流程..." + print *, "-------------------" + + print *, "步骤1: 初始化系统 ✓" + print *, "步骤2: 创建网格 ✓" + print *, "步骤3: 设置配置 ✓" + print *, "步骤4: 创建组件 (待实现)" + print *, "步骤5: 初始化解 (待实现)" + print *, "步骤6: 时间推进 (待实现)" + print *, "步骤7: 输出结果 (待实现)" + print *, "" + + print *, "✓ 流程框架定义完成" + end subroutine test_full_workflow + +end program test_architecture \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_basic_only.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_basic_only.f90 new file mode 100644 index 000000000..20901ddcc --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_basic_only.f90 @@ -0,0 +1,56 @@ +! tests/test_basic_only.f90 +program test_basic_only + ! 只测试最基本的功能,不依赖复杂模块 + use config_module, only: cfd_config, config_print, wp + use mesh_module, only: mesh_type + use registry_module, only: registry_init, registry_cleanup, & + register_component_simple, list_components + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "=== BASIC TEST - Minimal Functionality ===" + print *, "" + + ! 测试1: 配置 + print *, "1. Testing configuration..." + print *, "----------------------------" + call config_print(config) + print *, "" + + ! 测试2: 网格 + print *, "2. Testing mesh..." + print *, "------------------" + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=5) + print *, "Mesh initialized:" + print *, " Cells: ", mesh%ncells + print *, " Nodes: ", mesh%nnodes + print *, " dx: ", mesh%dx + print *, "" + + ! 测试3: 注册系统 + print *, "3. Testing registry..." + print *, "----------------------" + + call registry_init() + + ! 注册组件(使用简化版本) + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("flux", "rusanov") + + ! 列出组件 + call list_components() + print *, "" + + ! 清理 + call registry_cleanup() + + print *, "=== TEST PASSED ===" + print *, "✓ Configuration works" + print *, "✓ Mesh works" + print *, "✓ Registry works" + +end program test_basic_only \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_cfd_architecture.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_cfd_architecture.f90 new file mode 100644 index 000000000..1c40d4677 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_cfd_architecture.f90 @@ -0,0 +1,117 @@ +! tests/test_architecture.f90 +program test_architecture + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module + use config_module + use mesh_module + use component_manager_module + + implicit none + + print *, "=== CFD架构测试 ===" + print *, "" + + ! 测试1: 基本系统 + call test_basic_systems() + + ! 测试2: 组件注册 + call test_registry_integration() + + ! 测试3: 配置验证 + call test_config_validation() + + ! 测试4: 完整流程 + call test_full_workflow() + + print *, "" + print *, "=== 架构测试完成 ===" + +contains + + subroutine test_basic_systems() + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "1. 测试基本系统..." + print *, "-------------------" + + ! 测试配置 + call config_print(config) + print *, "✓ 配置创建成功" + + ! 测试网格 + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "✓ 网格创建成功" + print *, "" + end subroutine test_basic_systems + + subroutine test_registry_integration() + print *, "2. 测试注册系统集成..." + print *, "------------------------" + + call initialize_registry(verbose=.true.) + + ! 注册核心组件 + print *, "注册核心组件:" + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + ! 显示注册内容 + call component_registry%list_simple() + print *, "注册表大小: ", registry_get_size() + + call cleanup_registry() + print *, "✓ 注册系统测试完成" + print *, "" + end subroutine test_registry_integration + + subroutine test_config_validation() + type(cfd_config) :: config + logical :: is_valid + + print *, "3. 测试配置验证..." + print *, "-------------------" + + ! 测试有效配置 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + print *, "测试配置:" + print *, " - 重构器: ", trim(config%recon_scheme), " 阶数: ", config%spatial_order + print *, " - 通量: ", trim(config%flux_type) + print *, " - 波速: ", config%wave_speed + + ! 这里调用验证函数(需要先实现) + ! is_valid = validate_config(config) + print *, "✓ 配置验证接口准备就绪" + print *, "" + end subroutine test_config_validation + + subroutine test_full_workflow() + print *, "4. 测试完整流程..." + print *, "-------------------" + + print *, "步骤1: 初始化系统 ✓" + print *, "步骤2: 创建网格 ✓" + print *, "步骤3: 设置配置 ✓" + print *, "步骤4: 创建组件 (待实现)" + print *, "步骤5: 初始化解 (待实现)" + print *, "步骤6: 时间推进 (待实现)" + print *, "步骤7: 输出结果 (待实现)" + print *, "" + + print *, "✓ 流程框架定义完成" + end subroutine test_full_workflow + +end program test_architecture \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_component_manager.f90 new file mode 100644 index 000000000..f60c35056 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_component_manager.f90 @@ -0,0 +1,111 @@ +! tests/test_component_manager.f90 +program test_component_manager + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use component_manager_module, only: create_reconstructor, create_flux_calculator + use component_manager_module, only: component_manager_info, validate_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + implicit none + + type(cfd_config) :: config + class(reconstructor_base), allocatable :: recon + class(flux_calculator_base), allocatable :: flux + integer :: status + logical :: is_valid + + print *, "=== Component Manager Test ===" + print *, "" + + ! 显示组件管理器信息 + call component_manager_info() + print *, "" + + ! 测试1: 基本配置 + print *, "1. Testing basic ENO3 + Rusanov configuration..." + print *, "-----------------------------------------------" + + config%verbose = .true. + call config_print(config) + + ! 配置ENO3重构 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + call config_print(config) + print *, "" + + ! 验证配置 + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Configuration is valid" + else + print *, "[ERROR] Configuration is invalid" + end if + print *, "" + + ! 测试2: 创建组件 + print *, "2. Testing component creation..." + print *, "--------------------------------" + + ! 创建重构器(带状态检查) + recon = create_reconstructor(config, status) + if (status == 0) then + print *, "[OK] Reconstructor created successfully" + call recon%info() + else + print *, "[ERROR] Failed to create reconstructor, code:", status + end if + print *, "" + + ! 创建通量计算器 + flux = create_flux_calculator(config, status) + if (status == 0) then + print *, "[OK] Flux calculator created successfully" + call flux%info() + else + print *, "[ERROR] Failed to create flux calculator, code:", status + end if + print *, "" + + ! 测试3: WENO3重构测试 + print *, "3. Testing WENO3 configuration..." + print *, "---------------------------------" + + call config_with_reconstruction(config, "weno3", 3) + + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] WENO3 configuration is valid" + + ! 创建WENO3重构器 + recon = create_reconstructor(config) + call recon%info() + else + print *, "[ERROR] WENO3 configuration is invalid" + end if + print *, "" + + ! 测试4: 错误配置测试 + print *, "4. Testing invalid configuration..." + print *, "-----------------------------------" + + config%recon_scheme = "unknown_scheme" + config%flux_type = "unknown_flux" + + is_valid = validate_config(config) + if (.not. is_valid) then + print *, "[OK] Invalid configuration correctly rejected" + else + print *, "[ERROR] Invalid configuration should have been rejected" + end if + + ! 清理 + if (allocated(recon)) deallocate(recon) + if (allocated(flux)) deallocate(flux) + + print *, "" + print *, "=== Component manager test completed successfully ===" + +end program test_component_manager \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_component_manager_physics.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_component_manager_physics.f90 new file mode 100644 index 000000000..f2becca95 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_component_manager_physics.f90 @@ -0,0 +1,120 @@ +! tests/test_component_manager_physics.f90 (简化版) +program test_component_manager_physics + use base_modules, only: wp + use config_module, only: cfd_config, config_print, config_with_reconstruction + use component_manager_module, only: component_manager_info, validate_config + + implicit none + + type(cfd_config) :: config + logical :: is_valid + + print *, "=== Component Manager Physics Test (Simplified) ===" + print *, "" + + ! 测试1: 显示组件管理器信息 + print *, "1. Testing component manager info..." + print *, "-------------------------------------" + call component_manager_info() + print *, "" + + ! 测试2: 物理模块测试(默认) + print *, "2. Testing physics module with default configuration..." + print *, "------------------------------------------------------" + + config%verbose = .true. + call config_print(config) + + ! 验证配置 + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Default configuration is valid" + else + print *, "[ERROR] Default configuration is invalid" + end if + print *, "" + + ! 测试3: 测试物理配置 + print *, "3. Testing physics configuration..." + print *, "------------------------------------" + + ! 修改物理参数 + config%equation_type = "linear_advection" + config%problem_type = "linear_advection" + config%wave_speed = 2.5_wp + config%domain_length = 3.0_wp + + print *, "Modified physics configuration:" + print *, " Equation type: ", trim(config%equation_type) + print *, " Problem type: ", trim(config%problem_type) + print *, " Wave speed: ", config%wave_speed + print *, " Domain length: ", config%domain_length + print *, " Physics enabled: ", config%enable_physics + + ! 验证修改后的配置 + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Modified physics configuration is valid" + else + print *, "[ERROR] Modified physics configuration is invalid" + end if + print *, "" + + ! 测试4: 数值组件测试 + print *, "4. Testing numerical components with physics..." + print *, "-----------------------------------------------" + + call config_with_reconstruction(config, "weno3", 3) + config%flux_type = "rusanov" + + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Combined physics+numerics configuration is valid" + else + print *, "[ERROR] Combined configuration is invalid" + end if + print *, "" + + ! 测试5: 物理模块禁用测试 + print *, "5. Testing physics module disabled..." + print *, "---------------------------------------" + + config%enable_physics = .false. + config%verbose = .false. + + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Configuration valid even with physics disabled" + else + print *, "[ERROR] Configuration should be valid with physics disabled" + end if + print *, "" + + ! 测试6: 错误配置测试 + print *, "6. Testing error handling..." + print *, "-----------------------------" + + config%verbose = .true. + config%enable_physics = .true. + config%recon_scheme = "unknown_scheme" + config%flux_type = "unknown_flux" + config%equation_type = "unknown_equation" + config%problem_type = "unknown_problem" + + is_valid = validate_config(config) + if (.not. is_valid) then + print *, "[OK] Invalid configuration correctly rejected" + else + print *, "[ERROR] Invalid configuration should have been rejected" + end if + print *, "" + + print *, "=== Component Manager Physics Test Summary ===" + print *, "✓ Component manager info works" + print *, "✓ Configuration validation works with physics" + print *, "✓ Error handling works correctly" + print *, "✓ Combined physics+numerics validation works" + print *, "" + print *, "下一步: 集成物理模块到求解器框架" + +end program test_component_manager_physics \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_config_physics.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_config_physics.f90 new file mode 100644 index 000000000..c6fef5c0b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_config_physics.f90 @@ -0,0 +1,141 @@ +! tests/test_config_physics.f90 (修复版) +program test_config_physics + use base_modules, only: wp + use config_module, only: cfd_config, config_print, config_with_reconstruction + + implicit none + + type(cfd_config) :: config + + print *, "=== Configuration Physics Test (Simplified) ===" + print *, "" + + ! 测试1: 默认配置 + print *, "1. Testing default configuration..." + print *, "-----------------------------------" + call config_print(config) + print *, "" + + ! 测试2: 验证基础物理字段 + print *, "2. Testing basic physics fields..." + print *, "----------------------------------" + + print *, "Verifying default physics fields:" + + if (trim(config%equation_type) == "linear_advection") then + print *, " ✓ Default equation type: linear_advection" + else + print *, " ✗ Unexpected equation type: ", trim(config%equation_type) + end if + + if (trim(config%problem_type) == "linear_advection") then + print *, " ✓ Default problem type: linear_advection" + else + print *, " ✗ Unexpected problem type: ", trim(config%problem_type) + end if + + if (abs(config%domain_length - 2.0_wp) < 1e-10_wp) then + print *, " ✓ Default domain length: 2.0" + else + print *, " ✗ Unexpected domain length: ", config%domain_length + end if + + if (config%enable_physics) then + print *, " ✓ Physics enabled by default" + else + print *, " ✗ Physics not enabled by default" + end if + + print *, "" + + ! 测试3: 使用类型绑定的方法(正确的方法名) + print *, "3. Testing type-bound procedures..." + print *, "--------------------------------------" + + call config%set_physics_parameters( & + equation_type="burgers_equation", & + problem_type="sod_shock_tube", & + domain_length=3.0_wp, & + enable_physics=.false.) + + print *, "After set_physics_parameters:" + print *, " Equation type: ", trim(config%equation_type) + print *, " Problem type: ", trim(config%problem_type) + print *, " Domain length: ", config%domain_length + print *, " Physics enabled: ", config%enable_physics + + if (trim(config%equation_type) == "burgers_equation") then + print *, " ✓ Equation type modified successfully via set_physics_parameters" + end if + + if (trim(config%problem_type) == "sod_shock_tube") then + print *, " ✓ Problem type modified successfully via set_physics_parameters" + end if + + if (abs(config%domain_length - 3.0_wp) < 1e-10_wp) then + print *, " ✓ Domain length modified successfully via set_physics_parameters" + end if + + if (.not. config%enable_physics) then + print *, " ✓ Physics disabled successfully via set_physics_parameters" + end if + + print *, "" + + ! 测试4: 调用get_physics_info方法 + print *, "4. Testing get_physics_info method..." + print *, "--------------------------------------" + call config%get_physics_info() + print *, "" + + ! 测试5: 高斯脉冲配置 + print *, "5. Testing Gaussian pulse configuration..." + print *, "-----------------------------------------" + + config%ic_type = "gaussian" + config%pulse_center = 0.6_wp + config%pulse_width = 0.15_wp + + print *, "Gaussian pulse parameters:" + print *, " IC type: ", trim(config%ic_type) + print *, " Center: ", config%pulse_center + print *, " Width: ", config%pulse_width + + if (trim(config%ic_type) == "gaussian") then + print *, " ✓ Gaussian IC type set" + end if + + if (abs(config%pulse_center - 0.6_wp) < 1e-10_wp) then + print *, " ✓ Pulse center set" + end if + + if (abs(config%pulse_width - 0.15_wp) < 1e-10_wp) then + print *, " ✓ Pulse width set" + end if + + print *, "" + + ! 测试6: 重构配置 + print *, "6. Testing reconstruction configuration..." + print *, "------------------------------------------" + + call config_with_reconstruction(config, "weno", 5) + + print *, "Reconstruction configuration:" + print *, " Scheme: ", trim(config%recon_scheme) + print *, " Order: ", config%spatial_order + + if (trim(config%recon_scheme) == "weno" .and. config%spatial_order == 5) then + print *, " ✓ WENO5 configuration successful" + else + print *, " ✗ Reconstruction configuration failed" + end if + + print *, "" + + print *, "=== Configuration Physics Test Complete ===" + print *, "✓ Config module updated with physics support" + print *, "✓ Fields can be directly accessed and modified" + print *, "✓ Type-bound procedures work correctly" + +end program test_config_physics \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_domain_solution.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_domain_solution.f90 new file mode 100644 index 000000000..ff659bac2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_domain_solution.f90 @@ -0,0 +1,102 @@ +! tests/test_domain_solution.f90 +program test_domain_solution + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create, solution_reset + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + real(wp), allocatable :: initial_values(:) + integer :: i + + print *, "=== Domain and Solution Test ===" + print *, "" + + ! 测试1: 不同重构方案的ghost层计算 + print *, "1. Testing ghost layer calculation..." + print *, "--------------------------------------" + + ! ENO3 + call config_with_reconstruction(config, "eno", 3) + config%verbose = .false. + call mesh%init(ncells=10) + domain = domain_create(config, mesh) + print *, "ENO3: nghosts = ", domain%nghosts, " (expected: 3)" + + ! WENO3 + call config_with_reconstruction(config, "weno3", 3) + domain = domain_create(config, mesh) + print *, "WENO3: nghosts = ", domain%nghosts, " (expected: 2)" + + ! WENO5 + call config_with_reconstruction(config, "weno", 5) + domain = domain_create(config, mesh) + print *, "WENO5: nghosts = ", domain%nghosts, " (expected: 3)" + print *, "" + + ! 测试2: Solution数组 + print *, "2. Testing solution arrays..." + print *, "------------------------------" + + call config_with_reconstruction(config, "eno", 3) + config%verbose = .true. + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=10) + + domain = domain_create(config, mesh) + call domain%print_info() + print *, "" + + solution = solution_create(domain) + call solution%print_info() + print *, "" + + ! 测试3: 初始化和更新 + print *, "3. Testing initialization and update..." + print *, "----------------------------------------" + + allocate(initial_values(mesh%ncells)) + do i = 1, mesh%ncells + initial_values(i) = sin(2.0_wp * 3.14159265358979_wp * mesh%xcc(i) / mesh%L) + end do + + call solution%initialize(initial_values) + print *, "After initialization:" + print *, " u range: ", minval(solution%u), " to ", maxval(solution%u) + print *, " un range: ", minval(solution%un), " to ", maxval(solution%un) + + ! 修改当前解,测试更新 + solution%u = solution%u * 2.0_wp + call solution%update_old_field() + print *, "After update: max|u - un| = ", maxval(abs(solution%u - solution%un)) + print *, "" + + ! 测试4: 重置 + print *, "4. Testing reset..." + print *, "-------------------" + + call solution_reset(solution) + print *, "After reset:" + print *, " u max: ", maxval(abs(solution%u)) + print *, " un max: ", maxval(abs(solution%un)) + print *, " flux max: ", maxval(abs(solution%flux)) + print *, "" + + deallocate(initial_values) + + print *, "=== Test Summary ===" + print *, "✓ Ghost layer calculation works" + print *, "✓ Domain creation works" + print *, "✓ Solution arrays work" + print *, "✓ Initialization works" + print *, "✓ Field update works" + print *, "✓ Reset works" + print *, "" + print *, "Ready for next step: Implementing Physics modules" + +end program test_domain_solution \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_factory_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_factory_simple.f90 new file mode 100644 index 000000000..db65da7cf --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_factory_simple.f90 @@ -0,0 +1,58 @@ +! tests/test_factory_simple.f90 (修复版) +program test_factory_simple + use base_modules, only: wp ! ← 添加这行 + use config_module, only: cfd_config, config_print + use mesh_module, only: mesh_type + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(eno_reconstructor) :: eno + type(weno3_reconstructor) :: weno3 + type(rusanov_flux) :: rusanov + + print *, "=== Factory Pattern Simple Test ===" + print *, "" + + ! Test 1: Basic systems + print *, "1. Testing basic systems..." + print *, "-----------------------------" + call config_print(config) + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 2: Creating reconstructors + print *, "2. Testing reconstructors..." + print *, "------------------------------" + + ! 创建并测试ENO重构器 + print *, "Creating ENO reconstructor..." + eno = eno_reconstructor() ! 使用构造函数 + call eno%info() ! 必须调用info方法 + + print *, "" + print *, "Creating WENO3 reconstructor..." + weno3 = weno3_reconstructor() ! 使用构造函数 + call weno3%info() ! 必须调用info方法 + print *, "" + + ! Test 3: Creating flux calculator + print *, "3. Testing flux calculator..." + print *, "-------------------------------" + + print *, "Creating Rusanov flux calculator..." + rusanov = rusanov_flux() ! 使用构造函数 + call rusanov%info() ! 必须调用info方法 + print *, "" + + print *, "=== Factory pattern simple test completed successfully ===" + +end program test_factory_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_minimal_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_minimal_simple.f90 new file mode 100644 index 000000000..ec03ccf89 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_minimal_simple.f90 @@ -0,0 +1,87 @@ +! tests/test_minimal_simple.f90 +program test_minimal_simple + use base_modules, only: wp ! ← 添加这行 + use registry_module + use config_module + use mesh_module + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Simple Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call registry_init() + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call list_components() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + + if (has_component_simple("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component_simple("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Test getting available components (simple version) + print *, "5. Testing registry functionality" + print *, "---------------------------------" + print *, "Registry initialized: ", registry_is_initialized() + print *, "Number of components: ", registry_get_size() + print *, "" + + ! Cleanup + call registry_cleanup() + + print *, "=== Simple test completed successfully ===" + +end program test_minimal_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_physics_minimal.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_physics_minimal.f90 new file mode 100644 index 000000000..cf2a28f19 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_physics_minimal.f90 @@ -0,0 +1,91 @@ +! tests/test_physics_minimal.f90 +program test_physics_minimal + use precision_module, only: wp, ip + use linear_convection_equation, only: linear_convection_eq, create_linear_convection_eq + use linear_convection_problem, only: linear_convection_prob, create_linear_convection_prob + + implicit none + + type(linear_convection_eq) :: eq + type(linear_convection_prob) :: prob + real(wp) :: u, f, a + real(wp), allocatable :: x(:), u_ic(:), u_exact(:) + integer :: i, nx = 10 + + print *, "=== 最小物理模块测试 ===" + print *, "" + + ! 测试1: 方程功能 + print *, "1. 测试方程功能..." + print *, "-------------------" + + eq = create_linear_convection_eq(wave_speed=2.0_wp) + print *, "方程: ", eq%name + print *, "波速: ", eq%wave_speed + + u = 1.5_wp + f = eq%flux(u) + a = eq%speed() + + print *, "u = ", u + print *, "F(u) = ", f, " (期望: 3.0)" + print *, "波速 a = ", a, " (期望: 2.0)" + + if (abs(f - 3.0_wp) < 1e-10_wp .and. abs(a - 2.0_wp) < 1e-10_wp) then + print *, "✓ 方程功能正常" + else + print *, "✗ 方程功能异常" + end if + print *, "" + + ! 测试2: 问题功能 + print *, "2. 测试问题功能..." + print *, "-------------------" + + prob = create_linear_convection_prob(ic_type="step", domain_length=2.0_wp) + print *, "问题: ", prob%name + print *, "IC类型: ", trim(prob%ic_type) + print *, "域长度: ", prob%domain_length + + allocate(x(nx), u_ic(nx), u_exact(nx)) + do i = 1, nx + x(i) = 0.0_wp + (i-1) * 0.2_wp + end do + + ! 测试初始条件 + call prob%initial_condition(x, u_ic) + print *, "初始条件范围: ", minval(u_ic), " 到 ", maxval(u_ic) + + ! 测试精确解 + u_exact = prob%exact_solution(x, 0.0_wp) + print *, "t=0时精确解范围: ", minval(u_exact), " 到 ", maxval(u_exact) + + ! 检查阶跃函数 + if (abs(u_ic(1) - 1.0_wp) < 1e-10_wp .and. & + abs(u_ic(6) - 2.0_wp) < 1e-10_wp) then + print *, "✓ 阶跃初始条件正确" + else + print *, "✗ 阶跃初始条件错误" + end if + + ! 检查精确解与初始条件一致 + if (maxval(abs(u_ic - u_exact)) < 1e-10_wp) then + print *, "✓ t=0时精确解与初始条件一致" + else + print *, "✗ 精确解计算错误" + end if + print *, "" + + ! 测试3: 边界条件接口 + print *, "3. 测试边界条件接口..." + print *, "----------------------" + call prob%boundary_condition(u_ic, 0.0_wp) + print *, "✓ 边界条件接口正常" + print *, "" + + deallocate(x, u_ic, u_exact) + + print *, "=== 物理模块最小测试完成 ===" + print *, "下一步: 将物理模块集成到现有系统中" + +end program test_physics_minimal \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_simple_link.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_simple_link.f90 new file mode 100644 index 000000000..71cc614e8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_simple_link.f90 @@ -0,0 +1,78 @@ +! tests/test_simple_link.f90 +program test_simple_link + use base_modules, only: wp ! ← 添加这行 + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call registry_init() + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call list_components() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component_simple("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component_simple("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Cleanup + call registry_cleanup() + + print *, "=== Minimal test completed successfully ===" + +end program test_simple_link \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_solver_base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_solver_base.f90 new file mode 100644 index 000000000..6cfe47e41 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_solver_base.f90 @@ -0,0 +1,99 @@ +! tests/test_solver_base.f90 (修复版) +program test_solver_base + ! 所有 USE 语句必须在程序开始处 + use base_modules, only: wp + use config_module, only: cfd_config, config_print, config_with_reconstruction + use mesh_module, only: mesh_type + use solver_base_module, only: solver_base, SOLVER_UNINITIALIZED, & + SOLVER_INITIALIZED, SOLVER_COMPLETED, SOLVER_ERROR + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(solver_base) :: solver + integer :: state + + print *, "=== Solver Base Test ===" + print *, "" + + ! 测试1: 创建求解器 + print *, "1. Creating solver..." + print *, "----------------------" + + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%dt = 0.01_wp + + call config_print(config) + + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=10) + + solver = solver_base(config, mesh) + call solver%print_info() + print *, "" + + ! 测试2: 初始化 + print *, "2. Initializing solver..." + print *, "-------------------------" + + call solver%initialize() + state = solver%get_state() + print *, "State after initialization: ", state + print *, "Expected: ", SOLVER_INITIALIZED + print *, "Match? ", state == SOLVER_INITIALIZED + print *, "Error message: '", trim(solver%get_error()), "'" + print *, "" + + ! 测试3: 运行求解器 + print *, "3. Running solver..." + print *, "--------------------" + + call solver%run_to_time(0.05_wp) + state = solver%get_state() + print *, "State after run: ", state + print *, "Expected: ", SOLVER_COMPLETED + print *, "Match? ", state == SOLVER_COMPLETED + print *, "Current time: ", solver%current_time + print *, "Current step: ", solver%current_step + print *, "" + + ! 测试4: 再次运行(从已完成状态) + print *, "4. Running again from completed state..." + print *, "----------------------------------------" + + ! 需要先清理才能重新运行 + call solver%cleanup() + call solver%initialize() + call solver%run_to_time(0.1_wp) + + call solver%print_info() + print *, "" + + ! 测试5: 错误处理 + print *, "5. Testing error states..." + print *, "--------------------------" + + ! 创建一个未初始化的求解器 + call solver%cleanup() + state = solver%get_state() + print *, "Uninitialized state: ", state + print *, "Expected: ", SOLVER_UNINITIALIZED + print *, "Match? ", state == SOLVER_UNINITIALIZED + + ! 尝试运行未初始化的求解器 + call solver%run_to_time(0.01_wp) + state = solver%get_state() + print *, "State after error: ", state + print *, "Expected: ", SOLVER_ERROR + print *, "Match? ", state == SOLVER_ERROR + print *, "Error message: '", trim(solver%get_error()), "'" + print *, "" + + print *, "=== Solver Base Test Complete ===" + print *, "✓ Solver base class works" + print *, "✓ State management works" + print *, "✓ Time stepping framework works" + print *, "✓ Error handling works" + +end program test_solver_base \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_solver_framework.f90 b/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_solver_framework.f90 new file mode 100644 index 000000000..6754323d0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03a/tests/test_solver_framework.f90 @@ -0,0 +1,91 @@ +! tests/test_solver_framework.f90 +program test_solver_framework + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use mesh_module, only: mesh_type + use solver_module, only: cfd_solver, solver_create, solver_run, solver_cleanup + use solver_module, only: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, & + SOLVER_COMPLETED, SOLVER_ERROR + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(cfd_solver) :: solver + + print *, "=== 求解器框架测试 ===" + print *, "" + + ! 测试1: 基本创建 + print *, "1. 测试求解器创建..." + print *, "----------------------" + + ! 创建配置 + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.0_real64 + config%dt = 0.01_real64 + + call config_print(config) + print *, "" + + ! 创建网格 + call mesh%init(xmin=0.0_real64, xmax=2.0_real64, ncells=20) + call mesh%print_info() + print *, "" + + ! 创建求解器 + solver = solver_create(config, mesh) + print *, "✓ 求解器创建成功" + print *, " 状态: ", solver%get_state() + print *, "" + + ! 测试2: 求解器初始化 + print *, "2. 测试求解器初始化..." + print *, "------------------------" + + call solver%initialize() + print *, "✓ 求解器初始化完成" + print *, " 状态: ", solver%get_state() + print *, " 错误信息: '", trim(solver%get_error()), "'" + print *, "" + + ! 测试3: 简单运行 + print *, "3. 测试求解器运行..." + print *, "----------------------" + + call solver_run(solver, 0.05_real64) ! 运行到0.05秒 + print *, "✓ 求解器运行完成" + print *, " 最终状态: ", solver%get_state() + print *, "" + + ! 测试4: 清理 + print *, "4. 测试求解器清理..." + print *, "----------------------" + + call solver_cleanup(solver) + print *, "✓ 求解器清理完成" + print *, " 状态: ", solver%get_state() + print *, "" + + ! 测试5: 错误处理 + print *, "5. 测试错误处理..." + print *, "-------------------" + + ! 尝试重复初始化 + call solver%initialize() + print *, " 重复初始化状态: ", solver%get_state() + print *, " 错误信息: '", trim(solver%get_error()), "'" + + call solver_cleanup(solver) + print *, "" + + print *, "=== 框架测试总结 ===" + print *, "✓ 求解器创建/初始化/运行/清理流程验证完成" + print *, "✓ 状态管理正常工作" + print *, "✓ 错误处理机制就绪" + print *, "" + print *, "下一步: 添加实际数值计算功能" + +end program test_solver_framework \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03b/CMakeLists.txt new file mode 100644 index 000000000..55859dc2a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) +add_subdirectory(examples) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/README.md b/example/1d-linear-convection/weno3/fortran/registry/03b/README.md new file mode 100644 index 000000000..c4889757b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/README.md @@ -0,0 +1,221 @@ +# Fortran CFD Project + +基于工厂模式和注册系统的CFD求解器框架。 + +## 项目结构 + +``` +fortran_cfd/ +├── CMakeLists.txt # 根CMake配置 +├── scripts/ # 构建脚本 +│ ├── build.py # Python构建脚本(推荐) +│ ├── build.bat # Batch包装脚本 +│ ├── build.ps1 # PowerShell包装脚本 +│ └── requirements.txt # Python依赖 +├── src/ # 源代码 +│ ├── CMakeLists.txt +│ ├── core/ # 核心模块(注册系统、工厂接口) +│ ├── infrastructure/ # 基础设施(配置、网格) +│ └── numerics/ # 数值方法 +├── tests/ # 测试 +│ ├── CMakeLists.txt +│ ├── test_minimal_simple.f90 # 简单测试 +│ └── test_factory.f90 # 工厂模式测试 +└── README.md # 项目说明(本文件) +``` + +## 快速开始 + +### 使用Python脚本(推荐) + +```bash +# 进入脚本目录 +cd scripts + +# 基本构建 +python build.py + +# 清理并构建 +python build.py --clean + +# 发布构建 +python build.py --build-type Release --clean + +# 并行构建(使用所有核心) +python build.py -j + +# 不运行测试 +python build.py --no-tests + +# 查看帮助 +python build.py --help +``` + +### 使用批处理脚本 + +```bash +cd scripts +.\build.bat +``` + +### 使用PowerShell脚本 + +```powershell +cd scripts +.\build.ps1 +``` + +## 手动构建 + +```bash +# 设置Intel环境 +cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && powershell' + +# 配置项目 +mkdir build +cd build +cmake -G "Visual Studio 17 2022" -A x64 -T fortran=ifx .. + +# 构建项目 +cmake --build . --config Debug + +# 运行测试 +Debug\test_simple.exe +Debug\test_factory.exe +``` + +## 功能特性 + +✅ 组件注册系统 +✅ 工厂模式 +✅ ENO/WENO重构器 +✅ Rusanov通量计算器 +✅ 可扩展架构 +✅ 自动化测试 + +## 依赖 + +- Intel oneAPI(包含ifx编译器) +- CMake 3.12+ +- Python 3.6+(仅用于构建脚本) + +## 源代码文件 + +### 核心模块 +- `src/core/registry.f90` - 组件注册系统 +- `src/core/factory_interfaces.f90` - 工厂接口 + +### 基础设施 +- `src/infrastructure/config.f90` - 配置管理 +- `src/infrastructure/mesh.f90` - 网格生成 + +### 数值方法 +- `src/numerics/reconstructor/base.f90` - 重构器基类 +- `src/numerics/reconstructor/eno.f90` - ENO重构器 +- `src/numerics/reconstructor/weno3.f90` - WENO-3重构器 +- `src/numerics/flux/base.f90` - 通量计算器基类 +- `src/numerics/flux/rusanov.f90` - Rusanov通量 + +### 测试 +- `tests/test_minimal_simple.f90` - 简单功能测试 +- `tests/test_factory.f90` - 工厂模式测试 + +## 构建脚本 + +### Python脚本 (build.py) +主构建脚本,支持: +- 自动设置Intel oneAPI环境 +- 参数化构建选项 +- 并行编译 +- 自动化测试 +- 彩色输出 + +### Batch脚本 (build.bat) +Windows批处理包装器,调用Python脚本。 + +### PowerShell脚本 (build.ps1) +PowerShell包装器,调用Python脚本。 + +## 示例输出 + +成功构建后,会看到类似输出: + +``` +============================================================ + Fortran CFD Project Builder +============================================================ + +[1/4] Setting up Intel Fortran compiler... +✓ Intel oneAPI environment configured + +[2/4] Preparing build directory... +✓ Cleaned build directory + +[3/4] Configuring project... + $ cmake -G Visual Studio 17 2022 -A x64 -T fortran=ifx -DCMAKE_BUILD_TYPE=Debug .. +✓ CMake configuration successful + +[4/4] Building project... + $ cmake --build . --config Debug +✓ Build completed in 12.3 seconds + +============================================================ + Running Tests +============================================================ + +[TEST] Simple functionality test... +✓ Simple test passed + +[TEST] Factory pattern test... +✓ Factory test passed + +============================================================ + Build Summary +============================================================ +✓ Build directory: D:\path\to\build +✓ Build type: Debug +✓ Total time: 15.2s +``` + +## 开发指南 + +### 添加新组件 + +1. 在相应模块中创建Fortran源文件 +2. 实现工厂函数 +3. 使用`register_component_with_factory`注册组件 +4. 添加测试 + +### 扩展架构 + +项目采用模块化设计: +1. **注册系统** - 管理所有可用的组件 +2. **工厂模式** - 动态创建组件实例 +3. **抽象接口** - 确保组件兼容性 +4. **配置驱动** - 通过配置文件控制行为 + +## 故障排除 + +### 常见问题 + +1. **Intel环境未找到** + - 检查Intel oneAPI安装路径 + - 手动运行`setvars.bat` + +2. **CMake配置失败** + - 确保使用正确的生成器:`Visual Studio 17 2022` + - 检查Fortran编译器是否可用 + +3. **构建失败** + - 检查依赖项是否完整 + - 查看详细的错误信息 + +### 调试建议 + +- 使用`--verbose`参数获取详细输出 +- 检查`build/CMakeCache.txt`了解配置详情 +- 查看编译器错误日志 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/examples/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03b/examples/CMakeLists.txt new file mode 100644 index 000000000..9207e0f47 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/examples/CMakeLists.txt @@ -0,0 +1,22 @@ +# examples/CMakeLists.txt +message(STATUS "配置示例程序...") + +# 主示例程序:ENO/WENO对比 +add_executable(example_eno_weno_comparison + run_eno_weno.f90 +) + +target_link_libraries(example_eno_weno_comparison + PRIVATE + solver + infrastructure + core + physics + manager +) + +install(TARGETS example_eno_weno_comparison + RUNTIME DESTINATION bin/examples +) + +message(STATUS "示例程序配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/examples/run_eno_weno.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/examples/run_eno_weno.f90 new file mode 100644 index 000000000..e96e50b06 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/examples/run_eno_weno.f90 @@ -0,0 +1,318 @@ +! examples/run_eno_weno.f90 +program run_eno_weno + ! 示例程序:ENO/WENO对比分析 + ! 对应Julia版本的功能 + + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction, config_print + use mesh_module, only: mesh_type + use registry_module, only: registry_init, registry_cleanup, initialize_default_components + use solver_base_module, only: SOLVER_INITIALIZED, SOLVER_COMPLETED, SOLVER_ERROR + use physics_solver_module, only: physics_solver + + implicit none + + ! 求解器实例 + type(cfd_config) :: config_eno3, config_weno3, config_weno5 + type(mesh_type) :: mesh + type(physics_solver) :: solver_eno3, solver_weno3, solver_weno5 + + character(len=100) :: status_str + integer :: step_count + logical :: all_success ! 在声明部分声明变量 + + print *, "==========================================" + print *, " ENO/WENO对比分析 - Fortran版本" + print *, " 对应Julia examples/run_eno_weno.jl" + print *, "==========================================" + print *, "" + + ! 步骤0: 系统初始化 + print *, "[步骤0] 初始化系统..." + print *, "---------------------" + + ! 初始化注册系统 + call registry_init(verbose=.true.) + + ! 注册默认组件(关键:必须注册才能创建) + print *, "注册默认组件..." + call initialize_default_components() + print *, "" + + ! 步骤1: 创建网格 + print *, "[步骤1] 创建计算网格..." + print *, "------------------------" + + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=40) + call mesh%print_info() + print *, "" + + ! ========== ENO3 求解器 ========== + print *, "[步骤2] 配置并运行ENO3求解器..." + print *, "--------------------------------" + + ! 配置ENO3 + config_eno3%verbose = .true. + config_eno3%ic_type = "step" + config_eno3%wave_speed = 1.0_wp + config_eno3%final_time = 0.625_wp + config_eno3%dt = 0.0025_wp + config_eno3%rk_order = 2 + config_eno3%boundary_type = "periodic" + config_eno3%equation_type = "linear_advection" + config_eno3%problem_type = "linear_advection" + config_eno3%enable_physics = .true. + + call config_with_reconstruction(config_eno3, "eno", 3) + + print *, "ENO3配置信息:" + call config_print(config_eno3) + print *, "" + + ! 创建并运行ENO3求解器 + print *, "创建ENO3求解器实例..." + solver_eno3 = physics_solver(config_eno3, mesh) + + print *, "初始化ENO3求解器..." + call solver_eno3%initialize() + + print *, "运行ENO3求解器..." + step_count = 0 + call solver_eno3%run_to_time(config_eno3%final_time) + + ! 检查ENO3状态 + print *, "检查ENO3求解器状态..." + if (solver_eno3%get_state() == SOLVER_COMPLETED) then + print *, "✓ ENO3求解器运行完成" + print *, " 最终时间: ", solver_eno3%current_time + print *, " 总步数: ", solver_eno3%current_step + print *, " 错误信息: '", trim(solver_eno3%get_error()), "'" + else if (solver_eno3%get_state() == SOLVER_ERROR) then + print *, "✗ ENO3求解器运行失败" + print *, " 错误信息: ", trim(solver_eno3%get_error()) + else + print *, "? ENO3求解器状态: ", solver_eno3%get_state() + end if + print *, "" + + ! ========== WENO3 求解器 ========== + print *, "[步骤3] 配置并运行WENO3求解器..." + print *, "---------------------------------" + + ! 配置WENO3 + config_weno3%verbose = .true. + config_weno3%ic_type = "step" + config_weno3%wave_speed = 1.0_wp + config_weno3%final_time = 0.625_wp + config_weno3%dt = 0.0025_wp + config_weno3%rk_order = 2 + config_weno3%boundary_type = "periodic" + config_weno3%equation_type = "linear_advection" + config_weno3%problem_type = "linear_advection" + config_weno3%enable_physics = .true. + + call config_with_reconstruction(config_weno3, "weno3", 3) + + print *, "WENO3配置信息:" + call config_print(config_weno3) + print *, "" + + ! 创建并运行WENO3求解器 + print *, "创建WENO3求解器实例..." + solver_weno3 = physics_solver(config_weno3, mesh) + + print *, "初始化WENO3求解器..." + call solver_weno3%initialize() + + print *, "运行WENO3求解器..." + call solver_weno3%run_to_time(config_weno3%final_time) + + ! 检查WENO3状态 + print *, "检查WENO3求解器状态..." + if (solver_weno3%get_state() == SOLVER_COMPLETED) then + print *, "✓ WENO3求解器运行完成" + print *, " 最终时间: ", solver_weno3%current_time + print *, " 总步数: ", solver_weno3%current_step + print *, " 错误信息: '", trim(solver_weno3%get_error()), "'" + else if (solver_weno3%get_state() == SOLVER_ERROR) then + print *, "✗ WENO3求解器运行失败" + print *, " 错误信息: ", trim(solver_weno3%get_error()) + else + print *, "? WENO3求解器状态: ", solver_weno3%get_state() + end if + print *, "" + + ! ========== WENO5 求解器 ========== + print *, "[步骤4] 配置并运行WENO5求解器..." + print *, "---------------------------------" + + ! 配置WENO5 + config_weno5%verbose = .true. + config_weno5%ic_type = "step" + config_weno5%wave_speed = 1.0_wp + config_weno5%final_time = 0.625_wp + config_weno5%dt = 0.0025_wp + config_weno5%rk_order = 2 + config_weno5%boundary_type = "periodic" + config_weno5%equation_type = "linear_advection" + config_weno5%problem_type = "linear_advection" + config_weno5%enable_physics = .true. + + call config_with_reconstruction(config_weno5, "weno", 5) + + print *, "WENO5配置信息:" + call config_print(config_weno5) + print *, "" + + ! 创建并运行WENO5求解器 + print *, "创建WENO5求解器实例..." + solver_weno5 = physics_solver(config_weno5, mesh) + + print *, "初始化WENO5求解器..." + call solver_weno5%initialize() + + print *, "运行WENO5求解器..." + call solver_weno5%run_to_time(config_weno5%final_time) + + ! 检查WENO5状态 + print *, "检查WENO5求解器状态..." + if (solver_weno5%get_state() == SOLVER_COMPLETED) then + print *, "✓ WENO5求解器运行完成" + print *, " 最终时间: ", solver_weno5%current_time + print *, " 总步数: ", solver_weno5%current_step + print *, " 错误信息: '", trim(solver_weno5%get_error()), "'" + else if (solver_weno5%get_state() == SOLVER_ERROR) then + print *, "✗ WENO5求解器运行失败" + print *, " 错误信息: ", trim(solver_weno5%get_error()) + else + print *, "? WENO5求解器状态: ", solver_weno5%get_state() + end if + print *, "" + + ! ========== 结果汇总 ========== + print *, "[步骤5] 结果汇总..." + print *, "--------------------" + print *, "" + + print *, "求解器状态对比:" + print *, "---------------" + + ! ENO3状态 + if (solver_eno3%get_state() == SOLVER_COMPLETED) then + status_str = "完成 ✓" + else if (solver_eno3%get_state() == SOLVER_INITIALIZED) then + status_str = "已初始化" + else if (solver_eno3%get_state() == SOLVER_ERROR) then + status_str = "错误 ✗" + else + status_str = "未知状态" + end if + print *, "ENO3: ", trim(status_str) + print *, " 最终时间: ", solver_eno3%current_time + print *, " 总步数: ", solver_eno3%current_step + print *, "" + + ! WENO3状态 + if (solver_weno3%get_state() == SOLVER_COMPLETED) then + status_str = "完成 ✓" + else if (solver_weno3%get_state() == SOLVER_INITIALIZED) then + status_str = "已初始化" + else if (solver_weno3%get_state() == SOLVER_ERROR) then + status_str = "错误 ✗" + else + status_str = "未知状态" + end if + print *, "WENO3: ", trim(status_str) + print *, " 最终时间: ", solver_weno3%current_time + print *, " 总步数: ", solver_weno3%current_step + print *, "" + + ! WENO5状态 + if (solver_weno5%get_state() == SOLVER_COMPLETED) then + status_str = "完成 ✓" + else if (solver_weno5%get_state() == SOLVER_INITIALIZED) then + status_str = "已初始化" + else if (solver_weno5%get_state() == SOLVER_ERROR) then + status_str = "错误 ✗" + else + status_str = "未知状态" + end if + print *, "WENO5: ", trim(status_str) + print *, " 最终时间: ", solver_weno5%current_time + print *, " 总步数: ", solver_weno5%current_step + print *, "" + + ! ========== 清理 ========== + print *, "[步骤6] 系统清理..." + print *, "-------------------" + + call solver_eno3%cleanup() + call solver_weno3%cleanup() + call solver_weno5%cleanup() + call registry_cleanup() + + print *, "" + print *, "==========================================" + print *, " 分析完成" + print *, "==========================================" + print *, "" + + ! 总结所有求解器状态 + all_success = (solver_eno3%get_state() == SOLVER_COMPLETED) .and. & + (solver_weno3%get_state() == SOLVER_COMPLETED) .and. & + (solver_weno5%get_state() == SOLVER_COMPLETED) + + if (all_success) then + print *, "✓ 所有求解器成功运行!" + print *, "" + print *, "计算参数总结:" + print *, "--------------" + print *, "网格单元数: ", mesh%ncells + print *, "时间步长: ", config_eno3%dt + print *, "最终时间: ", config_eno3%final_time + print *, "波速: ", config_eno3%wave_speed + print *, "边界条件: ", trim(config_eno3%boundary_type) + print *, "初始条件: ", trim(config_eno3%ic_type) + print *, "" + print *, "重构格式对比:" + print *, "--------------" + print *, "ENO3: 3阶本质无振荡" + print *, "WENO3: 3阶加权本质无振荡" + print *, "WENO5: 5阶加权本质无振荡" + print *, "" + print *, "下一步开发计划:" + print *, "1. 添加边界条件模块实现" + print *, "2. 完善ENO/WENO重构算法" + print *, "3. 实现Rusanov通量计算" + print *, "4. 添加RK时间积分器" + print *, "5. 实现结果输出和可视化" + else + print *, "✗ 有求解器运行失败" + print *, "" + print *, "故障排除:" + print *, "----------" + + if (solver_eno3%get_state() /= SOLVER_COMPLETED) then + print *, "• ENO3失败: ", trim(solver_eno3%get_error()) + end if + + if (solver_weno3%get_state() /= SOLVER_COMPLETED) then + print *, "• WENO3失败: ", trim(solver_weno3%get_error()) + end if + + if (solver_weno5%get_state() /= SOLVER_COMPLETED) then + print *, "• WENO5失败: ", trim(solver_weno5%get_error()) + end if + + print *, "" + print *, "可能的原因:" + print *, "1. 组件注册不完整" + print *, "2. 重构器工厂配置错误" + print *, "3. 物理模块初始化失败" + print *, "4. 内存分配问题" + end if + + print *, "" + print *, "==========================================" + +end program run_eno_weno \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/scripts/build.bat b/example/1d-linear-convection/weno3/fortran/registry/03b/scripts/build.bat new file mode 100644 index 000000000..6fd6dc031 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/scripts/build.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Project Builder +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python build script with full Intel environment support... +echo. + +python build.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Build failed + pause + exit /b 1 +) + +echo. +echo [INFO] Build completed successfully! +echo. +echo [INFO] To run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/scripts/build.py b/example/1d-linear-convection/weno3/fortran/registry/03b/scripts/build.py new file mode 100644 index 000000000..3bf6d5375 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/scripts/build.py @@ -0,0 +1,629 @@ +#!/usr/bin/env python3 +""" +Fortran CFD Project Builder - 完整Python解决方案 +在Python内部处理Intel oneAPI环境配置 +""" + +import os +import sys +import subprocess +import shutil +import argparse +import time +import platform +import tempfile +from pathlib import Path + +class IntelEnvironment: + """Intel oneAPI环境管理器""" + + def __init__(self): + self.setvars_path = None + self.env_vars = {} + + def find_setvars(self): + """查找setvars.bat文件""" + possible_paths = [ + r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat", + r"C:\Program Files\Intel\oneAPI\setvars.bat", + r"C:\Program Files (x86)\Intel\oneAPI\compiler\latest\env\vars.bat", + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\setvars.bat"), + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\compiler\latest\env\vars.bat"), + ] + + for path in possible_paths: + if os.path.exists(path): + self.setvars_path = path + return True + + return False + + def setup_environment(self): + """设置Intel环境""" + if not self.find_setvars(): + return False + + try: + # 创建临时的批处理文件来捕获环境变量 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + f.write(f'@echo off\n') + f.write(f'call "{self.setvars_path}" >nul 2>&1\n') + f.write(f'set\n') # 输出所有环境变量 + temp_bat = f.name + + # 运行批处理文件并捕获输出 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + shell=True + ) + + # 解析环境变量 + for line in result.stdout.split('\n'): + line = line.strip() + if '=' in line: + key, value = line.split('=', 1) + self.env_vars[key.strip()] = value.strip() + + # 清理临时文件 + os.unlink(temp_bat) + + # 更新当前进程的环境变量 + os.environ.update(self.env_vars) + + return True + + except Exception as e: + print(f"设置Intel环境失败: {e}") + return False + + def get_compiler_info(self): + """获取编译器信息""" + info = {} + + # 检查ifx编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + env={**os.environ, **self.env_vars} if self.env_vars else os.environ + ) + + if result.returncode == 0: + for line in result.stdout.split('\n'): + if 'Version' in line or '版本' in line: + info['ifx_version'] = line.strip() + break + except: + pass + + # 检查环境变量 + info['ifx_root'] = self.env_vars.get('IFX_ROOT', '') + info['compiler_root'] = self.env_vars.get('ONEAPI_ROOT', '') + + return info + +class BuildSystem: + """构建系统主类""" + + def __init__(self): + self.project_root = Path(__file__).parent.parent + self.build_dir = self.project_root / "build" + self.intel_env = IntelEnvironment() + + # 设置控制台编码 + if sys.platform == "win32": + try: + import ctypes + # 设置控制台输出为UTF-8 + ctypes.windll.kernel32.SetConsoleOutputCP(65001) + except: + pass + + def print_header(self, text): + """打印标题""" + print(f"\n{'='*70}") + print(f" {text}") + print(f"{'='*70}\n") + + def print_step(self, step, total, message): + """打印步骤""" + print(f"[{step}/{total}] {message}...") + + def print_success(self, message): + """打印成功""" + print(f"\033[92m✓ {message}\033[0m") + + def print_error(self, message): + """打印错误""" + print(f"\033[91m✗ {message}\033[0m") + + def print_warning(self, message): + """打印警告""" + print(f"\033[93m! {message}\033[0m") + + def print_info(self, message): + """打印信息""" + print(f"\033[94mℹ {message}\033[0m") + + def check_prerequisites(self): + """检查前提条件""" + self.print_step(1, 6, "检查前提条件") + + # 检查Python版本 + python_version = sys.version.split()[0] + self.print_info(f"Python版本: {python_version}") + + # 检查平台 + self.print_info(f"平台: {platform.system()} {platform.release()}") + self.print_info(f"处理器核心数: {os.cpu_count()}") + + # 检查CMake + try: + result = subprocess.run( + ["cmake", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + version_line = result.stdout.split('\n')[0] + self.print_success(f"CMake: {version_line}") + else: + self.print_error("CMake未找到") + return False + except FileNotFoundError: + self.print_error("CMake未安装") + return False + + return True + + def setup_intel_environment(self, args): + """设置Intel环境""" + self.print_step(2, 6, "配置Intel oneAPI环境") + + if not self.intel_env.find_setvars(): + self.print_warning("未找到Intel oneAPI setvars.bat") + self.print_info("将尝试使用系统环境中的编译器") + + # 检查是否能直接访问编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + self.print_success("Intel编译器在系统PATH中找到") + return True + else: + self.print_warning("Intel编译器未在PATH中找到") + except: + self.print_warning("无法访问Intel编译器") + + return True # 继续,让CMake自己找编译器 + + # 设置环境 + if self.intel_env.setup_environment(): + compiler_info = self.intel_env.get_compiler_info() + + if compiler_info.get('ifx_version'): + self.print_success(f"Intel Fortran编译器: {compiler_info['ifx_version']}") + elif compiler_info.get('ifx_root'): + self.print_success(f"Intel编译器路径: {compiler_info['ifx_root']}") + else: + self.print_success("Intel oneAPI环境配置完成") + + return True + else: + self.print_warning("Intel环境配置失败,将继续使用系统环境") + return True + + def clean_build_directory(self, args): + """清理构建目录""" + if args.clean and self.build_dir.exists(): + self.print_info("清理构建目录...") + try: + shutil.rmtree(self.build_dir) + self.print_success("构建目录已清理") + except Exception as e: + self.print_error(f"清理失败: {e}") + if not args.force: + return False + return True + + def run_command(self, cmd, cwd=None, check=True, env=None): + """运行命令""" + if isinstance(cmd, list): + cmd_str = ' '.join(str(c) for c in cmd if c) + else: + cmd_str = str(cmd) + + print(f" \033[96m$\033[0m {cmd_str}") + + try: + # 合并环境变量 + exec_env = os.environ.copy() + if env: + exec_env.update(env) + if self.intel_env.env_vars: + exec_env.update(self.intel_env.env_vars) + + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=False, + env=exec_env + ) + + # 处理输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or '完成' in line or '生成' in line: + print(f" \033[92m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + if check and result.returncode != 0: + self.print_error(f"命令执行失败,退出码: {result.returncode}") + return False + + return True + + except Exception as e: + self.print_error(f"命令执行异常: {e}") + return False + + def configure_cmake(self, args): + """配置CMake""" + self.print_step(3, 6, "配置CMake项目") + + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + f"-DCMAKE_BUILD_TYPE={args.build_type}", + ] + + if args.compiler == "ifx": + cmake_cmd.extend(["-T", "fortran=ifx"]) + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + success = self.run_command(cmake_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("CMake配置完成") + else: + self.print_error("CMake配置失败") + + return success + + def build_project(self, args): + """构建项目""" + self.print_step(4, 6, "构建项目") + + build_cmd = [ + "cmake", + "--build", ".", + "--config", args.build_type, + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + success = self.run_command(build_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("项目构建完成") + else: + self.print_error("构建失败") + + return success + + def run_tests_with_environment(self, test_exe): + """运行单个测试,确保有Intel环境""" + try: + # 创建临时的批处理文件来运行测试 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'"{test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'"{test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + return result + + except Exception as e: + print(f"运行测试失败: {e}") + return None + + def run_tests(self, args): + """运行测试""" + self.print_step(5, 6, "运行测试") + + # 查找测试可执行文件 + test_dir = self.build_dir / "bin" / args.build_type + if not test_dir.exists(): + test_dir = self.build_dir / "bin" + if not test_dir.exists(): + test_dir = self.build_dir + + test_files = list(test_dir.glob("test_*.exe")) + + if not test_files: + self.print_warning("未找到测试程序") + return True + + all_passed = True + + for test_exe in sorted(test_files): + test_name = test_exe.stem + self.print_info(f"运行测试: {test_name}") + print(f" {'-'*50}") + + # 运行测试 + result = self.run_tests_with_environment(str(test_exe)) + + if result is None: + self.print_error(f" {test_name} 运行失败") + all_passed = False + continue + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + print(f" {line}") + + if result.returncode == 0: + self.print_success(f" {test_name} 通过") + else: + self.print_error(f" {test_name} 失败 (退出码: {result.returncode})") + all_passed = False + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + print() # 空行 + + return all_passed + + def create_test_runner(self, args): + """创建独立的测试运行器""" + self.print_step(6, 6, "创建测试运行器") + + runner_path = self.build_dir / "run_tests.bat" + + content = f'''@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Test Runner +echo ======================================== +echo. + +REM Setup Intel oneAPI environment +set "SETVARS_PATH={self.intel_env.setvars_path or ''}" +if exist "%SETVARS_PATH%" ( + call "%SETVARS_PATH%" >nul + echo [INFO] Intel environment configured +) else ( + echo [WARNING] Intel environment not found + echo [WARNING] Tests may fail without runtime libraries +) + +echo. + +REM Run all test executables +set "TEST_COUNT=0" +set "PASS_COUNT=0" + +for %%f in ("bin\\{args.build_type}\\test_*.exe") do ( + set /a TEST_COUNT+=1 + echo [TEST %%f] + echo {'-'*50} + + %%f + if errorlevel 1 ( + echo [FAILED] %%f + ) else ( + echo [PASSED] %%f + set /a PASS_COUNT+=1 + ) + echo. +) + +echo ======================================== +echo Tests: %PASS_COUNT%/%TEST_COUNT% passed +if %PASS_COUNT% equ %TEST_COUNT% ( + echo [SUCCESS] All tests passed! +) else ( + echo [FAILURE] Some tests failed +) +echo ======================================== + +pause +''' + + with open(runner_path, 'w', encoding='utf-8') as f: + f.write(content) + + self.print_success(f"测试运行器已创建: {runner_path}") + self.print_info(f"使用方法: cd build && run_tests.bat") + + return runner_path + + def generate_report(self, args, build_time, tests_passed): + """生成构建报告""" + self.print_header("构建完成") + + print(f"项目: {self.project_root.name}") + print(f"构建类型: {args.build_type}") + print(f"编译器: {args.compiler}") + print(f"并行作业: {args.jobs}") + print(f"总耗时: {build_time:.1f}秒") + print(f"测试结果: {'全部通过' if tests_passed else '有失败'}") + + # 显示生成的可执行文件 + bin_dir = self.build_dir / "bin" / args.build_type + if bin_dir.exists(): + print(f"\n生成的可执行文件:") + for exe in sorted(bin_dir.glob("*.exe")): + size_mb = exe.stat().st_size / (1024 * 1024) + print(f" • {exe.name} ({size_mb:.2f} MB)") + + # 显示测试运行器信息 + runner_path = self.build_dir / "run_tests.bat" + if runner_path.exists(): + print(f"\n独立测试运行器:") + print(f" • {runner_path.name}") + print(f" 在Intel oneAPI环境中运行所有测试") + + def run(self): + """运行构建系统""" + parser = argparse.ArgumentParser( + description="Fortran CFD项目构建工具", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认构建 + %(prog)s --clean # 清理后构建 + %(prog)s --build-type Release # Release构建 + %(prog)s --no-tests # 只构建,不运行测试 + %(prog)s -j8 --verbose # 8线程并行构建,详细输出 + """ + ) + + parser.add_argument("--build-type", choices=["Debug", "Release"], + default="Debug", help="构建类型") + parser.add_argument("--compiler", choices=["ifx", "ifort"], + default="ifx", help="Fortran编译器") + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-tests", action="store_true", + help="跳过测试") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + + args = parser.parse_args() + + # 开始构建 + start_time = time.time() + + self.print_header("Fortran CFD 项目构建系统") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 检查前提条件 + if not self.check_prerequisites(): + if not args.force: + return 1 + + # 2. 设置Intel环境 + if not self.setup_intel_environment(args): + if not args.force: + return 1 + + # 3. 清理目录 + if not self.clean_build_directory(args): + if not args.force: + return 1 + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 4. 配置CMake + if not self.configure_cmake(args): + if not args.force: + return 1 + + # 5. 构建项目 + if not self.build_project(args): + if not args.force: + return 1 + + # 6. 运行测试和创建测试运行器 + tests_passed = True + if not args.no_tests: + tests_passed = self.run_tests(args) + + # 创建测试运行器 + self.create_test_runner(args) # 传递 args 参数 + + # 7. 生成报告 + build_time = time.time() - start_time + self.generate_report(args, build_time, tests_passed) + + return 0 if tests_passed else 1 + + except KeyboardInterrupt: + self.print_error("\n构建被用户中断") + return 1 + except Exception as e: + self.print_error(f"构建过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + builder = BuildSystem() + return builder.run() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/scripts/requirements.txt b/example/1d-linear-convection/weno3/fortran/registry/03b/scripts/requirements.txt new file mode 100644 index 000000000..d21a12b45 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/scripts/requirements.txt @@ -0,0 +1,2 @@ +# 构建脚本的Python依赖(可选) +# 目前没有特殊依赖,保持空文件或添加未来可能需要的包 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/scripts/run_all_steps.bat b/example/1d-linear-convection/weno3/fortran/registry/03b/scripts/run_all_steps.bat new file mode 100644 index 000000000..d506149b2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/scripts/run_all_steps.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo CFD Project: All Steps +echo ======================================== +echo. + +echo [INFO] Starting Step 1: Physics Modules Test... +call run_step1.bat + +if errorlevel 1 ( + echo [ERROR] Step 1 failed + pause + exit /b 1 +) + +echo. +echo [INFO] Starting Step 2: Configuration Physics Update... +call run_step2.bat + +if errorlevel 1 ( + echo [ERROR] Step 2 failed + pause + exit /b 1 +) + +echo. +echo ======================================== +echo All Steps Completed Successfully! +echo ======================================== +echo. +echo [INFO] Next: Update component manager for physics support +echo [INFO] Run: run_step3.bat (to be created) +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/scripts/run_example.py b/example/1d-linear-convection/weno3/fortran/registry/03b/scripts/run_example.py new file mode 100644 index 000000000..d7c199178 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/scripts/run_example.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# scripts/run_example.py +""" +运行ENO/WENO示例程序 +""" + +import os +import sys +import subprocess +from pathlib import Path + +# 添加当前目录到路径 +sys.path.insert(0, str(Path(__file__).parent)) + +try: + from build import BuildSystem +except ImportError: + print("错误: 找不到build.py,请确保在scripts目录中运行") + sys.exit(1) + +def run_example(): + """运行示例程序""" + builder = BuildSystem() + + print("\n" + "="*70) + print(" 运行ENO/WENO对比示例程序") + print("="*70 + "\n") + + # 检查是否已构建 + exe_path = builder.build_dir / "bin" / "Debug" / "example_eno_weno_comparison.exe" + + if not exe_path.exists(): + print("示例程序未构建,先构建项目...") + print("-"*50) + + # 使用简化的构建 + result = subprocess.run( + ["python", "build.py", "--no-tests", "--clean"], + cwd=builder.project_root / "scripts", + capture_output=True, + text=True + ) + + if result.returncode != 0: + print("构建失败:") + print(result.stderr) + return False + + # 运行示例程序 + print("运行示例程序...") + print("-"*50) + + try: + result = subprocess.run( + [str(exe_path)], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace' + ) + + print(result.stdout) + + if result.stderr: + print("标准错误输出:") + print(result.stderr) + + return result.returncode == 0 + + except Exception as e: + print(f"运行示例程序失败: {e}") + return False + +def main(): + """主函数""" + success = run_example() + + if success: + print("\n" + "="*70) + print(" ✓ 示例程序运行成功") + print("="*70) + return 0 + else: + print("\n" + "="*70) + print(" ✗ 示例程序运行失败") + print("="*70) + return 1 + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/scripts/run_step1.bat b/example/1d-linear-convection/weno3/fortran/registry/03b/scripts/run_step1.bat new file mode 100644 index 000000000..0b6b1f17e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/scripts/run_step1.bat @@ -0,0 +1,39 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Step 1: Physics Modules Test +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python step1 script with full Intel environment support... +echo. + +python run_step1.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Step 1 failed + pause + exit /b 1 +) + +echo. +echo [INFO] Step 1 completed successfully! +echo. +echo [INFO] Next step: Update config to include physics settings +echo [INFO] Run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/scripts/run_step1.py b/example/1d-linear-convection/weno3/fortran/registry/03b/scripts/run_step1.py new file mode 100644 index 000000000..5e087a690 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/scripts/run_step1.py @@ -0,0 +1,319 @@ +#!/usr/bin/env python3 +""" +Step 1: Physics Modules Test +扩展build.py,专门用于测试物理模块 +""" + +import os +import sys +import subprocess +import time +from pathlib import Path + +# 添加当前目录到路径,以便导入build.py的类 +sys.path.insert(0, str(Path(__file__).parent)) + +try: + from build import IntelEnvironment, BuildSystem +except ImportError: + print("Error: Cannot import build.py. Make sure build.py is in the same directory.") + sys.exit(1) + +class Step1System(BuildSystem): + """Step 1 测试系统,继承自BuildSystem""" + + def __init__(self): + super().__init__() + self.test_name = "test_physics_minimal" + self.test_exe = None + + def find_test_executable(self): + """查找测试可执行文件""" + possible_paths = [ + self.build_dir / "bin" / "Debug" / f"{self.test_name}.exe", + self.build_dir / "Debug" / f"{self.test_name}.exe", + self.build_dir / f"{self.test_name}.exe", + self.build_dir / "bin" / f"{self.test_name}.exe", + ] + + for path in possible_paths: + if path.exists(): + self.test_exe = path + self.print_success(f"Found test executable: {path}") + return True + + # 如果没有找到,尝试搜索 + self.print_warning(f"Could not find {self.test_name}.exe") + self.print_info("Searching for test executables...") + + try: + result = subprocess.run( + ["dir", str(self.build_dir), "/s", "/b", "*.exe"], + capture_output=True, + text=True, + encoding='utf-8', + shell=True + ) + + if result.returncode == 0: + test_files = [line.strip() for line in result.stdout.split('\n') + if line and 'test_' in line.lower()] + + if test_files: + self.print_info("Found test files:") + for test_file in test_files: + self.print_info(f" {test_file}") + return False + except: + pass + + return False + + def run_test_with_intel_env(self): + """在Intel环境下运行测试""" + if not self.test_exe: + self.print_error("No test executable found") + return False + + self.print_step(1, 2, f"Running test: {self.test_exe.name}") + + try: + # 创建临时的批处理文件来运行测试(包含Intel环境) + import tempfile + + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + # 设置Intel环境 + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'echo [INFO] Intel environment configured\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'echo [WARNING] Intel environment not found\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + self.print_info(f"Command: {temp_bat}") + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower() or 'fail' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or 'pass' in line.lower() or '✓' in line: + print(f" \033[92m{line}\033[0m") + elif '=' in line or '---' in line: + print(f" \033[96m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + self.print_warning("Test stderr output:") + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + self.print_step(2, 2, "Test execution completed") + + if result.returncode == 0: + self.print_success("Test passed") + return True + else: + self.print_error(f"Test failed (exit code: {result.returncode})") + return False + + except Exception as e: + self.print_error(f"Failed to run test: {e}") + return False + + def build_project_if_needed(self, args): + """如果需要,构建项目""" + if args.no_build: + self.print_info("Skipping build (--no-build flag)") + return True + + self.print_step(1, 3, "Building project") + + # 调用父类的构建方法 + build_args = argparse.Namespace() + build_args.clean = args.clean + build_args.build_type = "Debug" + build_args.compiler = "ifx" + build_args.no_tests = True # 不运行所有测试 + build_args.jobs = os.cpu_count() + build_args.verbose = args.verbose + build_args.force = args.force + + # 清理构建目录 + if args.clean and self.build_dir.exists(): + self.print_info("Cleaning build directory...") + import shutil + try: + shutil.rmtree(self.build_dir) + self.print_success("Build directory cleaned") + except Exception as e: + self.print_error(f"Clean failed: {e}") + if not args.force: + return False + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 配置CMake + self.print_step(2, 3, "Configuring CMake") + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + "-DCMAKE_BUILD_TYPE=Debug", + "-T", "fortran=ifx", + ] + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + if not self.run_command(cmake_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + # 构建项目 + self.print_step(3, 3, "Building project") + build_cmd = [ + "cmake", + "--build", ".", + "--config", "Debug", + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + if not self.run_command(build_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + self.print_success("Build completed") + return True + + def run(self): + """运行Step 1测试""" + parser = argparse.ArgumentParser( + description="Step 1: Physics Modules Test", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认运行 + %(prog)s --clean # 清理后构建并测试 + %(prog)s --no-build # 只运行测试,不重新构建 + %(prog)s --verbose # 详细输出 + %(prog)s -j4 # 使用4个并行作业构建 + """ + ) + + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-build", action="store_true", + help="不重新构建,直接运行测试") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + + args = parser.parse_args() + + # 开始测试 + start_time = time.time() + + self.print_header("Step 1: Physics Modules Implementation Test") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 设置Intel环境 + self.print_step(1, 4, "Setting up Intel oneAPI environment") + if not self.setup_intel_environment(args): + if not args.force: + self.print_error("Intel environment setup failed") + return 1 + self.print_warning("Intel environment setup failed, continuing...") + + # 2. 构建项目(如果需要) + self.print_step(2, 4, "Building project if needed") + if not self.build_project_if_needed(args): + if not args.force: + return 1 + + # 3. 查找测试可执行文件 + self.print_step(3, 4, "Finding test executable") + if not self.find_test_executable(): + if not args.force: + return 1 + self.print_warning("Test executable not found, but continuing due to --force") + return 0 + + # 4. 运行测试 + self.print_step(4, 4, "Running physics module test") + test_passed = self.run_test_with_intel_env() + + # 生成报告 + test_time = time.time() - start_time + self.print_header("Step 1 Complete") + + print(f"测试: {'通过 ✓' if test_passed else '失败 ✗'}") + print(f"测试程序: {self.test_exe.name if self.test_exe else '未找到'}") + print(f"总耗时: {test_time:.1f}秒") + + if test_passed: + print(f"\n下一步: 更新配置以包含物理设置") + print(f"建议: 修改config.f90,添加physics相关字段") + return 0 + else: + if args.force: + self.print_warning("测试失败,但由于--force标志继续执行") + return 0 + return 1 + + except KeyboardInterrupt: + self.print_error("\n测试被用户中断") + return 1 + except Exception as e: + self.print_error(f"测试过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + system = Step1System() + return system.run() + +if __name__ == "__main__": + # 需要导入argparse + import argparse + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/scripts/run_step2.bat b/example/1d-linear-convection/weno3/fortran/registry/03b/scripts/run_step2.bat new file mode 100644 index 000000000..9c1f62de4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/scripts/run_step2.bat @@ -0,0 +1,39 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Step 2: Configuration Physics Update +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python step2 script with full Intel environment support... +echo. + +python run_step2.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Step 2 failed + pause + exit /b 1 +) + +echo. +echo [INFO] Step 2 completed successfully! +echo. +echo [INFO] Next step: Update component manager to support physics +echo [INFO] Run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/scripts/run_step2.py b/example/1d-linear-convection/weno3/fortran/registry/03b/scripts/run_step2.py new file mode 100644 index 000000000..c16b76088 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/scripts/run_step2.py @@ -0,0 +1,284 @@ +#!/usr/bin/env python3 +""" +Step 2: Configuration Physics Update +测试配置模块的物理功能更新 +""" + +import os +import sys +import subprocess +import time +from pathlib import Path + +# 添加当前目录到路径,以便导入build.py的类 +sys.path.insert(0, str(Path(__file__).parent)) + +try: + from build import IntelEnvironment, BuildSystem +except ImportError: + print("Error: Cannot import build.py. Make sure build.py is in the same directory.") + sys.exit(1) + +class Step2System(BuildSystem): + """Step 2 测试系统,继承自BuildSystem""" + + def __init__(self): + super().__init__() + self.test_name = "test_config_physics" + self.test_exe = None + + def find_test_executable(self): + """查找测试可执行文件""" + possible_paths = [ + self.build_dir / "bin" / "Debug" / f"{self.test_name}.exe", + self.build_dir / "Debug" / f"{self.test_name}.exe", + self.build_dir / f"{self.test_name}.exe", + self.build_dir / "bin" / f"{self.test_name}.exe", + ] + + for path in possible_paths: + if path.exists(): + self.test_exe = path + self.print_success(f"Found test executable: {path}") + return True + + return False + + def run_test_with_intel_env(self): + """在Intel环境下运行测试""" + if not self.test_exe: + self.print_error("No test executable found") + return False + + self.print_step(1, 2, f"Running test: {self.test_exe.name}") + + try: + # 创建临时的批处理文件来运行测试(包含Intel环境) + import tempfile + + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + # 设置Intel环境 + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'echo [INFO] Intel environment configured\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'echo [WARNING] Intel environment not found\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + self.print_info(f"Command: {temp_bat}") + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower() or 'fail' in line.lower() or '✗' in line: + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or 'pass' in line.lower() or '✓' in line: + print(f" \033[92m{line}\033[0m") + elif '=' in line or '---' in line or '===' in line: + print(f" \033[96m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + self.print_warning("Test stderr output:") + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + self.print_step(2, 2, "Test execution completed") + + if result.returncode == 0: + self.print_success("Test passed") + return True + else: + self.print_error(f"Test failed (exit code: {result.returncode})") + return False + + except Exception as e: + self.print_error(f"Failed to run test: {e}") + return False + + def build_project(self, args): + """构建项目""" + if args.no_build: + self.print_info("Skipping build (--no-build flag)") + return True + + self.print_step(1, 3, "Building project") + + # 清理构建目录 + if args.clean and self.build_dir.exists(): + self.print_info("Cleaning build directory...") + import shutil + try: + shutil.rmtree(self.build_dir) + self.print_success("Build directory cleaned") + except Exception as e: + self.print_error(f"Clean failed: {e}") + if not args.force: + return False + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 配置CMake + self.print_step(2, 3, "Configuring CMake") + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + "-DCMAKE_BUILD_TYPE=Debug", + "-T", "fortran=ifx", + ] + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + if not self.run_command(cmake_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + # 构建项目 + self.print_step(3, 3, "Building project") + build_cmd = [ + "cmake", + "--build", ".", + "--config", "Debug", + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + if not self.run_command(build_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + self.print_success("Build completed") + return True + + def run(self): + """运行Step 2测试""" + import argparse + + parser = argparse.ArgumentParser( + description="Step 2: Configuration Physics Update", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认运行 + %(prog)s --clean # 清理后构建并测试 + %(prog)s --no-build # 只运行测试,不重新构建 + %(prog)s --verbose # 详细输出 + """ + ) + + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-build", action="store_true", + help="不重新构建,直接运行测试") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + + args = parser.parse_args() + + # 开始测试 + start_time = time.time() + + self.print_header("Step 2: Configuration Physics Update") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 设置Intel环境 + self.print_step(1, 4, "Setting up Intel oneAPI environment") + if not self.setup_intel_environment(args): + if not args.force: + self.print_error("Intel environment setup failed") + return 1 + self.print_warning("Intel environment setup failed, continuing...") + + # 2. 构建项目(如果需要) + self.print_step(2, 4, "Building project if needed") + if not self.build_project(args): + if not args.force: + return 1 + + # 3. 查找测试可执行文件 + self.print_step(3, 4, "Finding test executable") + if not self.find_test_executable(): + self.print_error(f"Test executable {self.test_name}.exe not found") + if not args.force: + return 1 + self.print_warning("Test executable not found, but continuing due to --force") + return 0 + + # 4. 运行测试 + self.print_step(4, 4, "Running configuration physics test") + test_passed = self.run_test_with_intel_env() + + # 生成报告 + test_time = time.time() - start_time + self.print_header("Step 2 Complete") + + print(f"测试: {'通过 ✓' if test_passed else '失败 ✗'}") + print(f"测试程序: {self.test_exe.name if self.test_exe else '未找到'}") + print(f"总耗时: {test_time:.1f}秒") + + if test_passed: + print(f"\n下一步: 更新组件管理器以支持物理模块") + print(f"建议: 修改component_manager.f90,添加physics组件创建") + return 0 + else: + if args.force: + self.print_warning("测试失败,但由于--flag继续执行") + return 0 + return 1 + + except KeyboardInterrupt: + self.print_error("\n测试被用户中断") + return 1 + except Exception as e: + self.print_error(f"测试过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + system = Step2System() + return system.run() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03b/src/CMakeLists.txt new file mode 100644 index 000000000..59ad3c3f2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/CMakeLists.txt @@ -0,0 +1,31 @@ +# ==================== src\CMakeLists.txt ==================== + +# src/CMakeLists.txt +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(base) +add_subdirectory(core) +add_subdirectory(infrastructure) +add_subdirectory(numerics) +add_subdirectory(physics) # ← 新增物理模块目录 +add_subdirectory(manager) +add_subdirectory(solver) + +message(STATUS "源代码目录配置完成") + +add_executable(run_eno_weno + ${CMAKE_CURRENT_SOURCE_DIR}/run_eno_weno.f90 +) + +target_link_libraries(run_eno_weno + PRIVATE + solver + infrastructure + core + physics + manager +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/base/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03b/src/base/CMakeLists.txt new file mode 100644 index 000000000..74f4aa65f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/base/CMakeLists.txt @@ -0,0 +1,16 @@ +# src/base/CMakeLists.txt +message(STATUS "Configuring base module...") + +add_library(base STATIC + modules.f90 + precision.f90 # 新增 +) + +set_target_properties(base PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Base module configured") + +# src/core/CMakeLists.txt +message(STATUS "Configuring core module...") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/base/modules.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/src/base/modules.f90 new file mode 100644 index 000000000..43aaee241 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/base/modules.f90 @@ -0,0 +1,36 @@ +! src/base/modules.f90 +module base_modules + use, intrinsic :: iso_fortran_env, only: real64, int32 + implicit none + + public :: wp, ip, max_name_len, string_len, cfd_config_base, component_info + + integer, parameter :: wp = real64 + integer, parameter :: ip = int32 + integer, parameter :: string_len = 100 + integer, parameter :: max_name_len = 32 + + ! 基础配置类型 + type :: cfd_config_base + character(len=max_name_len) :: ic_type = "step" + character(len=max_name_len) :: recon_scheme = "eno" + character(len=max_name_len) :: flux_type = "rusanov" + integer(ip) :: rk_order = 1 + real(wp) :: wave_speed = 1.0_wp + real(wp) :: final_time = 0.625_wp + real(wp) :: dt = 0.025_wp + character(len=max_name_len) :: boundary_type = "periodic" + integer(ip) :: spatial_order = 2 + character(len=max_name_len) :: equation_type = "linear_advection" + character(len=max_name_len) :: problem_type = "linear_advection" + logical :: verbose = .true. + end type cfd_config_base + + ! 组件信息类型 + type :: component_info + character(len=max_name_len) :: category = "" + character(len=max_name_len) :: name = "" + integer(ip) :: order = 0 + end type component_info + +end module base_modules \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/base/precision.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/src/base/precision.f90 new file mode 100644 index 000000000..4ac5fd7ef --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/base/precision.f90 @@ -0,0 +1,9 @@ +! src/base/precision.f90(简单版本) +module precision_module + use base_modules, only: wp, ip + implicit none + + ! 重新导出,确保兼容 + public :: wp, ip + +end module precision_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03b/src/core/CMakeLists.txt new file mode 100644 index 000000000..d8b8df064 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/core/CMakeLists.txt @@ -0,0 +1,14 @@ +# src/core/CMakeLists.txt +message(STATUS "Configuring core module...") + +add_library(core STATIC + registry.f90 +) + +target_link_libraries(core PRIVATE base) + +set_target_properties(core PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Core module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/core/factory_base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/src/core/factory_base.f90 new file mode 100644 index 000000000..302418a15 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/core/factory_base.f90 @@ -0,0 +1,57 @@ +! src/core/factory_base.f90 +module factory_base_module + use base_modules, only: wp, ip + use registry_module, only: create_component, has_component + + implicit none + private + public :: wp, ip, factory_base, factory_create + + ! 工厂基类 + type :: factory_base + character(len=max_name_length) :: category = "" + contains + procedure :: create => factory_base_create + procedure :: get_available => factory_base_get_available + end type factory_base + + ! 便捷函数类型 + abstract interface + function factory_function_interface(category, name) result(instance) + import :: wp + character(len=*), intent(in) :: category, name + class(*), allocatable :: instance + end function factory_function_interface + end interface + +contains + + ! 创建工厂实例 + function factory_create(category) result(factory) + character(len=*), intent(in) :: category + type(factory_base) :: factory + factory%category = trim(category) + end function factory_create + + ! 工厂创建方法 + function factory_base_create(this, name) result(instance) + class(factory_base), intent(in) :: this + character(len=*), intent(in) :: name + class(*), allocatable :: instance + + instance = create_component(this%category, name) + end function factory_base_create + + ! 获取可用组件列表(简化版) + subroutine factory_base_get_available(this, names, count) + class(factory_base), intent(in) :: this + character(len=*), allocatable, intent(out) :: names(:) + integer(ip), intent(out) :: count + + ! 这里需要实现从注册表获取列表的逻辑 + ! 暂时返回空列表 + count = 0 + allocate(character(len=max_name_length) :: names(0)) + end subroutine factory_base_get_available + +end module factory_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..a960167c3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/core/factory_interfaces.f90 @@ -0,0 +1,17 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, factory_procedure + + ! Factory procedure interface + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/src/core/registry.f90 new file mode 100644 index 000000000..d155aa19b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/core/registry.f90 @@ -0,0 +1,257 @@ +! src/core/registry.f90 (更新版) +module registry_module + use base_modules, only: wp, ip, max_name_len, component_info + + implicit none + private + + ! 明确公开所有需要的接口 + public :: wp, ip ! 类型参数 + public :: component_info ! 类型 + public :: registry_init, registry_cleanup ! 初始化/清理 + public :: register_component_simple ! 注册组件 + public :: has_component_simple ! 检查组件 + public :: list_components ! 列出组件 + public :: registry_is_initialized ! 检查初始化状态 + public :: registry_get_size ! 获取大小 + public :: initialize_default_components ! 新增:初始化默认组件 + + ! 全局注册表 + type :: component_registry + type(component_info), allocatable :: components(:) + integer(ip) :: count = 0 + integer(ip) :: capacity = 100 + logical :: initialized = .false. + logical :: verbose = .true. + logical :: default_components_added = .false. ! 新增:标记是否已添加默认组件 + end type component_registry + + type(component_registry) :: registry + +contains + + ! ==================== 公共API ==================== + + subroutine registry_init(verbose) + logical, optional, intent(in) :: verbose + + if (registry%initialized) then + if (registry%verbose) then + print *, "[REGISTRY] Already initialized" + end if + return + end if + + if (present(verbose)) then + registry%verbose = verbose + end if + + allocate(registry%components(registry%capacity)) + registry%initialized = .true. + + if (registry%verbose) then + print *, "[REGISTRY] Initialized with capacity:", registry%capacity + end if + end subroutine registry_init + + subroutine registry_cleanup() + if (allocated(registry%components)) then + deallocate(registry%components) + end if + registry%initialized = .false. + registry%count = 0 + registry%default_components_added = .false. ! 重置标记 + + if (registry%verbose) then + print *, "[REGISTRY] Cleaned up" + end if + end subroutine registry_cleanup + + ! 新增:初始化默认组件 + subroutine initialize_default_components() + if (.not. registry%initialized) then + call registry_init() + end if + + if (registry%default_components_added) then + if (registry%verbose) then + print *, "[REGISTRY] Default components already added" + end if + return + end if + + ! 注册重构器 + call register_component_simple("reconstructor", "eno", order=3) + call register_component_simple("reconstructor", "weno3", order=3) + call register_component_simple("reconstructor", "weno5", order=5) + + ! 注册通量计算器 + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + + ! 注册边界条件 + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + + ! 注册时间积分器 + call register_component_simple("integrator", "rk1", order=1) + call register_component_simple("integrator", "rk2", order=2) + call register_component_simple("integrator", "rk3", order=3) + + ! 注册方程 + call register_component_simple("equation", "linear_advection") + + ! 注册问题 + call register_component_simple("problem", "linear_advection") + + ! 注册初始条件 + call register_component_simple("initial_condition", "step") + call register_component_simple("initial_condition", "sin") + call register_component_simple("initial_condition", "gaussian") + + registry%default_components_added = .true. + + if (registry%verbose) then + print *, "[REGISTRY] Default components registered" + print *, "[REGISTRY] Total components:", registry%count + end if + end subroutine initialize_default_components + + subroutine register_component_simple(category, name, order) + character(len=*), intent(in) :: category, name + integer(ip), optional, intent(in) :: order + + integer(ip) :: i + type(component_info) :: info + + if (.not. registry%initialized) then + call registry_init() + end if + + ! 检查是否已存在 + do i = 1, registry%count + if (trim(registry%components(i)%category) == trim(category) .and. & + trim(registry%components(i)%name) == trim(name)) then + if (registry%verbose) then + print *, "[WARN] Overwriting component: ", trim(category), ".", trim(name) + end if + + ! 更新 + if (present(order)) then + registry%components(i)%order = order + else + registry%components(i)%order = 0 + end if + return + end if + end do + + ! 扩展数组 + if (registry%count >= registry%capacity) then + call expand_registry() + end if + + ! 添加新组件 + registry%count = registry%count + 1 + + info%category = trim(category) + info%name = trim(name) + info%order = 0 + if (present(order)) then + info%order = order + end if + + registry%components(registry%count) = info + + if (registry%verbose) then + print *, "[OK] Registered simple: ", trim(category), ".", trim(name) + end if + end subroutine register_component_simple + + logical function has_component_simple(category, name) + character(len=*), intent(in) :: category, name + + integer(ip) :: i + + has_component_simple = .false. + + if (.not. registry%initialized) return + + do i = 1, registry%count + if (trim(registry%components(i)%category) == trim(category) .and. & + trim(registry%components(i)%name) == trim(name)) then + has_component_simple = .true. + return + end if + end do + end function has_component_simple + + subroutine list_components(category) + character(len=*), optional, intent(in) :: category + + integer(ip) :: i, count + + if (.not. registry%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + if (registry%count == 0) then + print *, "[INFO] No components registered" + return + end if + + count = 0 + print *, "=== Registry Contents ===" + do i = 1, registry%count + if (.not. present(category) .or. & + trim(registry%components(i)%category) == trim(category)) then + call print_component_info(registry%components(i)) + count = count + 1 + end if + end do + + print *, "Total:", count, "components" + print *, "==========================" + end subroutine list_components + + ! ==================== 新增函数 ==================== + + logical function registry_is_initialized() + ! 检查注册表是否已初始化 + registry_is_initialized = registry%initialized + end function registry_is_initialized + + integer(ip) function registry_get_size() + ! 获取注册表中的组件数量 + registry_get_size = registry%count + end function registry_get_size + + ! ==================== 内部辅助函数 ==================== + + subroutine expand_registry() + type(component_info), allocatable :: temp(:) + + registry%capacity = registry%capacity * 2 + allocate(temp(registry%capacity)) + temp(1:registry%count) = registry%components(1:registry%count) + call move_alloc(temp, registry%components) + + if (registry%verbose) then + print *, "[INFO] Registry expanded to capacity:", registry%capacity + end if + end subroutine expand_registry + + subroutine print_component_info(info) + type(component_info), intent(in) :: info + + if (info%order > 0) then + print *, " [", trim(info%category), ".", trim(info%name), & + " (order:", info%order, ")]" + else + print *, " [", trim(info%category), ".", trim(info%name), "]" + end if + end subroutine print_component_info + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/core/registry_initializer.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/src/core/registry_initializer.f90 new file mode 100644 index 000000000..44023d1dd --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/core/registry_initializer.f90 @@ -0,0 +1,39 @@ +! src/core/registry_initializer.f90 (新增文件) +module registry_initializer_module + use registry_module, only: register_component_simple + implicit none + private + public :: initialize_default_registry + +contains + + subroutine initialize_default_registry() + ! 注册重构器 + call register_component_simple("reconstructor", "eno", order=3) + call register_component_simple("reconstructor", "weno3", order=3) + call register_component_simple("reconstructor", "weno5", order=5) + + ! 注册通量计算器 + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + + ! 注册边界条件 + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + + ! 注册时间积分器 + call register_component_simple("integrator", "rk1", order=1) + call register_component_simple("integrator", "rk2", order=2) + call register_component_simple("integrator", "rk3", order=3) + + ! 注册方程 + call register_component_simple("equation", "linear_advection") + + ! 注册问题 + call register_component_simple("problem", "linear_advection") + + print *, "[REGISTRY] Default components registered" + end subroutine initialize_default_registry + +end module registry_initializer_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03b/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..70cbbd2f3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/infrastructure/CMakeLists.txt @@ -0,0 +1,17 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "Configuring infrastructure module...") + +add_library(infrastructure STATIC + config.f90 + mesh.f90 + domain.f90 # 新增 + solution.f90 # 新增 +) + +target_link_libraries(infrastructure PRIVATE base) + +set_target_properties(infrastructure PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Infrastructure module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/src/infrastructure/config.f90 new file mode 100644 index 000000000..7586a1a50 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/infrastructure/config.f90 @@ -0,0 +1,144 @@ +! src/infrastructure/config.f90 (修复版) +module config_module + use base_modules, only: wp, ip, max_name_len, cfd_config_base + + implicit none + public :: wp, ip, cfd_config, config_print, config_with_reconstruction + + ! 扩展配置类型 - 添加物理相关字段 + type, extends(cfd_config_base) :: cfd_config + ! 物理参数 + real(wp) :: left_boundary_value = 1.0_wp + real(wp) :: right_boundary_value = 2.0_wp + real(wp) :: domain_length = 2.0_wp + + ! 新增:物理模块相关配置 + real(wp) :: pulse_center = 0.5_wp ! 高斯脉冲中心 + real(wp) :: pulse_width = 0.1_wp ! 高斯脉冲宽度 + logical :: enable_physics = .true. ! 是否启用物理模块 + contains + ! 新增:物理相关配置方法 + procedure :: set_physics_parameters + procedure :: get_physics_info + end type cfd_config + +contains + + subroutine config_print(cfg) + type(cfd_config), intent(in) :: cfg + + print *, "=== CFD Configuration ===" + print *, "Initial condition: ", trim(cfg%ic_type) + print *, "Reconstruction: ", trim(cfg%recon_scheme), " (order:", cfg%spatial_order, ")" + print *, "Flux type: ", trim(cfg%flux_type) + print *, "Time integration: RK", cfg%rk_order + print *, "Wave speed: ", cfg%wave_speed + print *, "Final time: ", cfg%final_time + print *, "Time step: ", cfg%dt + print *, "Boundary: ", trim(cfg%boundary_type) + + ! 新增:物理配置信息 + print *, "--- Physics Configuration ---" + print *, "Equation type: ", trim(cfg%equation_type) + print *, "Problem type: ", trim(cfg%problem_type) + print *, "Domain length: ", cfg%domain_length + print *, "Physics enabled: ", cfg%enable_physics + + if (cfg%ic_type == "gaussian") then + print *, "Pulse center: ", cfg%pulse_center + print *, "Pulse width: ", cfg%pulse_width + end if + + print *, "===============================" + end subroutine config_print + + subroutine config_with_reconstruction(cfg, scheme, order) + type(cfd_config), intent(inout) :: cfg + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + integer :: i + + ! 转换为小写 + cfg%recon_scheme = scheme + do i = 1, len_trim(cfg%recon_scheme) + if (cfg%recon_scheme(i:i) >= 'A' .and. cfg%recon_scheme(i:i) <= 'Z') then + cfg%recon_scheme(i:i) = char(ichar(cfg%recon_scheme(i:i)) + 32) + end if + end do + + ! 设置阶数 + if (present(order)) then + cfg%spatial_order = order + else + if (index(cfg%recon_scheme, 'weno') > 0) then + cfg%spatial_order = 5 + else if (trim(cfg%recon_scheme) == 'eno') then + cfg%spatial_order = 3 + end if + end if + + if (cfg%verbose) then + print *, "[CONFIG] Reconstruction: ", trim(cfg%recon_scheme), & + " Order: ", cfg%spatial_order + end if + end subroutine config_with_reconstruction + + ! ========== 新增:物理参数设置方法 ========== + + subroutine set_physics_parameters(this, equation_type, problem_type, & + domain_length, enable_physics) + class(cfd_config), intent(inout) :: this + character(len=*), intent(in), optional :: equation_type, problem_type + real(wp), intent(in), optional :: domain_length + logical, intent(in), optional :: enable_physics + + if (present(equation_type)) then + this%equation_type = trim(equation_type) + if (this%verbose) then + print *, "[CONFIG] Set equation type: ", trim(this%equation_type) + end if + end if + + if (present(problem_type)) then + this%problem_type = trim(problem_type) + if (this%verbose) then + print *, "[CONFIG] Set problem type: ", trim(this%problem_type) + end if + end if + + if (present(domain_length)) then + this%domain_length = domain_length + if (this%verbose) then + print *, "[CONFIG] Set domain length: ", this%domain_length + end if + end if + + if (present(enable_physics)) then + this%enable_physics = enable_physics + if (this%verbose) then + print *, "[CONFIG] Physics module enabled: ", this%enable_physics + end if + end if + end subroutine set_physics_parameters + + subroutine get_physics_info(this) + class(cfd_config), intent(in) :: this + + print *, "=== Physics Configuration Info ===" + print *, "Equation type: ", trim(this%equation_type) + print *, "Problem type: ", trim(this%problem_type) + print *, "Domain length: ", this%domain_length + print *, "Wave speed: ", this%wave_speed + print *, "Physics enabled: ", this%enable_physics + + if (this%ic_type == "gaussian") then + print *, "Pulse parameters:" + print *, " Center: ", this%pulse_center + print *, " Width: ", this%pulse_width + end if + + print *, "==================================" + end subroutine get_physics_info + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/infrastructure/domain.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/src/infrastructure/domain.f90 new file mode 100644 index 000000000..c3662f039 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/infrastructure/domain.f90 @@ -0,0 +1,102 @@ +! src/infrastructure/domain.f90 +module domain_module + use base_modules, only: wp, ip, max_name_len + use config_module, only: cfd_config + use mesh_module, only: mesh_type + + implicit none + private + public :: wp, ip, domain_type, domain_create, is_physical_cell + + type :: domain_type + type(cfd_config), pointer :: config => null() + type(mesh_type), pointer :: mesh => null() + integer(ip) :: nghosts = 0 + integer(ip) :: ist = 1 ! 物理区域起始索引(1-based) + integer(ip) :: ied = 1 ! 物理区域结束索引(exclusive) + integer(ip) :: ntcells = 0 ! 总单元数(含ghost) + contains + procedure :: print_info => domain_print_info + procedure :: get_physical_indices => domain_get_physical_indices + end type domain_type + +contains + + function domain_create(config, mesh) result(domain) + type(cfd_config), target, intent(in) :: config + type(mesh_type), target, intent(in) :: mesh + type(domain_type) :: domain + + domain%config => config + domain%mesh => mesh + + ! 计算ghost层数(参考Julia的_calc_nghosts) + domain%nghosts = calc_nghosts(config) + domain%ist = domain%nghosts + 1 + domain%ied = domain%ist + mesh%ncells + domain%ntcells = mesh%ncells + 2 * domain%nghosts + + if (config%verbose) then + print *, "[DOMAIN] Created:" + print *, " Ghost layers: ", domain%nghosts + print *, " Physical cells: ", domain%ist, " to ", domain%ied - 1 + print *, " Total cells: ", domain%ntcells + end if + end function domain_create + + function calc_nghosts(config) result(nghosts) + type(cfd_config), intent(in) :: config + integer(ip) :: nghosts + + character(len=max_name_len) :: scheme + + scheme = config%recon_scheme + + if (scheme == "eno") then + nghosts = config%spatial_order + else if (index(scheme, "weno") > 0) then + nghosts = config%spatial_order / 2 + 1 + else + print *, "[WARNING] Unknown scheme, using default nghosts=2" + nghosts = 2 + end if + + if (nghosts <= 0) then + print *, "[ERROR] Invalid nghosts: ", nghosts + nghosts = 2 + end if + end function calc_nghosts + + logical function is_physical_cell(this, idx) + class(domain_type), intent(in) :: this + integer(ip), intent(in) :: idx + is_physical_cell = (idx >= this%ist .and. idx < this%ied) + end function is_physical_cell + + function domain_get_physical_indices(this) result(indices) + class(domain_type), intent(in) :: this + integer(ip), allocatable :: indices(:) + integer(ip) :: i, count + + count = this%ied - this%ist + allocate(indices(count)) + + do i = 1, count + indices(i) = this%ist + i - 1 + end do + end function domain_get_physical_indices + + subroutine domain_print_info(this) + class(domain_type), intent(in) :: this + + print *, "=== Domain Information ===" + print *, "Configuration: ", trim(this%config%recon_scheme), & + " order ", this%config%spatial_order + print *, "Ghost layers: ", this%nghosts + print *, "Physical cells: ", this%ist, " to ", this%ied - 1 + print *, "Total cells: ", this%ntcells + print *, "Mesh cells: ", this%mesh%ncells + print *, "==========================" + end subroutine domain_print_info + +end module domain_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..f810f3a1b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/infrastructure/mesh.f90 @@ -0,0 +1,73 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use base_modules, only: wp, ip + + implicit none + public :: wp, ip, mesh_type, mesh_init, mesh_print_info + + ! 网格类型 + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer(ip) :: ncells = 40 + integer(ip) :: nnodes + integer(ip) :: nx + real(wp), allocatable :: x(:) ! 节点坐标 + real(wp), allocatable :: xcc(:) ! 单元中心坐标 + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer(ip), optional, intent(in) :: ncells + + integer(ip) :: i + + ! 设置参数 + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! 计算 + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! 分配内存 + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! 生成节点坐标 + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! 生成单元中心坐标 + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== Mesh Information ===" + print *, "Domain: [", this%xmin, ", ", this%xmax, "]" + print *, "Cells: ", this%ncells + print *, "Nodes: ", this%nnodes + print *, "dx: ", this%dx + print *, "L: ", this%L + print *, "========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/infrastructure/solution.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/src/infrastructure/solution.f90 new file mode 100644 index 000000000..ce88fd8a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/infrastructure/solution.f90 @@ -0,0 +1,131 @@ +! src/infrastructure/solution.f90 +module solution_module + use base_modules, only: wp, ip + use domain_module, only: domain_type + + implicit none + private + public :: wp, ip, solution_type, solution_create, solution_reset + + type :: solution_type + type(domain_type), pointer :: domain => null() + real(wp), allocatable :: u(:) ! 当前解(含ghost) + real(wp), allocatable :: un(:) ! 旧解 + real(wp), allocatable :: q_face_left(:) ! 左界面值 + real(wp), allocatable :: q_face_right(:)! 右界面值 + real(wp), allocatable :: flux(:) ! 通量 + real(wp), allocatable :: res(:) ! 残差 + contains + procedure :: initialize => solution_initialize + procedure :: update_old_field => solution_update_old_field + procedure :: print_info => solution_print_info + procedure :: reset => solution_reset_instance + end type solution_type + +contains + + function solution_create(domain) result(solution) + type(domain_type), target, intent(in) :: domain + type(solution_type) :: solution + + integer(ip) :: ncells, nnodes, ntcells + + solution%domain => domain + + ncells = domain%mesh%ncells + nnodes = domain%mesh%nnodes + ntcells = domain%ntcells + + ! 分配数组(与Julia solution.jl一致) + allocate(solution%u(ntcells), source=0.0_wp) + allocate(solution%un(ntcells), source=0.0_wp) + allocate(solution%q_face_left(nnodes), source=0.0_wp) + allocate(solution%q_face_right(nnodes), source=0.0_wp) + allocate(solution%flux(nnodes), source=0.0_wp) + allocate(solution%res(ncells), source=0.0_wp) + + if (domain%config%verbose) then + print *, "[SOLUTION] Created:" + print *, " u size: ", size(solution%u), " (with ghosts)" + print *, " flux size: ", size(solution%flux) + print *, " res size: ", size(solution%res) + end if + end function solution_create + + subroutine solution_initialize(this, initial_values) + class(solution_type), intent(inout) :: this + real(wp), intent(in), optional :: initial_values(:) + + integer(ip) :: i, idx + type(domain_type), pointer :: domain + + domain => this%domain + + if (present(initial_values)) then + ! 应用初始值到物理区域 + do i = domain%ist, domain%ied - 1 + idx = i - domain%ist + 1 + if (idx <= size(initial_values)) then + this%u(i) = initial_values(idx) + end if + end do + else + ! 默认为0 + this%u = 0.0_wp + end if + + ! 同步旧场(与Julia的update_old_field一致) + call this%update_old_field() + + if (domain%config%verbose) then + print *, "[SOLUTION] Initialized" + print *, " u range: ", minval(this%u), " to ", maxval(this%u) + end if + end subroutine solution_initialize + + subroutine solution_update_old_field(this) + class(solution_type), intent(inout) :: this + this%un = this%u ! 与Julia的 un .= u 一致 + end subroutine solution_update_old_field + + subroutine solution_reset_instance(this) + class(solution_type), intent(inout) :: this + call solution_reset(this) + end subroutine solution_reset_instance + + subroutine solution_reset(solution) + type(solution_type), intent(inout) :: solution + + if (allocated(solution%u)) solution%u = 0.0_wp + if (allocated(solution%un)) solution%un = 0.0_wp + if (allocated(solution%q_face_left)) solution%q_face_left = 0.0_wp + if (allocated(solution%q_face_right)) solution%q_face_right = 0.0_wp + if (allocated(solution%flux)) solution%flux = 0.0_wp + if (allocated(solution%res)) solution%res = 0.0_wp + + if (associated(solution%domain) .and. solution%domain%config%verbose) then + print *, "[SOLUTION] Reset" + end if + end subroutine solution_reset + + subroutine solution_print_info(this) + class(solution_type), intent(in) :: this + + print *, "=== Solution Information ===" + print *, "Arrays:" + print *, " u: ", size(this%u), " elements" + print *, " un: ", size(this%un), " elements" + print *, " q_face_left: ", size(this%q_face_left), " elements" + print *, " q_face_right: ", size(this%q_face_right), " elements" + print *, " flux: ", size(this%flux), " elements" + print *, " res: ", size(this%res), " elements" + + if (allocated(this%u)) then + print *, "Values:" + print *, " u min/max: ", minval(this%u), maxval(this%u) + print *, " un min/max: ", minval(this%un), maxval(this%un) + end if + print *, "============================" + end subroutine solution_print_info + +end module solution_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/manager/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03b/src/manager/CMakeLists.txt new file mode 100644 index 000000000..00c8bf49b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/manager/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/manager/CMakeLists.txt +message(STATUS "配置管理器模块...") + +# 创建管理器库 +add_library(manager STATIC + component_manager.f90 + component_factory.f90 +) + +# 明确依赖关系:管理器依赖所有其他模块 +target_link_libraries(manager + PRIVATE + core + infrastructure + reconstructor + flux +) + +# 设置模块输出目录 +set_target_properties(manager PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "管理器模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/manager/component_factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/src/manager/component_factory.f90 new file mode 100644 index 000000000..de8cbf1a8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/manager/component_factory.f90 @@ -0,0 +1,142 @@ +! src/manager/component_factory.f90 (完整文件) +module component_factory_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use config_module, only: cfd_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + + use eno_reconstructor_module, only: eno_reconstructor, create_eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor, create_weno3_reconstructor + use weno5_reconstructor_module, only: weno5_reconstructor, create_weno5_reconstructor + use rusanov_flux_module, only: rusanov_flux, create_rusanov_flux + + implicit none + private + public :: wp, create_reconstructor, create_flux_calculator + + ! 错误代码 + integer, parameter :: CM_SUCCESS = 0 + integer, parameter :: CM_ERROR_UNKNOWN_SCHEME = 1 + integer, parameter :: CM_ERROR_UNKNOWN_FLUX = 2 + integer, parameter :: CM_ERROR_INVALID_ORDER = 3 + +contains + + ! ==================== 重构器创建 ==================== + + function create_reconstructor(config, status) result(recon) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(reconstructor_base), allocatable :: recon + + character(len=20) :: scheme + integer :: order, error_code + + scheme = trim(adjustl(config%recon_scheme)) + order = config%spatial_order + + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating reconstructor: ", scheme, " order=", order + end if + + ! 处理"weno"作为WENO5的别名(与Julia一致) + if (scheme == "weno" .and. order == 5) then + scheme = "weno5" + end if + + select case(scheme) + case('eno') + allocate(eno_reconstructor :: recon) + select type(recon) + type is(eno_reconstructor) + recon = create_eno_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case('weno3') + allocate(weno3_reconstructor :: recon) + select type(recon) + type is(weno3_reconstructor) + recon = create_weno3_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case('weno5') + allocate(weno5_reconstructor :: recon) + select type(recon) + type is(weno5_reconstructor) + recon = create_weno5_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case default + error_code = CM_ERROR_UNKNOWN_SCHEME + if (config%verbose) then + print *, "[ERROR] Unknown reconstructor scheme: ", scheme + print *, " Available: eno, weno3, weno5" + end if + end select + + ! 检查阶数有效性 + if (error_code == CM_SUCCESS) then + if (order < 1) then + error_code = CM_ERROR_INVALID_ORDER + if (config%verbose) then + print *, "[ERROR] Invalid spatial order: ", order + end if + end if + end if + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Reconstructor creation failed" + end if + end function create_reconstructor + + ! ==================== 通量计算器创建 ==================== + + function create_flux_calculator(config, status) result(flux) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(flux_calculator_base), allocatable :: flux + + character(len=20) :: flux_type + integer :: error_code + + flux_type = trim(adjustl(config%flux_type)) + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating flux calculator: ", flux_type + end if + + select case(flux_type) + case('rusanov') + allocate(rusanov_flux :: flux) + select type(flux) + type is(rusanov_flux) + flux = create_rusanov_flux() + flux%wave_speed_default = config%wave_speed + end select + + case default + error_code = CM_ERROR_UNKNOWN_FLUX + if (config%verbose) then + print *, "[ERROR] Unknown flux type: ", flux_type + print *, " Available: rusanov" + end if + end select + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Flux calculator creation failed" + end if + end function create_flux_calculator + +end module component_factory_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/manager/component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/src/manager/component_manager.f90 new file mode 100644 index 000000000..25eac29be --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/manager/component_manager.f90 @@ -0,0 +1,76 @@ +! src/manager/component_manager.f90 (完整文件) +module component_manager_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use config_module, only: cfd_config + use component_factory_module, only: create_reconstructor, create_flux_calculator + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + + implicit none + private + public :: wp, component_manager_info, validate_config + public :: create_reconstructor, create_flux_calculator + +contains + + ! ==================== 配置验证 ==================== + + function validate_config(config) result(is_valid) + type(cfd_config), intent(in) :: config + logical :: is_valid + + integer :: status + class(reconstructor_base), allocatable :: test_recon + class(flux_calculator_base), allocatable :: test_flux + + is_valid = .false. + + ! 测试创建重构器 + test_recon = create_reconstructor(config, status) + if (status /= 0) then + if (config%verbose) then + print *, "[CONFIG VALIDATION] Invalid reconstructor configuration" + end if + return + end if + + ! 测试创建通量计算器 + test_flux = create_flux_calculator(config, status) + if (status /= 0) then + if (config%verbose) then + print *, "[CONFIG VALIDATION] Invalid flux configuration" + end if + return + end if + + ! 清理测试组件 + if (allocated(test_recon)) deallocate(test_recon) + if (allocated(test_flux)) deallocate(test_flux) + + is_valid = .true. + + if (config%verbose) then + print *, "[CONFIG VALIDATION] Configuration is valid" + end if + end function validate_config + + ! ==================== 信息显示 ==================== + + subroutine component_manager_info() + print *, "=== Component Manager ===" + print *, "Available reconstructors:" + print *, " - eno (orders: 1-7)" + print *, " - weno3 (order: 3)" + print *, " - weno5 (order: 5)" + print *, "" + print *, "Available flux calculators:" + print *, " - rusanov" + print *, "" + print *, "Features:" + print *, " - Configuration validation" + print *, " - Component creation from config" + print *, " - Error handling with status codes" + print *, "=========================" + end subroutine component_manager_info + +end module component_manager_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..66874a7ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/CMakeLists.txt @@ -0,0 +1,7 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..3fde7746b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,28 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +# Start with just the base module +add_library(flux STATIC + base.f90 + rusanov.f90 +) + +#target_link_libraries(flux PRIVATE core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 明确设置不使用 /Qm64 选项 +if(CMAKE_Fortran_COMPILER_ID MATCHES "IntelLLVM|Intel") + set_target_properties(flux PROPERTIES + Fortran_FLAGS "${CMAKE_Fortran_FLAGS} /Qm64" # 明确设置,避免继承问题 + ) +endif() + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/flux/base.f90 new file mode 100644 index 000000000..7080a7ae4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/flux/base.f90 @@ -0,0 +1,30 @@ +!src/numerics/flux/base.f90 +module flux_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, flux_calculator_base + + ! Base flux calculator type + type :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure :: info => flux_info + procedure :: print_basic_info => flux_print_basic ! 添加辅助方法 + end type flux_calculator_base + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "Flux calculator information:" + print *, " Name: ", trim(this%name) + end subroutine flux_info + + subroutine flux_print_basic(this) + class(flux_calculator_base), intent(in) :: this + print *, " Name: ", trim(this%name) + end subroutine flux_print_basic + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/flux/rusanov.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/flux/rusanov.f90 new file mode 100644 index 000000000..daa9e3bb5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/flux/rusanov.f90 @@ -0,0 +1,41 @@ +! src/numerics/flux/rusanov.f90 +module rusanov_flux_module + use, intrinsic :: iso_fortran_env, only: real64 + use flux_base_module, only: flux_calculator_base + implicit none + + private + public :: real64, rusanov_flux, create_rusanov_flux + + type, extends(flux_calculator_base) :: rusanov_flux + real(real64) :: wave_speed_default = 1.0_real64 + contains + procedure :: info => rusanov_info + end type rusanov_flux + + ! 构造函数接口 + interface rusanov_flux + module procedure create_rusanov_flux + end interface + +contains + + ! 构造函数 + type(rusanov_flux) function create_rusanov_flux() result(this) + this%name = "Rusanov" + this%wave_speed_default = 1.0_real64 + end function create_rusanov_flux + + subroutine rusanov_info(this) + class(rusanov_flux), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Flux calculator information:" + call this%print_basic_info() + + ! 添加Rusanov特有信息 + print *, " Type: Rusanov flux" + print *, " Default wave speed: ", this%wave_speed_default + end subroutine rusanov_info + +end module rusanov_flux_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..c88ea647b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,23 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "Configuring reconstructor module...") + +# Start with just the base module +add_library(reconstructor STATIC + base.f90 + eno.f90 + weno3.f90 + weno5.f90 +) + +target_link_libraries(reconstructor PRIVATE core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Reconstructor module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..53798d021 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/reconstructor/base.f90 @@ -0,0 +1,33 @@ +!src/numerics/reconstructor/base.f90 +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, reconstructor_base + + ! Base reconstructor type + type :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + contains + procedure :: info => reconstructor_info + procedure :: print_basic_info => reconstructor_print_basic ! 添加一个辅助方法 + end type reconstructor_base + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + print *, "Reconstructor information:" + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_info + + subroutine reconstructor_print_basic(this) + class(reconstructor_base), intent(in) :: this + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_print_basic + +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/reconstructor/eno.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/reconstructor/eno.f90 new file mode 100644 index 000000000..f973e8b32 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/reconstructor/eno.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/eno.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, eno_reconstructor, create_eno_reconstructor ! ← 添加这个 + + type, extends(reconstructor_base) :: eno_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => eno_info + end type eno_reconstructor + + ! 构造函数接口 + interface eno_reconstructor + module procedure create_eno_reconstructor + end interface + +contains + + ! 构造函数 + type(eno_reconstructor) function create_eno_reconstructor() result(this) + this%name = "ENO" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_eno_reconstructor + + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加ENO特有信息 + print *, " Type: ENO reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/reconstructor/weno3.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/reconstructor/weno3.f90 new file mode 100644 index 000000000..d5b7a7477 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/reconstructor/weno3.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/weno3.f90 +module weno3_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno3_reconstructor, create_weno3_reconstructor + + type, extends(reconstructor_base) :: weno3_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno3_info + end type weno3_reconstructor + + ! 构造函数接口 + interface weno3_reconstructor + module procedure create_weno3_reconstructor + end interface + +contains + + ! 构造函数 + type(weno3_reconstructor) function create_weno3_reconstructor() result(this) + this%name = "WENO3" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_weno3_reconstructor + + subroutine weno3_info(this) + class(weno3_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加WENO3特有信息 + print *, " Type: WENO-3 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno3_info + +end module weno3_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/reconstructor/weno5.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/reconstructor/weno5.f90 new file mode 100644 index 000000000..a869c67d0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/numerics/reconstructor/weno5.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/weno5.f90(新增) +module weno5_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno5_reconstructor, create_weno5_reconstructor + + type, extends(reconstructor_base) :: weno5_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno5_info + end type weno5_reconstructor + + ! 构造函数接口 + interface weno5_reconstructor + module procedure create_weno5_reconstructor + end interface + +contains + + ! 构造函数 + type(weno5_reconstructor) function create_weno5_reconstructor() result(this) + this%name = "WENO5" + this%order = 5 + this%epsilon = 1.0e-6_real64 + end function create_weno5_reconstructor + + subroutine weno5_info(this) + class(weno5_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加WENO5特有信息 + print *, " Type: WENO-5 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno5_info + +end module weno5_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/physics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03b/src/physics/CMakeLists.txt new file mode 100644 index 000000000..cc4e233ab --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/physics/CMakeLists.txt @@ -0,0 +1,19 @@ +# src/physics/CMakeLists.txt +message(STATUS "配置物理模块...") + +# 创建物理模块库 +add_library(physics STATIC + physics_interface.f90 + equations/linear_convection.f90 + problems/linear_convection_problem.f90 +) + +# 链接依赖 +target_link_libraries(physics PRIVATE base) + +# 设置模块输出目录 +set_target_properties(physics PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "物理模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/physics/equations/linear_convection.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/src/physics/equations/linear_convection.f90 new file mode 100644 index 000000000..fff7be55d --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/physics/equations/linear_convection.f90 @@ -0,0 +1,49 @@ +! src/physics/equations/linear_convection.f90 +module linear_convection_equation + use precision_module, only: wp, ip + use physics_interface, only: physics_equation + implicit none + private + + ! 具体方程类型 - 先声明 + type, extends(physics_equation) :: linear_convection_eq + real(wp) :: wave_speed = 1.0_wp + contains + procedure :: flux => lc_flux + procedure :: speed => lc_speed + end type linear_convection_eq + + ! 公开接口 + public :: wp, ip + public :: linear_convection_eq, create_linear_convection_eq + +contains + + ! 构造函数 + function create_linear_convection_eq(wave_speed) result(eq) + real(wp), intent(in), optional :: wave_speed + type(linear_convection_eq) :: eq + + eq%name = "Linear Convection" + if (present(wave_speed)) then + eq%wave_speed = wave_speed + else + eq%wave_speed = 1.0_wp + end if + end function create_linear_convection_eq + + ! 方法实现 + pure function lc_flux(this, u) result(f) + class(linear_convection_eq), intent(in) :: this + real(wp), intent(in) :: u + real(wp) :: f + f = this%wave_speed * u + end function lc_flux + + pure function lc_speed(this) result(a) + class(linear_convection_eq), intent(in) :: this + real(wp) :: a + a = this%wave_speed + end function lc_speed + +end module linear_convection_equation \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/physics/physics_interface.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/src/physics/physics_interface.f90 new file mode 100644 index 000000000..45002da6c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/physics/physics_interface.f90 @@ -0,0 +1,64 @@ +! src/physics/physics_interface.f90 +module physics_interface + use precision_module, only: wp, ip + implicit none + private + + ! 定义抽象基类型 - 先声明为私有,然后在公开部分导出 + type, abstract :: physics_equation + character(len=:), allocatable :: name + contains + procedure(eq_flux_abs), deferred :: flux + procedure(eq_speed_abs), deferred :: speed + end type physics_equation + + type, abstract :: physics_problem + character(len=:), allocatable :: name + contains + procedure(prob_ic_abs), deferred :: initial_condition + procedure(prob_bc_abs), deferred :: boundary_condition + procedure(prob_exact_abs), deferred :: exact_solution + end type physics_problem + + ! 抽象接口定义 + abstract interface + pure function eq_flux_abs(this, u) result(f) + import :: physics_equation, wp + class(physics_equation), intent(in) :: this + real(wp), intent(in) :: u + real(wp) :: f + end function eq_flux_abs + + pure function eq_speed_abs(this) result(a) + import :: physics_equation, wp + class(physics_equation), intent(in) :: this + real(wp) :: a + end function eq_speed_abs + + subroutine prob_ic_abs(this, x, u) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(in) :: x(:) + real(wp), intent(out) :: u(:) + end subroutine prob_ic_abs + + subroutine prob_bc_abs(this, u, t) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(inout) :: u(:) + real(wp), intent(in), optional :: t + end subroutine prob_bc_abs + + function prob_exact_abs(this, x, t) result(u) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(in) :: x(:), t + real(wp), dimension(size(x)) :: u + end function prob_exact_abs + end interface + + ! 公开接口 - 使用独立的public语句 + public :: wp, ip + public :: physics_equation, physics_problem + +end module physics_interface \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/physics/problems/linear_convection_problem.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/src/physics/problems/linear_convection_problem.f90 new file mode 100644 index 000000000..06226ed13 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/physics/problems/linear_convection_problem.f90 @@ -0,0 +1,118 @@ +! src/physics/problems/linear_convection_problem.f90 +module linear_convection_problem + use precision_module, only: wp, ip + use physics_interface, only: physics_problem + implicit none + private + + ! 具体问题类型 - 先声明 + type, extends(physics_problem) :: linear_convection_prob + real(wp) :: wave_speed = 1.0_wp + real(wp) :: domain_length = 2.0_wp + character(len=20) :: ic_type = "step" + character(len=20) :: boundary_type = "periodic" + contains + procedure :: initial_condition => lc_initial_condition + procedure :: boundary_condition => lc_boundary_condition + procedure :: exact_solution => lc_exact_solution + end type linear_convection_prob + + ! 公开接口 + public :: wp, ip + public :: linear_convection_prob, create_linear_convection_prob + +contains + + ! 构造函数 + function create_linear_convection_prob(wave_speed, domain_length, & + ic_type, boundary_type) result(prob) + real(wp), intent(in), optional :: wave_speed, domain_length + character(len=*), intent(in), optional :: ic_type, boundary_type + type(linear_convection_prob) :: prob + + prob%name = "Linear Convection Problem" + + if (present(wave_speed)) prob%wave_speed = wave_speed + if (present(domain_length)) prob%domain_length = domain_length + if (present(ic_type)) prob%ic_type = ic_type + if (present(boundary_type)) prob%boundary_type = boundary_type + end function create_linear_convection_prob + + ! 初始条件 + subroutine lc_initial_condition(this, x, u) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(in) :: x(:) + real(wp), intent(out) :: u(:) + + integer :: i + + select case (trim(this%ic_type)) + case ("step") + do i = 1, size(x) + if (x(i) >= 0.5_wp .and. x(i) <= 1.0_wp) then + u(i) = 2.0_wp + else + u(i) = 1.0_wp + end if + end do + + case ("sin", "sine") + do i = 1, size(x) + u(i) = sin(2.0_wp * 3.141592653589793_wp * x(i) / this%domain_length) + end do + + case ("gaussian") + do i = 1, size(x) + u(i) = exp(-((x(i) - 0.5_wp) / 0.1_wp)**2) + end do + + case default + ! 默认阶跃函数 + do i = 1, size(x) + if (x(i) >= 0.5_wp .and. x(i) <= 1.0_wp) then + u(i) = 2.0_wp + else + u(i) = 1.0_wp + end if + end do + end select + end subroutine lc_initial_condition + + ! 边界条件(虚拟实现,实际在boundary模块) + subroutine lc_boundary_condition(this, u, t) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(inout) :: u(:) + real(wp), intent(in), optional :: t + + ! 边界条件将在独立模块实现 + print *, "[PROBLEM] Boundary condition placeholder" + if (present(t)) then + print *, " Time = ", t + end if + end subroutine lc_boundary_condition + + ! 精确解(周期性平移) + function lc_exact_solution(this, x, t) result(u) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(in) :: x(:), t + real(wp), dimension(size(x)) :: u + real(wp), dimension(size(x)) :: x_shifted + integer :: i + + ! 周期性平移 + do i = 1, size(x) + x_shifted(i) = x(i) - this%wave_speed * t + ! 确保在 [0, domain_length) 范围内 + do while (x_shifted(i) < 0.0_wp) + x_shifted(i) = x_shifted(i) + this%domain_length + end do + do while (x_shifted(i) >= this%domain_length) + x_shifted(i) = x_shifted(i) - this%domain_length + end do + end do + + ! 重用初始条件函数 + call this%initial_condition(x_shifted, u) + end function lc_exact_solution + +end module linear_convection_problem \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/run_eno_weno.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/src/run_eno_weno.f90 new file mode 100644 index 000000000..5821c5a0a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/run_eno_weno.f90 @@ -0,0 +1,171 @@ +! src/run_eno_weno.f90 +program run_eno_weno + ! 使用所有需要的模块 + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction, config_print + use mesh_module, only: mesh_type + use solver_base_module, only: SOLVER_INITIALIZED, SOLVER_COMPLETED + + use physics_solver_module, only: physics_solver + use registry_module, only: registry_init, registry_cleanup + + implicit none + + type(cfd_config) :: config_eno3, config_weno3, config_weno5 + type(mesh_type) :: mesh + type(physics_solver) :: solver_eno3, solver_weno3, solver_weno5 + + character(len=100) :: output_file + integer :: status + + print *, "=== ENO/WENO对比分析 (Fortran版本) ===" + print *, "" + + ! 初始化注册系统 + call registry_init(verbose=.true.) + print *, "" + + ! 步骤1: 初始化网格 + print *, "步骤1: 初始化网格..." + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=40) + call mesh%print_info() + print *, "" + + ! 步骤2: 配置并运行ENO3求解器 + print *, "步骤2: 运行ENO3求解器..." + print *, "-------------------------" + + ! 创建ENO3配置 + config_eno3%verbose = .true. + call config_with_reconstruction(config_eno3, "eno", 3) + config_eno3%dt = 0.0025_wp + config_eno3%rk_order = 2 + config_eno3%wave_speed = 1.0_wp + config_eno3%final_time = 0.625_wp + config_eno3%ic_type = "step" + + print *, "ENO3配置:" + call config_print(config_eno3) + + ! 创建ENO3求解器 + print *, "创建ENO3求解器..." + solver_eno3 = physics_solver(config_eno3, mesh) + call solver_eno3%print_info() + print *, "" + + ! 初始化并运行ENO3求解器 + print *, "运行ENO3求解器..." + call solver_eno3%initialize() + call solver_eno3%run_to_time(config_eno3%final_time) + + if (solver_eno3%get_state() == SOLVER_COMPLETED) then + print *, "✓ ENO3求解器运行完成" + print *, " 最终时间: ", solver_eno3%current_time + print *, " 总步数: ", solver_eno3%current_step + else + print *, "✗ ENO3求解器运行失败" + print *, " 错误信息: ", trim(solver_eno3%get_error()) + end if + print *, "" + + ! 步骤3: 配置并运行WENO3求解器 + print *, "步骤3: 运行WENO3求解器..." + print *, "--------------------------" + + ! 创建WENO3配置 + config_weno3%verbose = .true. + call config_with_reconstruction(config_weno3, "weno3", 3) + config_weno3%dt = 0.0025_wp + config_weno3%rk_order = 2 + config_weno3%wave_speed = 1.0_wp + config_weno3%final_time = 0.625_wp + config_weno3%ic_type = "step" + + print *, "WENO3配置:" + call config_print(config_weno3) + + ! 创建WENO3求解器 + print *, "创建WENO3求解器..." + solver_weno3 = physics_solver(config_weno3, mesh) + call solver_weno3%print_info() + print *, "" + + ! 初始化并运行WENO3求解器 + print *, "运行WENO3求解器..." + call solver_weno3%initialize() + call solver_weno3%run_to_time(config_weno3%final_time) + + if (solver_weno3%get_state() == SOLVER_COMPLETED) then + print *, "✓ WENO3求解器运行完成" + print *, " 最终时间: ", solver_weno3%current_time + print *, " 总步数: ", solver_weno3%current_step + else + print *, "✗ WENO3求解器运行失败" + print *, " 错误信息: ", trim(solver_weno3%get_error()) + end if + print *, "" + + ! 步骤4: 配置并运行WENO5求解器 + print *, "步骤4: 运行WENO5求解器..." + print *, "--------------------------" + + ! 创建WENO5配置 + config_weno5%verbose = .true. + call config_with_reconstruction(config_weno5, "weno", 5) + config_weno5%dt = 0.0025_wp + config_weno5%rk_order = 2 + config_weno5%wave_speed = 1.0_wp + config_weno5%final_time = 0.625_wp + config_weno5%ic_type = "step" + + print *, "WENO5配置:" + call config_print(config_weno5) + + ! 创建WENO5求解器 + print *, "创建WENO5求解器..." + solver_weno5 = physics_solver(config_weno5, mesh) + call solver_weno5%print_info() + print *, "" + + ! 初始化并运行WENO5求解器 + print *, "运行WENO5求解器..." + call solver_weno5%initialize() + call solver_weno5%run_to_time(config_weno5%final_time) + + if (solver_weno5%get_state() == SOLVER_COMPLETED) then + print *, "✓ WENO5求解器运行完成" + print *, " 最终时间: ", solver_weno5%current_time + print *, " 总步数: ", solver_weno5%current_step + else + print *, "✗ WENO5求解器运行失败" + print *, " 错误信息: ", trim(solver_weno5%get_error()) + end if + print *, "" + + ! 清理注册系统 + call registry_cleanup() + + ! 清理求解器 + call solver_eno3%cleanup() + call solver_weno3%cleanup() + call solver_weno5%cleanup() + + print *, "=== 分析完成 ===" + print *, "总结:" + print *, " ENO3: 时间=", solver_eno3%current_time, " 步数=", solver_eno3%current_step + print *, " WENO3: 时间=", solver_weno3%current_time, " 步数=", solver_weno3%current_step + print *, " WENO5: 时间=", solver_weno5%current_time, " 步数=", solver_weno5%current_step + + if (solver_eno3%get_state() == SOLVER_COMPLETED .and. & + solver_weno3%get_state() == SOLVER_COMPLETED .and. & + solver_weno5%get_state() == SOLVER_COMPLETED) then + print *, "" + print *, "✓ 所有求解器成功运行!" + print *, "下一步: 添加边界条件模块" + else + print *, "" + print *, "✗ 有求解器运行失败" + print *, "需要先调试现有代码" + end if + +end program run_eno_weno \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/solver/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03b/src/solver/CMakeLists.txt new file mode 100644 index 000000000..f8499eecd --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/solver/CMakeLists.txt @@ -0,0 +1,21 @@ +# src/solver/CMakeLists.txt +message(STATUS "配置求解器模块...") + +add_library(solver STATIC + base.f90 + physics_solver.f90 +) + +target_link_libraries(solver + PRIVATE + infrastructure + core + physics + manager +) + +set_target_properties(solver PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "求解器模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/solver/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/src/solver/base.f90 new file mode 100644 index 000000000..1881ba089 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/solver/base.f90 @@ -0,0 +1,258 @@ +! src/solver/base.f90 +module solver_base_module + use base_modules, only: wp => wp, ip => ip ! 重命名以避免冲突 + + use config_module, only: cfd_config + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create + + implicit none + private + + ! 明确导出列表 + public :: wp, ip ! 类型参数 + public :: solver_base, create_solver_base ! 类型和构造函数 + public :: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, SOLVER_RUNNING + public :: SOLVER_COMPLETED, SOLVER_ERROR ! 状态常量 + + ! 求解器状态枚举 + integer, parameter :: SOLVER_UNINITIALIZED = 0 + integer, parameter :: SOLVER_INITIALIZED = 1 + integer, parameter :: SOLVER_RUNNING = 2 + integer, parameter :: SOLVER_COMPLETED = 3 + integer, parameter :: SOLVER_ERROR = 4 + + ! 求解器基类 + type :: solver_base + ! 基本组件 + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + + ! 状态管理 + integer :: state = SOLVER_UNINITIALIZED + character(len=100) :: error_message = "" + real(wp) :: current_time = 0.0_wp + integer(ip) :: current_step = 0 + + ! 时间控制 + real(wp) :: dt_original = 0.0_wp + contains + procedure :: initialize => solver_base_initialize + procedure :: step => solver_base_step + procedure :: run_to_time => solver_base_run_to_time + procedure :: cleanup => solver_base_cleanup + procedure :: get_state => solver_base_get_state + procedure :: get_error => solver_base_get_error + procedure :: print_info => solver_base_print_info + end type solver_base + + ! 构造函数接口 + interface solver_base + module procedure create_solver_base + end interface + +contains + + ! ==================== 构造函数 ==================== + + function create_solver_base(config, mesh) result(solver) + type(cfd_config), intent(in) :: config + type(mesh_type), intent(in) :: mesh + type(solver_base) :: solver + + solver%config = config + solver%mesh = mesh + + ! 创建域 + solver%domain = domain_create(config, mesh) + + ! 创建解 + solver%solution = solution_create(solver%domain) + + ! 保存原始时间步长 + solver%dt_original = config%dt + + if (config%verbose) then + print *, "[SOLVER] Base solver created" + print *, " Mesh cells: ", mesh%ncells + print *, " Domain total cells: ", solver%domain%ntcells + end if + end function create_solver_base + + ! ==================== 初始化 ==================== + + subroutine solver_base_initialize(this) + class(solver_base), intent(inout) :: this + + if (this%state == SOLVER_INITIALIZED) then + if (this%config%verbose) then + print *, "[SOLVER] Already initialized" + end if + return + end if + + ! 初始化解(通过配置) + ! 这里暂时简化,实际需要调用初始条件工厂 + print *, "[INFO] Base solver initialized (simplified)" + + ! 更新状态 + this%state = SOLVER_INITIALIZED + this%current_time = 0.0_wp + this%current_step = 0 + + if (this%config%verbose) then + print *, "[SOLVER] Initialized at t = ", this%current_time + end if + end subroutine solver_base_initialize + + ! ==================== 单步计算(虚方法) ==================== + + subroutine solver_base_step(this, dt) + class(solver_base), intent(inout) :: this + real(wp), intent(in) :: dt + + ! 基类中这只是虚方法,需要在子类中实现 + print *, "[INFO] Base solver step (virtual method)" + print *, " dt = ", dt + print *, " t = ", this%current_time + + ! 更新时间 + this%current_time = this%current_time + dt + this%current_step = this%current_step + 1 + + ! 简单模拟:只是更新状态 + if (this%config%verbose) then + print *, "[SOLVER] Step completed: t = ", this%current_time, & + ", step = ", this%current_step + end if + end subroutine solver_base_step + + ! ==================== 运行到指定时间 ==================== + + subroutine solver_base_run_to_time(this, final_time) + class(solver_base), intent(inout) :: this + real(wp), intent(in) :: final_time + + real(wp) :: dt, t_remaining + integer :: step_count + + if (this%state /= SOLVER_INITIALIZED) then + this%error_message = "Solver not initialized" + this%state = SOLVER_ERROR + return + end if + + this%state = SOLVER_RUNNING + step_count = 0 + + if (this%config%verbose) then + print *, "[SOLVER] Running from t = ", this%current_time, & + " to t = ", final_time + end if + + do while (this%current_time < final_time) + ! 计算时间步长 + dt = min(this%config%dt, final_time - this%current_time) + + ! 执行时间步 + call this%step(dt) + + step_count = step_count + 1 + + ! 每10步输出一次进度 + if (mod(step_count, 10) == 0 .and. this%config%verbose) then + print *, "[SOLVER] Progress: t = ", this%current_time, & + " / ", final_time + end if + end do + + ! 恢复原始时间步长 + this%config%dt = this%dt_original + + ! 更新状态 + this%state = SOLVER_COMPLETED + + if (this%config%verbose) then + print *, "[SOLVER] Run completed:" + print *, " Final time: ", this%current_time + print *, " Total steps: ", this%current_step + end if + end subroutine solver_base_run_to_time + + ! ==================== 清理 ==================== + + subroutine solver_base_cleanup(this) + class(solver_base), intent(inout) :: this + + ! 重置状态 + this%state = SOLVER_UNINITIALIZED + this%current_time = 0.0_wp + this%current_step = 0 + this%error_message = "" + + if (this%config%verbose) then + print *, "[SOLVER] Cleaned up" + end if + end subroutine solver_base_cleanup + + ! ==================== 状态查询 ==================== + + function solver_base_get_state(this) result(state) + class(solver_base), intent(in) :: this + integer :: state + state = this%state + end function solver_base_get_state + + function solver_base_get_error(this) result(error_msg) + class(solver_base), intent(in) :: this + character(len=100) :: error_msg + error_msg = trim(this%error_message) + end function solver_base_get_error + + ! ==================== 信息打印 ==================== + + subroutine solver_base_print_info(this) + class(solver_base), intent(in) :: this + + character(len=20) :: state_str + + ! 状态字符串 + select case (this%state) + case (SOLVER_UNINITIALIZED) + state_str = "Uninitialized" + case (SOLVER_INITIALIZED) + state_str = "Initialized" + case (SOLVER_RUNNING) + state_str = "Running" + case (SOLVER_COMPLETED) + state_str = "Completed" + case (SOLVER_ERROR) + state_str = "Error" + case default + state_str = "Unknown" + end select + + print *, "=== Solver Information ===" + print *, "State: ", trim(state_str) + print *, "Current time: ", this%current_time + print *, "Current step: ", this%current_step + print *, "Error message: '", trim(this%error_message), "'" + + ! 配置信息 + print *, "Configuration:" + print *, " Scheme: ", trim(this%config%recon_scheme) + print *, " Order: ", this%config%spatial_order + print *, " dt: ", this%config%dt + + ! 域信息 + print *, "Domain:" + print *, " Ghost layers: ", this%domain%nghosts + print *, " Physical cells: ", this%domain%ist, " to ", this%domain%ied - 1 + + print *, "=========================" + end subroutine solver_base_print_info + +end module solver_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/src/solver/physics_solver.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/src/solver/physics_solver.f90 new file mode 100644 index 000000000..89ee4f40e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/src/solver/physics_solver.f90 @@ -0,0 +1,332 @@ +! src/solver/physics_solver.f90 (修复版) +module physics_solver_module + use base_modules, only: wp => wp, ip => ip + use config_module, only: cfd_config + use mesh_module, only: mesh_type + use solver_base_module, only: solver_base, create_solver_base + use solver_base_module, only: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, & + SOLVER_RUNNING, SOLVER_COMPLETED, SOLVER_ERROR + + use physics_interface, only: physics_equation, physics_problem + use linear_convection_equation, only: linear_convection_eq, create_linear_convection_eq + use linear_convection_problem, only: linear_convection_prob, create_linear_convection_prob + + use component_manager_module, only: create_reconstructor, create_flux_calculator + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + + implicit none + private + + ! 明确导出列表 + public :: wp, ip, physics_solver, create_physics_solver + + ! 基于物理的求解器(扩展自solver_base) + type, extends(solver_base) :: physics_solver + ! 物理组件 + class(physics_equation), allocatable :: equation + class(physics_problem), allocatable :: problem + + ! 数值组件 + class(reconstructor_base), allocatable :: reconstructor + class(flux_calculator_base), allocatable :: flux_calculator + + ! 状态信息 + logical :: physics_initialized = .false. + contains + procedure :: initialize => physics_solver_initialize + procedure :: step => physics_solver_step + procedure :: cleanup => physics_solver_cleanup + procedure :: print_info => physics_solver_print_info + procedure :: create_physics_components + procedure :: create_numerical_components + end type physics_solver + + ! 构造函数接口 + interface physics_solver + module procedure create_physics_solver + end interface + +contains + + ! ==================== 构造函数 ==================== + + function create_physics_solver(config, mesh) result(solver) + type(cfd_config), intent(in) :: config + type(mesh_type), intent(in) :: mesh + type(physics_solver) :: solver + + ! 先调用父类构造函数 + solver%solver_base = create_solver_base(config, mesh) + + ! 创建物理组件 + call solver%create_physics_components() + + ! 创建数值组件 + call solver%create_numerical_components() + + if (config%verbose) then + print *, "[PHYSICS SOLVER] Created with physics support" + end if + end function create_physics_solver + + ! ==================== 创建物理组件 ==================== + + subroutine create_physics_components(this) + class(physics_solver), intent(inout) :: this + + ! 检查是否启用物理模块 + if (.not. this%config%enable_physics) then + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Physics module disabled, skipping physics components" + end if + return + end if + + ! 创建物理方程 + select case (trim(this%config%equation_type)) + case ("linear_advection") + allocate(linear_convection_eq :: this%equation) + select type(eq => this%equation) + type is(linear_convection_eq) + eq = create_linear_convection_eq(wave_speed=this%config%wave_speed) + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Created linear convection equation" + print *, " Wave speed: ", eq%wave_speed + end if + end select + + case default + print *, "[WARNING] Unknown equation type: ", trim(this%config%equation_type) + print *, " Using linear convection as default" + + allocate(linear_convection_eq :: this%equation) + select type(eq => this%equation) + type is(linear_convection_eq) + eq = create_linear_convection_eq(wave_speed=this%config%wave_speed) + end select + end select + + ! 创建物理问题 + select case (trim(this%config%problem_type)) + case ("linear_advection") + allocate(linear_convection_prob :: this%problem) + select type(prob => this%problem) + type is(linear_convection_prob) + prob = create_linear_convection_prob( & + wave_speed=this%config%wave_speed, & + domain_length=this%config%domain_length, & + ic_type=this%config%ic_type, & + boundary_type=this%config%boundary_type) + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Created linear convection problem" + print *, " IC type: ", trim(prob%ic_type) + print *, " Domain length: ", prob%domain_length + end if + end select + + case default + print *, "[WARNING] Unknown problem type: ", trim(this%config%problem_type) + print *, " Using linear convection as default" + + allocate(linear_convection_prob :: this%problem) + select type(prob => this%problem) + type is(linear_convection_prob) + prob = create_linear_convection_prob( & + wave_speed=this%config%wave_speed, & + domain_length=this%config%domain_length, & + ic_type=this%config%ic_type, & + boundary_type=this%config%boundary_type) + end select + end select + + this%physics_initialized = .true. + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Physics components created successfully" + end if + end subroutine create_physics_components + + ! ==================== 创建数值组件 ==================== + + subroutine create_numerical_components(this) + class(physics_solver), intent(inout) :: this + integer :: status + + ! 创建重构器 + this%reconstructor = create_reconstructor(this%config, status) + if (status /= 0) then + print *, "[ERROR] Failed to create reconstructor" + this%state = SOLVER_ERROR + this%error_message = "Failed to create reconstructor" + return + end if + + ! 创建通量计算器 + this%flux_calculator = create_flux_calculator(this%config, status) + if (status /= 0) then + print *, "[ERROR] Failed to create flux calculator" + this%state = SOLVER_ERROR + this%error_message = "Failed to create flux calculator" + return + end if + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Numerical components created successfully" + end if + end subroutine create_numerical_components + + ! ==================== 初始化 ==================== + + subroutine physics_solver_initialize(this) + class(physics_solver), intent(inout) :: this + + ! 调用父类初始化 + call this%solver_base%initialize() + + ! 如果启用了物理模块,应用初始条件 + if (this%physics_initialized .and. allocated(this%problem)) then + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Applying initial condition from physics problem" + end if + + select type(prob => this%problem) + type is(linear_convection_prob) + ! 获取网格单元中心坐标 + call prob%initial_condition(this%mesh%xcc, this%solution%u(this%domain%ist:this%domain%ied-1)) + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Initial condition applied" + print *, " u range: ", minval(this%solution%u), " to ", maxval(this%solution%u) + end if + end select + end if + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Initialized with physics support" + end if + end subroutine physics_solver_initialize + + ! ==================== 时间步进(简化实现) ==================== + + subroutine physics_solver_step(this, dt) + class(physics_solver), intent(inout) :: this + real(wp), intent(in) :: dt + + integer :: i, j + real(wp) :: u_val, f_val, residual + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Step with dt = ", dt + end if + + ! 简化的数值方法:直接使用方程计算通量 + if (allocated(this%equation) .and. allocated(this%reconstructor) .and. & + allocated(this%flux_calculator)) then + + ! 这里应该是完整的数值方法: + ! 1. 边界条件 + ! 2. 重构 + ! 3. 通量计算 + ! 4. 残差计算 + ! 5. 时间积分 + + ! 简化的占位实现:只是模拟计算过程 + do i = this%domain%ist, this%domain%ied - 1 + j = i - this%domain%ist + 1 + u_val = this%solution%u(i) + + ! 使用方程计算通量(简化) + select type(eq => this%equation) + type is(linear_convection_eq) + f_val = eq%flux(u_val) + end select + + ! 简化的更新:只是演示 + this%solution%u(i) = u_val - dt * f_val / this%mesh%dx + end do + + ! 更新时间 + this%current_time = this%current_time + dt + this%current_step = this%current_step + 1 + + if (this%config%verbose .and. mod(this%current_step, 10) == 0) then + print *, "[PHYSICS SOLVER] Step ", this%current_step, & + " completed, t = ", this%current_time + end if + + else + ! 如果没有完整的组件,回退到基类方法 + call this%solver_base%step(dt) + end if + end subroutine physics_solver_step + + ! ==================== 清理 ==================== + + subroutine physics_solver_cleanup(this) + class(physics_solver), intent(inout) :: this + + ! 清理物理组件 + if (allocated(this%equation)) deallocate(this%equation) + if (allocated(this%problem)) deallocate(this%problem) + + ! 清理数值组件 + if (allocated(this%reconstructor)) deallocate(this%reconstructor) + if (allocated(this%flux_calculator)) deallocate(this%flux_calculator) + + ! 调用父类清理 + call this%solver_base%cleanup() + + this%physics_initialized = .false. + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Cleaned up physics components" + end if + end subroutine physics_solver_cleanup + + ! ==================== 信息打印 ==================== + + subroutine physics_solver_print_info(this) + class(physics_solver), intent(in) :: this + + ! 调用父类信息打印 + call this%solver_base%print_info() + + ! 添加物理信息 + print *, "=== Physics Information ===" + print *, "Physics initialized: ", this%physics_initialized + + if (allocated(this%equation)) then + print *, "Equation: allocated" + select type(eq => this%equation) + type is(linear_convection_eq) + print *, " Type: Linear Convection" + print *, " Wave speed: ", eq%wave_speed + class default + print *, " Type: Unknown" + end select + else + print *, "Equation: not allocated" + end if + + if (allocated(this%problem)) then + print *, "Problem: allocated" + select type(prob => this%problem) + type is(linear_convection_prob) + print *, " Type: Linear Convection Problem" + print *, " IC type: ", trim(prob%ic_type) + print *, " Domain length: ", prob%domain_length + class default + print *, " Type: Unknown" + end select + else + print *, "Problem: not allocated" + end if + + print *, "Numerical components:" + print *, " Reconstructor: ", allocated(this%reconstructor) + print *, " Flux calculator: ", allocated(this%flux_calculator) + print *, "==========================" + end subroutine physics_solver_print_info + +end module physics_solver_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/CMakeLists.txt new file mode 100644 index 000000000..aabc1c7e3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/CMakeLists.txt @@ -0,0 +1,95 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +# 基础设施测试 +add_executable(test_infrastructure test_infrastructure.f90) +target_link_libraries(test_infrastructure + PRIVATE + infrastructure + core +) + +# 注册系统测试 +add_executable(test_registry test_registry.f90) +target_link_libraries(test_registry + PRIVATE + core + infrastructure +) + +# 物理模块测试 +add_executable(test_physics test_physics.f90) +target_link_libraries(test_physics + PRIVATE + physics + base +) + +# 组件管理器测试 +add_executable(test_component_manager test_component_manager.f90) +target_link_libraries(test_component_manager + PRIVATE + manager + infrastructure +) + +# 配置物理测试 +add_executable(test_config_physics test_config_physics.f90) +target_link_libraries(test_config_physics + PRIVATE + infrastructure + core +) + +# 求解器基础测试 +add_executable(test_solver_base test_solver_base.f90) +target_link_libraries(test_solver_base + PRIVATE + solver + infrastructure + core +) + +# 物理求解器测试 +add_executable(test_physics_solver test_physics_solver.f90) +target_link_libraries(test_physics_solver + PRIVATE + solver + infrastructure + core + physics + manager +) + +add_executable(test_simple_link test_simple_link.f90) +target_link_libraries(test_simple_link + PRIVATE + reconstructor + flux +) + + +add_executable(test_factory_simple test_factory_simple.f90) +target_link_libraries(test_factory_simple + PRIVATE + core + infrastructure + reconstructor + flux +) + +add_executable(test_domain_solution test_domain_solution.f90) +target_link_libraries(test_domain_solution + PRIVATE + infrastructure + core +) + +add_executable(test_component_manager_physics test_component_manager_physics.f90) +target_link_libraries(test_component_manager_physics + PRIVATE + manager + infrastructure + physics + core +) diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_architecture.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_architecture.f90 new file mode 100644 index 000000000..1c40d4677 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_architecture.f90 @@ -0,0 +1,117 @@ +! tests/test_architecture.f90 +program test_architecture + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module + use config_module + use mesh_module + use component_manager_module + + implicit none + + print *, "=== CFD架构测试 ===" + print *, "" + + ! 测试1: 基本系统 + call test_basic_systems() + + ! 测试2: 组件注册 + call test_registry_integration() + + ! 测试3: 配置验证 + call test_config_validation() + + ! 测试4: 完整流程 + call test_full_workflow() + + print *, "" + print *, "=== 架构测试完成 ===" + +contains + + subroutine test_basic_systems() + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "1. 测试基本系统..." + print *, "-------------------" + + ! 测试配置 + call config_print(config) + print *, "✓ 配置创建成功" + + ! 测试网格 + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "✓ 网格创建成功" + print *, "" + end subroutine test_basic_systems + + subroutine test_registry_integration() + print *, "2. 测试注册系统集成..." + print *, "------------------------" + + call initialize_registry(verbose=.true.) + + ! 注册核心组件 + print *, "注册核心组件:" + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + ! 显示注册内容 + call component_registry%list_simple() + print *, "注册表大小: ", registry_get_size() + + call cleanup_registry() + print *, "✓ 注册系统测试完成" + print *, "" + end subroutine test_registry_integration + + subroutine test_config_validation() + type(cfd_config) :: config + logical :: is_valid + + print *, "3. 测试配置验证..." + print *, "-------------------" + + ! 测试有效配置 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + print *, "测试配置:" + print *, " - 重构器: ", trim(config%recon_scheme), " 阶数: ", config%spatial_order + print *, " - 通量: ", trim(config%flux_type) + print *, " - 波速: ", config%wave_speed + + ! 这里调用验证函数(需要先实现) + ! is_valid = validate_config(config) + print *, "✓ 配置验证接口准备就绪" + print *, "" + end subroutine test_config_validation + + subroutine test_full_workflow() + print *, "4. 测试完整流程..." + print *, "-------------------" + + print *, "步骤1: 初始化系统 ✓" + print *, "步骤2: 创建网格 ✓" + print *, "步骤3: 设置配置 ✓" + print *, "步骤4: 创建组件 (待实现)" + print *, "步骤5: 初始化解 (待实现)" + print *, "步骤6: 时间推进 (待实现)" + print *, "步骤7: 输出结果 (待实现)" + print *, "" + + print *, "✓ 流程框架定义完成" + end subroutine test_full_workflow + +end program test_architecture \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_cfd_architecture.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_cfd_architecture.f90 new file mode 100644 index 000000000..1c40d4677 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_cfd_architecture.f90 @@ -0,0 +1,117 @@ +! tests/test_architecture.f90 +program test_architecture + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module + use config_module + use mesh_module + use component_manager_module + + implicit none + + print *, "=== CFD架构测试 ===" + print *, "" + + ! 测试1: 基本系统 + call test_basic_systems() + + ! 测试2: 组件注册 + call test_registry_integration() + + ! 测试3: 配置验证 + call test_config_validation() + + ! 测试4: 完整流程 + call test_full_workflow() + + print *, "" + print *, "=== 架构测试完成 ===" + +contains + + subroutine test_basic_systems() + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "1. 测试基本系统..." + print *, "-------------------" + + ! 测试配置 + call config_print(config) + print *, "✓ 配置创建成功" + + ! 测试网格 + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "✓ 网格创建成功" + print *, "" + end subroutine test_basic_systems + + subroutine test_registry_integration() + print *, "2. 测试注册系统集成..." + print *, "------------------------" + + call initialize_registry(verbose=.true.) + + ! 注册核心组件 + print *, "注册核心组件:" + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + ! 显示注册内容 + call component_registry%list_simple() + print *, "注册表大小: ", registry_get_size() + + call cleanup_registry() + print *, "✓ 注册系统测试完成" + print *, "" + end subroutine test_registry_integration + + subroutine test_config_validation() + type(cfd_config) :: config + logical :: is_valid + + print *, "3. 测试配置验证..." + print *, "-------------------" + + ! 测试有效配置 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + print *, "测试配置:" + print *, " - 重构器: ", trim(config%recon_scheme), " 阶数: ", config%spatial_order + print *, " - 通量: ", trim(config%flux_type) + print *, " - 波速: ", config%wave_speed + + ! 这里调用验证函数(需要先实现) + ! is_valid = validate_config(config) + print *, "✓ 配置验证接口准备就绪" + print *, "" + end subroutine test_config_validation + + subroutine test_full_workflow() + print *, "4. 测试完整流程..." + print *, "-------------------" + + print *, "步骤1: 初始化系统 ✓" + print *, "步骤2: 创建网格 ✓" + print *, "步骤3: 设置配置 ✓" + print *, "步骤4: 创建组件 (待实现)" + print *, "步骤5: 初始化解 (待实现)" + print *, "步骤6: 时间推进 (待实现)" + print *, "步骤7: 输出结果 (待实现)" + print *, "" + + print *, "✓ 流程框架定义完成" + end subroutine test_full_workflow + +end program test_architecture \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_component_manager.f90 new file mode 100644 index 000000000..f60c35056 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_component_manager.f90 @@ -0,0 +1,111 @@ +! tests/test_component_manager.f90 +program test_component_manager + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use component_manager_module, only: create_reconstructor, create_flux_calculator + use component_manager_module, only: component_manager_info, validate_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + implicit none + + type(cfd_config) :: config + class(reconstructor_base), allocatable :: recon + class(flux_calculator_base), allocatable :: flux + integer :: status + logical :: is_valid + + print *, "=== Component Manager Test ===" + print *, "" + + ! 显示组件管理器信息 + call component_manager_info() + print *, "" + + ! 测试1: 基本配置 + print *, "1. Testing basic ENO3 + Rusanov configuration..." + print *, "-----------------------------------------------" + + config%verbose = .true. + call config_print(config) + + ! 配置ENO3重构 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + call config_print(config) + print *, "" + + ! 验证配置 + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Configuration is valid" + else + print *, "[ERROR] Configuration is invalid" + end if + print *, "" + + ! 测试2: 创建组件 + print *, "2. Testing component creation..." + print *, "--------------------------------" + + ! 创建重构器(带状态检查) + recon = create_reconstructor(config, status) + if (status == 0) then + print *, "[OK] Reconstructor created successfully" + call recon%info() + else + print *, "[ERROR] Failed to create reconstructor, code:", status + end if + print *, "" + + ! 创建通量计算器 + flux = create_flux_calculator(config, status) + if (status == 0) then + print *, "[OK] Flux calculator created successfully" + call flux%info() + else + print *, "[ERROR] Failed to create flux calculator, code:", status + end if + print *, "" + + ! 测试3: WENO3重构测试 + print *, "3. Testing WENO3 configuration..." + print *, "---------------------------------" + + call config_with_reconstruction(config, "weno3", 3) + + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] WENO3 configuration is valid" + + ! 创建WENO3重构器 + recon = create_reconstructor(config) + call recon%info() + else + print *, "[ERROR] WENO3 configuration is invalid" + end if + print *, "" + + ! 测试4: 错误配置测试 + print *, "4. Testing invalid configuration..." + print *, "-----------------------------------" + + config%recon_scheme = "unknown_scheme" + config%flux_type = "unknown_flux" + + is_valid = validate_config(config) + if (.not. is_valid) then + print *, "[OK] Invalid configuration correctly rejected" + else + print *, "[ERROR] Invalid configuration should have been rejected" + end if + + ! 清理 + if (allocated(recon)) deallocate(recon) + if (allocated(flux)) deallocate(flux) + + print *, "" + print *, "=== Component manager test completed successfully ===" + +end program test_component_manager \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_component_manager_physics.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_component_manager_physics.f90 new file mode 100644 index 000000000..f2becca95 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_component_manager_physics.f90 @@ -0,0 +1,120 @@ +! tests/test_component_manager_physics.f90 (简化版) +program test_component_manager_physics + use base_modules, only: wp + use config_module, only: cfd_config, config_print, config_with_reconstruction + use component_manager_module, only: component_manager_info, validate_config + + implicit none + + type(cfd_config) :: config + logical :: is_valid + + print *, "=== Component Manager Physics Test (Simplified) ===" + print *, "" + + ! 测试1: 显示组件管理器信息 + print *, "1. Testing component manager info..." + print *, "-------------------------------------" + call component_manager_info() + print *, "" + + ! 测试2: 物理模块测试(默认) + print *, "2. Testing physics module with default configuration..." + print *, "------------------------------------------------------" + + config%verbose = .true. + call config_print(config) + + ! 验证配置 + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Default configuration is valid" + else + print *, "[ERROR] Default configuration is invalid" + end if + print *, "" + + ! 测试3: 测试物理配置 + print *, "3. Testing physics configuration..." + print *, "------------------------------------" + + ! 修改物理参数 + config%equation_type = "linear_advection" + config%problem_type = "linear_advection" + config%wave_speed = 2.5_wp + config%domain_length = 3.0_wp + + print *, "Modified physics configuration:" + print *, " Equation type: ", trim(config%equation_type) + print *, " Problem type: ", trim(config%problem_type) + print *, " Wave speed: ", config%wave_speed + print *, " Domain length: ", config%domain_length + print *, " Physics enabled: ", config%enable_physics + + ! 验证修改后的配置 + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Modified physics configuration is valid" + else + print *, "[ERROR] Modified physics configuration is invalid" + end if + print *, "" + + ! 测试4: 数值组件测试 + print *, "4. Testing numerical components with physics..." + print *, "-----------------------------------------------" + + call config_with_reconstruction(config, "weno3", 3) + config%flux_type = "rusanov" + + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Combined physics+numerics configuration is valid" + else + print *, "[ERROR] Combined configuration is invalid" + end if + print *, "" + + ! 测试5: 物理模块禁用测试 + print *, "5. Testing physics module disabled..." + print *, "---------------------------------------" + + config%enable_physics = .false. + config%verbose = .false. + + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Configuration valid even with physics disabled" + else + print *, "[ERROR] Configuration should be valid with physics disabled" + end if + print *, "" + + ! 测试6: 错误配置测试 + print *, "6. Testing error handling..." + print *, "-----------------------------" + + config%verbose = .true. + config%enable_physics = .true. + config%recon_scheme = "unknown_scheme" + config%flux_type = "unknown_flux" + config%equation_type = "unknown_equation" + config%problem_type = "unknown_problem" + + is_valid = validate_config(config) + if (.not. is_valid) then + print *, "[OK] Invalid configuration correctly rejected" + else + print *, "[ERROR] Invalid configuration should have been rejected" + end if + print *, "" + + print *, "=== Component Manager Physics Test Summary ===" + print *, "✓ Component manager info works" + print *, "✓ Configuration validation works with physics" + print *, "✓ Error handling works correctly" + print *, "✓ Combined physics+numerics validation works" + print *, "" + print *, "下一步: 集成物理模块到求解器框架" + +end program test_component_manager_physics \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_config_physics.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_config_physics.f90 new file mode 100644 index 000000000..c6fef5c0b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_config_physics.f90 @@ -0,0 +1,141 @@ +! tests/test_config_physics.f90 (修复版) +program test_config_physics + use base_modules, only: wp + use config_module, only: cfd_config, config_print, config_with_reconstruction + + implicit none + + type(cfd_config) :: config + + print *, "=== Configuration Physics Test (Simplified) ===" + print *, "" + + ! 测试1: 默认配置 + print *, "1. Testing default configuration..." + print *, "-----------------------------------" + call config_print(config) + print *, "" + + ! 测试2: 验证基础物理字段 + print *, "2. Testing basic physics fields..." + print *, "----------------------------------" + + print *, "Verifying default physics fields:" + + if (trim(config%equation_type) == "linear_advection") then + print *, " ✓ Default equation type: linear_advection" + else + print *, " ✗ Unexpected equation type: ", trim(config%equation_type) + end if + + if (trim(config%problem_type) == "linear_advection") then + print *, " ✓ Default problem type: linear_advection" + else + print *, " ✗ Unexpected problem type: ", trim(config%problem_type) + end if + + if (abs(config%domain_length - 2.0_wp) < 1e-10_wp) then + print *, " ✓ Default domain length: 2.0" + else + print *, " ✗ Unexpected domain length: ", config%domain_length + end if + + if (config%enable_physics) then + print *, " ✓ Physics enabled by default" + else + print *, " ✗ Physics not enabled by default" + end if + + print *, "" + + ! 测试3: 使用类型绑定的方法(正确的方法名) + print *, "3. Testing type-bound procedures..." + print *, "--------------------------------------" + + call config%set_physics_parameters( & + equation_type="burgers_equation", & + problem_type="sod_shock_tube", & + domain_length=3.0_wp, & + enable_physics=.false.) + + print *, "After set_physics_parameters:" + print *, " Equation type: ", trim(config%equation_type) + print *, " Problem type: ", trim(config%problem_type) + print *, " Domain length: ", config%domain_length + print *, " Physics enabled: ", config%enable_physics + + if (trim(config%equation_type) == "burgers_equation") then + print *, " ✓ Equation type modified successfully via set_physics_parameters" + end if + + if (trim(config%problem_type) == "sod_shock_tube") then + print *, " ✓ Problem type modified successfully via set_physics_parameters" + end if + + if (abs(config%domain_length - 3.0_wp) < 1e-10_wp) then + print *, " ✓ Domain length modified successfully via set_physics_parameters" + end if + + if (.not. config%enable_physics) then + print *, " ✓ Physics disabled successfully via set_physics_parameters" + end if + + print *, "" + + ! 测试4: 调用get_physics_info方法 + print *, "4. Testing get_physics_info method..." + print *, "--------------------------------------" + call config%get_physics_info() + print *, "" + + ! 测试5: 高斯脉冲配置 + print *, "5. Testing Gaussian pulse configuration..." + print *, "-----------------------------------------" + + config%ic_type = "gaussian" + config%pulse_center = 0.6_wp + config%pulse_width = 0.15_wp + + print *, "Gaussian pulse parameters:" + print *, " IC type: ", trim(config%ic_type) + print *, " Center: ", config%pulse_center + print *, " Width: ", config%pulse_width + + if (trim(config%ic_type) == "gaussian") then + print *, " ✓ Gaussian IC type set" + end if + + if (abs(config%pulse_center - 0.6_wp) < 1e-10_wp) then + print *, " ✓ Pulse center set" + end if + + if (abs(config%pulse_width - 0.15_wp) < 1e-10_wp) then + print *, " ✓ Pulse width set" + end if + + print *, "" + + ! 测试6: 重构配置 + print *, "6. Testing reconstruction configuration..." + print *, "------------------------------------------" + + call config_with_reconstruction(config, "weno", 5) + + print *, "Reconstruction configuration:" + print *, " Scheme: ", trim(config%recon_scheme) + print *, " Order: ", config%spatial_order + + if (trim(config%recon_scheme) == "weno" .and. config%spatial_order == 5) then + print *, " ✓ WENO5 configuration successful" + else + print *, " ✗ Reconstruction configuration failed" + end if + + print *, "" + + print *, "=== Configuration Physics Test Complete ===" + print *, "✓ Config module updated with physics support" + print *, "✓ Fields can be directly accessed and modified" + print *, "✓ Type-bound procedures work correctly" + +end program test_config_physics \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_domain_solution.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_domain_solution.f90 new file mode 100644 index 000000000..ff659bac2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_domain_solution.f90 @@ -0,0 +1,102 @@ +! tests/test_domain_solution.f90 +program test_domain_solution + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create, solution_reset + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + real(wp), allocatable :: initial_values(:) + integer :: i + + print *, "=== Domain and Solution Test ===" + print *, "" + + ! 测试1: 不同重构方案的ghost层计算 + print *, "1. Testing ghost layer calculation..." + print *, "--------------------------------------" + + ! ENO3 + call config_with_reconstruction(config, "eno", 3) + config%verbose = .false. + call mesh%init(ncells=10) + domain = domain_create(config, mesh) + print *, "ENO3: nghosts = ", domain%nghosts, " (expected: 3)" + + ! WENO3 + call config_with_reconstruction(config, "weno3", 3) + domain = domain_create(config, mesh) + print *, "WENO3: nghosts = ", domain%nghosts, " (expected: 2)" + + ! WENO5 + call config_with_reconstruction(config, "weno", 5) + domain = domain_create(config, mesh) + print *, "WENO5: nghosts = ", domain%nghosts, " (expected: 3)" + print *, "" + + ! 测试2: Solution数组 + print *, "2. Testing solution arrays..." + print *, "------------------------------" + + call config_with_reconstruction(config, "eno", 3) + config%verbose = .true. + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=10) + + domain = domain_create(config, mesh) + call domain%print_info() + print *, "" + + solution = solution_create(domain) + call solution%print_info() + print *, "" + + ! 测试3: 初始化和更新 + print *, "3. Testing initialization and update..." + print *, "----------------------------------------" + + allocate(initial_values(mesh%ncells)) + do i = 1, mesh%ncells + initial_values(i) = sin(2.0_wp * 3.14159265358979_wp * mesh%xcc(i) / mesh%L) + end do + + call solution%initialize(initial_values) + print *, "After initialization:" + print *, " u range: ", minval(solution%u), " to ", maxval(solution%u) + print *, " un range: ", minval(solution%un), " to ", maxval(solution%un) + + ! 修改当前解,测试更新 + solution%u = solution%u * 2.0_wp + call solution%update_old_field() + print *, "After update: max|u - un| = ", maxval(abs(solution%u - solution%un)) + print *, "" + + ! 测试4: 重置 + print *, "4. Testing reset..." + print *, "-------------------" + + call solution_reset(solution) + print *, "After reset:" + print *, " u max: ", maxval(abs(solution%u)) + print *, " un max: ", maxval(abs(solution%un)) + print *, " flux max: ", maxval(abs(solution%flux)) + print *, "" + + deallocate(initial_values) + + print *, "=== Test Summary ===" + print *, "✓ Ghost layer calculation works" + print *, "✓ Domain creation works" + print *, "✓ Solution arrays work" + print *, "✓ Initialization works" + print *, "✓ Field update works" + print *, "✓ Reset works" + print *, "" + print *, "Ready for next step: Implementing Physics modules" + +end program test_domain_solution \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_factory_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_factory_simple.f90 new file mode 100644 index 000000000..db65da7cf --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_factory_simple.f90 @@ -0,0 +1,58 @@ +! tests/test_factory_simple.f90 (修复版) +program test_factory_simple + use base_modules, only: wp ! ← 添加这行 + use config_module, only: cfd_config, config_print + use mesh_module, only: mesh_type + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(eno_reconstructor) :: eno + type(weno3_reconstructor) :: weno3 + type(rusanov_flux) :: rusanov + + print *, "=== Factory Pattern Simple Test ===" + print *, "" + + ! Test 1: Basic systems + print *, "1. Testing basic systems..." + print *, "-----------------------------" + call config_print(config) + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 2: Creating reconstructors + print *, "2. Testing reconstructors..." + print *, "------------------------------" + + ! 创建并测试ENO重构器 + print *, "Creating ENO reconstructor..." + eno = eno_reconstructor() ! 使用构造函数 + call eno%info() ! 必须调用info方法 + + print *, "" + print *, "Creating WENO3 reconstructor..." + weno3 = weno3_reconstructor() ! 使用构造函数 + call weno3%info() ! 必须调用info方法 + print *, "" + + ! Test 3: Creating flux calculator + print *, "3. Testing flux calculator..." + print *, "-------------------------------" + + print *, "Creating Rusanov flux calculator..." + rusanov = rusanov_flux() ! 使用构造函数 + call rusanov%info() ! 必须调用info方法 + print *, "" + + print *, "=== Factory pattern simple test completed successfully ===" + +end program test_factory_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_infrastructure.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_infrastructure.f90 new file mode 100644 index 000000000..22fa92d1a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_infrastructure.f90 @@ -0,0 +1,56 @@ +! tests/test_infrastructure.f90 (原test_basic_only.f90) +program test_infrastructure + use base_modules, only: wp + use config_module, only: cfd_config, config_print + use mesh_module, only: mesh_type + use registry_module, only: registry_init, registry_cleanup, & + register_component_simple, list_components + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "=== 基础设施测试 ===" + print *, "" + + ! 测试1: 配置 + print *, "1. 测试配置模块..." + print *, "-------------------" + call config_print(config) + print *, "" + + ! 测试2: 网格 + print *, "2. 测试网格模块..." + print *, "------------------" + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=5) + print *, "网格初始化:" + print *, " 单元数: ", mesh%ncells + print *, " 节点数: ", mesh%nnodes + print *, " 网格间距: ", mesh%dx + print *, "" + + ! 测试3: 注册系统 + print *, "3. 测试注册系统..." + print *, "------------------" + + call registry_init() + + ! 注册组件(使用简化版本) + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("flux", "rusanov") + + ! 列出组件 + call list_components() + print *, "" + + ! 清理 + call registry_cleanup() + + print *, "=== 基础设施测试通过 ===" + print *, "✓ 配置模块工作正常" + print *, "✓ 网格模块工作正常" + print *, "✓ 注册系统工作正常" + +end program test_infrastructure \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_physics.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_physics.f90 new file mode 100644 index 000000000..3dababb6a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_physics.f90 @@ -0,0 +1,91 @@ +! tests/test_physics.f90 (原test_physics_minimal.f90) +program test_physics + use base_modules, only: wp, ip + use linear_convection_equation, only: linear_convection_eq, create_linear_convection_eq + use linear_convection_problem, only: linear_convection_prob, create_linear_convection_prob + + implicit none + + type(linear_convection_eq) :: eq + type(linear_convection_prob) :: prob + real(wp) :: u, f, a + real(wp), allocatable :: x(:), u_ic(:), u_exact(:) + integer :: i, nx = 10 + + print *, "=== 物理模块基础测试 ===" + print *, "" + + ! 测试1: 方程功能 + print *, "1. 测试方程功能..." + print *, "-------------------" + + eq = create_linear_convection_eq(wave_speed=2.0_wp) + print *, "方程: ", eq%name + print *, "波速: ", eq%wave_speed + + u = 1.5_wp + f = eq%flux(u) + a = eq%speed() + + print *, "u = ", u + print *, "F(u) = ", f, " (期望: 3.0)" + print *, "波速 a = ", a, " (期望: 2.0)" + + if (abs(f - 3.0_wp) < 1e-10_wp .and. abs(a - 2.0_wp) < 1e-10_wp) then + print *, "✓ 方程功能正常" + else + print *, "✗ 方程功能异常" + end if + print *, "" + + ! 测试2: 问题功能 + print *, "2. 测试问题功能..." + print *, "-------------------" + + prob = create_linear_convection_prob(ic_type="step", domain_length=2.0_wp) + print *, "问题: ", prob%name + print *, "初始条件类型: ", trim(prob%ic_type) + print *, "域长度: ", prob%domain_length + + allocate(x(nx), u_ic(nx), u_exact(nx)) + do i = 1, nx + x(i) = 0.0_wp + (i-1) * 0.2_wp + end do + + ! 测试初始条件 + call prob%initial_condition(x, u_ic) + print *, "初始条件范围: ", minval(u_ic), " 到 ", maxval(u_ic) + + ! 测试精确解 + u_exact = prob%exact_solution(x, 0.0_wp) + print *, "t=0时精确解范围: ", minval(u_exact), " 到 ", maxval(u_exact) + + ! 检查阶跃函数 + if (abs(u_ic(1) - 1.0_wp) < 1e-10_wp .and. & + abs(u_ic(6) - 2.0_wp) < 1e-10_wp) then + print *, "✓ 阶跃初始条件正确" + else + print *, "✗ 阶跃初始条件错误" + end if + + ! 检查精确解与初始条件一致 + if (maxval(abs(u_ic - u_exact)) < 1e-10_wp) then + print *, "✓ t=0时精确解与初始条件一致" + else + print *, "✗ 精确解计算错误" + end if + print *, "" + + ! 测试3: 边界条件接口 + print *, "3. 测试边界条件接口..." + print *, "----------------------" + call prob%boundary_condition(u_ic, 0.0_wp) + print *, "✓ 边界条件接口正常" + print *, "" + + deallocate(x, u_ic, u_exact) + + print *, "=== 物理模块测试完成 ===" + print *, "下一步: 将物理模块集成到现有系统中" + +end program test_physics \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_physics_solver.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_physics_solver.f90 new file mode 100644 index 000000000..ba49ffbac --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_physics_solver.f90 @@ -0,0 +1,133 @@ +! tests/test_physics_solver.f90 (修复版) +program test_physics_solver + use base_modules, only: wp ! 使用一致的wp定义 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use mesh_module, only: mesh_type + use solver_base_module, only: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, & + SOLVER_COMPLETED, SOLVER_ERROR + use physics_solver_module, only: physics_solver + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(physics_solver) :: psolver + character(len=100) :: error_msg + integer :: state + + print *, "=== Physics Solver Test ===" + print *, "" + + ! 测试1: 创建物理求解器(默认物理配置) + print *, "1. Creating physics solver (default physics)..." + print *, "------------------------------------------------" + + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%dt = 0.01_wp + config%enable_physics = .true. + + call config_print(config) + + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=10) + + psolver = physics_solver(config, mesh) + call psolver%print_info() + print *, "" + + ! 测试2: 初始化 + print *, "2. Initializing physics solver..." + print *, "----------------------------------" + + call psolver%initialize() + state = psolver%get_state() + error_msg = psolver%get_error() + print *, "State after initialization: ", state + print *, "Expected: ", SOLVER_INITIALIZED + print *, "Match? ", state == SOLVER_INITIALIZED + print *, "Error message: '", trim(error_msg), "'" + print *, "" + + ! 测试3: 运行一小段时间 + print *, "3. Running physics solver (short time)..." + print *, "------------------------------------------" + + call psolver%run_to_time(0.02_wp) + state = psolver%get_state() + error_msg = psolver%get_error() + print *, "State after short run: ", state + print *, "Expected: ", SOLVER_COMPLETED + print *, "Match? ", state == SOLVER_COMPLETED + print *, "Current time: ", psolver%current_time + print *, "Current step: ", psolver%current_step + print *, "" + + ! 测试4: 禁用物理模块 + print *, "4. Testing physics solver with physics disabled..." + print *, "--------------------------------------------------" + + config%enable_physics = .false. + config%verbose = .false. + + psolver = physics_solver(config, mesh) + call psolver%initialize() + call psolver%run_to_time(0.01_wp) + + state = psolver%get_state() + error_msg = psolver%get_error() + print *, "State with physics disabled: ", state + print *, "Expected: ", SOLVER_COMPLETED + print *, "Match? ", state == SOLVER_COMPLETED + print *, "" + + ! 测试5: 不同物理配置 + print *, "5. Testing different physics configurations..." + print *, "----------------------------------------------" + + config%verbose = .true. + config%enable_physics = .true. + config%equation_type = "linear_advection" + config%problem_type = "linear_advection" + config%wave_speed = 2.5_wp + config%domain_length = 3.0_wp + config%ic_type = "gaussian" + + psolver = physics_solver(config, mesh) + call psolver%initialize() + + print *, "Physics configuration test completed" + print *, "" + + ! 测试6: 清理和错误处理 + print *, "6. Testing cleanup and error handling..." + print *, "----------------------------------------" + + call psolver%cleanup() + state = psolver%get_state() + error_msg = psolver%get_error() + print *, "State after cleanup: ", state + print *, "Expected: ", SOLVER_UNINITIALIZED + print *, "Match? ", state == SOLVER_UNINITIALIZED + + ! 尝试运行已清理的求解器 + call psolver%run_to_time(0.01_wp) + state = psolver%get_state() + error_msg = psolver%get_error() + print *, "State after error attempt: ", state + print *, "Expected: ", SOLVER_ERROR + print *, "Match? ", state == SOLVER_ERROR + print *, "Error message: '", trim(error_msg), "'" + print *, "" + + ! 最终信息 + print *, "=== Physics Solver Test Complete ===" + print *, "✓ Physics solver creation works" + print *, "✓ Physics component initialization works" + print *, "✓ Physics-enabled time stepping works" + print *, "✓ Physics disabled mode works" + print *, "✓ Different physics configurations work" + print *, "✓ Cleanup and error handling work" + print *, "" + print *, "下一步: 实现完整的数值方法(重构、通量、时间积分)" + +end program test_physics_solver \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_registry.f90 new file mode 100644 index 000000000..e82651ffb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_registry.f90 @@ -0,0 +1,87 @@ +! tests/test_registry.f90 (原test_minimal_simple.f90) +program test_registry + use base_modules, only: wp + use registry_module + use config_module + use mesh_module + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== 注册系统功能测试 ===" + print *, "" + + ! 测试1: 配置系统 + print *, "1. 测试配置系统" + print *, "--------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! 测试2: 网格系统 + print *, "2. 测试网格系统" + print *, "--------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! 测试3: 注册系统 + print *, "3. 测试注册系统" + print *, "--------------" + + call registry_init() + + ! 注册组件 + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call list_components() + print *, "注册表大小: ", registry_get_size() + print *, "" + + ! 测试组件查找 + print *, "4. 测试组件查找" + print *, "--------------" + + if (has_component_simple("reconstructor", "eno")) then + print *, "找到: reconstructor.eno" + else + print *, "未找到: reconstructor.eno" + end if + + if (has_component_simple("reconstructor", "unknown")) then + print *, "找到: reconstructor.unknown" + else + print *, "未找到: reconstructor.unknown" + end if + print *, "" + + ! 测试获取可用组件 + print *, "5. 测试注册系统功能" + print *, "------------------" + print *, "注册表已初始化: ", registry_is_initialized() + print *, "组件数量: ", registry_get_size() + print *, "" + + ! 清理 + call registry_cleanup() + + print *, "=== 注册系统测试完成 ===" + +end program test_registry \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_simple_link.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_simple_link.f90 new file mode 100644 index 000000000..71cc614e8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_simple_link.f90 @@ -0,0 +1,78 @@ +! tests/test_simple_link.f90 +program test_simple_link + use base_modules, only: wp ! ← 添加这行 + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call registry_init() + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call list_components() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component_simple("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component_simple("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Cleanup + call registry_cleanup() + + print *, "=== Minimal test completed successfully ===" + +end program test_simple_link \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_solver_base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_solver_base.f90 new file mode 100644 index 000000000..6cfe47e41 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_solver_base.f90 @@ -0,0 +1,99 @@ +! tests/test_solver_base.f90 (修复版) +program test_solver_base + ! 所有 USE 语句必须在程序开始处 + use base_modules, only: wp + use config_module, only: cfd_config, config_print, config_with_reconstruction + use mesh_module, only: mesh_type + use solver_base_module, only: solver_base, SOLVER_UNINITIALIZED, & + SOLVER_INITIALIZED, SOLVER_COMPLETED, SOLVER_ERROR + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(solver_base) :: solver + integer :: state + + print *, "=== Solver Base Test ===" + print *, "" + + ! 测试1: 创建求解器 + print *, "1. Creating solver..." + print *, "----------------------" + + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%dt = 0.01_wp + + call config_print(config) + + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=10) + + solver = solver_base(config, mesh) + call solver%print_info() + print *, "" + + ! 测试2: 初始化 + print *, "2. Initializing solver..." + print *, "-------------------------" + + call solver%initialize() + state = solver%get_state() + print *, "State after initialization: ", state + print *, "Expected: ", SOLVER_INITIALIZED + print *, "Match? ", state == SOLVER_INITIALIZED + print *, "Error message: '", trim(solver%get_error()), "'" + print *, "" + + ! 测试3: 运行求解器 + print *, "3. Running solver..." + print *, "--------------------" + + call solver%run_to_time(0.05_wp) + state = solver%get_state() + print *, "State after run: ", state + print *, "Expected: ", SOLVER_COMPLETED + print *, "Match? ", state == SOLVER_COMPLETED + print *, "Current time: ", solver%current_time + print *, "Current step: ", solver%current_step + print *, "" + + ! 测试4: 再次运行(从已完成状态) + print *, "4. Running again from completed state..." + print *, "----------------------------------------" + + ! 需要先清理才能重新运行 + call solver%cleanup() + call solver%initialize() + call solver%run_to_time(0.1_wp) + + call solver%print_info() + print *, "" + + ! 测试5: 错误处理 + print *, "5. Testing error states..." + print *, "--------------------------" + + ! 创建一个未初始化的求解器 + call solver%cleanup() + state = solver%get_state() + print *, "Uninitialized state: ", state + print *, "Expected: ", SOLVER_UNINITIALIZED + print *, "Match? ", state == SOLVER_UNINITIALIZED + + ! 尝试运行未初始化的求解器 + call solver%run_to_time(0.01_wp) + state = solver%get_state() + print *, "State after error: ", state + print *, "Expected: ", SOLVER_ERROR + print *, "Match? ", state == SOLVER_ERROR + print *, "Error message: '", trim(solver%get_error()), "'" + print *, "" + + print *, "=== Solver Base Test Complete ===" + print *, "✓ Solver base class works" + print *, "✓ State management works" + print *, "✓ Time stepping framework works" + print *, "✓ Error handling works" + +end program test_solver_base \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_solver_framework.f90 b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_solver_framework.f90 new file mode 100644 index 000000000..6754323d0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03b/tests/test_solver_framework.f90 @@ -0,0 +1,91 @@ +! tests/test_solver_framework.f90 +program test_solver_framework + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use mesh_module, only: mesh_type + use solver_module, only: cfd_solver, solver_create, solver_run, solver_cleanup + use solver_module, only: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, & + SOLVER_COMPLETED, SOLVER_ERROR + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(cfd_solver) :: solver + + print *, "=== 求解器框架测试 ===" + print *, "" + + ! 测试1: 基本创建 + print *, "1. 测试求解器创建..." + print *, "----------------------" + + ! 创建配置 + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.0_real64 + config%dt = 0.01_real64 + + call config_print(config) + print *, "" + + ! 创建网格 + call mesh%init(xmin=0.0_real64, xmax=2.0_real64, ncells=20) + call mesh%print_info() + print *, "" + + ! 创建求解器 + solver = solver_create(config, mesh) + print *, "✓ 求解器创建成功" + print *, " 状态: ", solver%get_state() + print *, "" + + ! 测试2: 求解器初始化 + print *, "2. 测试求解器初始化..." + print *, "------------------------" + + call solver%initialize() + print *, "✓ 求解器初始化完成" + print *, " 状态: ", solver%get_state() + print *, " 错误信息: '", trim(solver%get_error()), "'" + print *, "" + + ! 测试3: 简单运行 + print *, "3. 测试求解器运行..." + print *, "----------------------" + + call solver_run(solver, 0.05_real64) ! 运行到0.05秒 + print *, "✓ 求解器运行完成" + print *, " 最终状态: ", solver%get_state() + print *, "" + + ! 测试4: 清理 + print *, "4. 测试求解器清理..." + print *, "----------------------" + + call solver_cleanup(solver) + print *, "✓ 求解器清理完成" + print *, " 状态: ", solver%get_state() + print *, "" + + ! 测试5: 错误处理 + print *, "5. 测试错误处理..." + print *, "-------------------" + + ! 尝试重复初始化 + call solver%initialize() + print *, " 重复初始化状态: ", solver%get_state() + print *, " 错误信息: '", trim(solver%get_error()), "'" + + call solver_cleanup(solver) + print *, "" + + print *, "=== 框架测试总结 ===" + print *, "✓ 求解器创建/初始化/运行/清理流程验证完成" + print *, "✓ 状态管理正常工作" + print *, "✓ 错误处理机制就绪" + print *, "" + print *, "下一步: 添加实际数值计算功能" + +end program test_solver_framework \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03c/CMakeLists.txt new file mode 100644 index 000000000..55859dc2a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) +add_subdirectory(examples) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/README.md b/example/1d-linear-convection/weno3/fortran/registry/03c/README.md new file mode 100644 index 000000000..c4889757b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/README.md @@ -0,0 +1,221 @@ +# Fortran CFD Project + +基于工厂模式和注册系统的CFD求解器框架。 + +## 项目结构 + +``` +fortran_cfd/ +├── CMakeLists.txt # 根CMake配置 +├── scripts/ # 构建脚本 +│ ├── build.py # Python构建脚本(推荐) +│ ├── build.bat # Batch包装脚本 +│ ├── build.ps1 # PowerShell包装脚本 +│ └── requirements.txt # Python依赖 +├── src/ # 源代码 +│ ├── CMakeLists.txt +│ ├── core/ # 核心模块(注册系统、工厂接口) +│ ├── infrastructure/ # 基础设施(配置、网格) +│ └── numerics/ # 数值方法 +├── tests/ # 测试 +│ ├── CMakeLists.txt +│ ├── test_minimal_simple.f90 # 简单测试 +│ └── test_factory.f90 # 工厂模式测试 +└── README.md # 项目说明(本文件) +``` + +## 快速开始 + +### 使用Python脚本(推荐) + +```bash +# 进入脚本目录 +cd scripts + +# 基本构建 +python build.py + +# 清理并构建 +python build.py --clean + +# 发布构建 +python build.py --build-type Release --clean + +# 并行构建(使用所有核心) +python build.py -j + +# 不运行测试 +python build.py --no-tests + +# 查看帮助 +python build.py --help +``` + +### 使用批处理脚本 + +```bash +cd scripts +.\build.bat +``` + +### 使用PowerShell脚本 + +```powershell +cd scripts +.\build.ps1 +``` + +## 手动构建 + +```bash +# 设置Intel环境 +cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && powershell' + +# 配置项目 +mkdir build +cd build +cmake -G "Visual Studio 17 2022" -A x64 -T fortran=ifx .. + +# 构建项目 +cmake --build . --config Debug + +# 运行测试 +Debug\test_simple.exe +Debug\test_factory.exe +``` + +## 功能特性 + +✅ 组件注册系统 +✅ 工厂模式 +✅ ENO/WENO重构器 +✅ Rusanov通量计算器 +✅ 可扩展架构 +✅ 自动化测试 + +## 依赖 + +- Intel oneAPI(包含ifx编译器) +- CMake 3.12+ +- Python 3.6+(仅用于构建脚本) + +## 源代码文件 + +### 核心模块 +- `src/core/registry.f90` - 组件注册系统 +- `src/core/factory_interfaces.f90` - 工厂接口 + +### 基础设施 +- `src/infrastructure/config.f90` - 配置管理 +- `src/infrastructure/mesh.f90` - 网格生成 + +### 数值方法 +- `src/numerics/reconstructor/base.f90` - 重构器基类 +- `src/numerics/reconstructor/eno.f90` - ENO重构器 +- `src/numerics/reconstructor/weno3.f90` - WENO-3重构器 +- `src/numerics/flux/base.f90` - 通量计算器基类 +- `src/numerics/flux/rusanov.f90` - Rusanov通量 + +### 测试 +- `tests/test_minimal_simple.f90` - 简单功能测试 +- `tests/test_factory.f90` - 工厂模式测试 + +## 构建脚本 + +### Python脚本 (build.py) +主构建脚本,支持: +- 自动设置Intel oneAPI环境 +- 参数化构建选项 +- 并行编译 +- 自动化测试 +- 彩色输出 + +### Batch脚本 (build.bat) +Windows批处理包装器,调用Python脚本。 + +### PowerShell脚本 (build.ps1) +PowerShell包装器,调用Python脚本。 + +## 示例输出 + +成功构建后,会看到类似输出: + +``` +============================================================ + Fortran CFD Project Builder +============================================================ + +[1/4] Setting up Intel Fortran compiler... +✓ Intel oneAPI environment configured + +[2/4] Preparing build directory... +✓ Cleaned build directory + +[3/4] Configuring project... + $ cmake -G Visual Studio 17 2022 -A x64 -T fortran=ifx -DCMAKE_BUILD_TYPE=Debug .. +✓ CMake configuration successful + +[4/4] Building project... + $ cmake --build . --config Debug +✓ Build completed in 12.3 seconds + +============================================================ + Running Tests +============================================================ + +[TEST] Simple functionality test... +✓ Simple test passed + +[TEST] Factory pattern test... +✓ Factory test passed + +============================================================ + Build Summary +============================================================ +✓ Build directory: D:\path\to\build +✓ Build type: Debug +✓ Total time: 15.2s +``` + +## 开发指南 + +### 添加新组件 + +1. 在相应模块中创建Fortran源文件 +2. 实现工厂函数 +3. 使用`register_component_with_factory`注册组件 +4. 添加测试 + +### 扩展架构 + +项目采用模块化设计: +1. **注册系统** - 管理所有可用的组件 +2. **工厂模式** - 动态创建组件实例 +3. **抽象接口** - 确保组件兼容性 +4. **配置驱动** - 通过配置文件控制行为 + +## 故障排除 + +### 常见问题 + +1. **Intel环境未找到** + - 检查Intel oneAPI安装路径 + - 手动运行`setvars.bat` + +2. **CMake配置失败** + - 确保使用正确的生成器:`Visual Studio 17 2022` + - 检查Fortran编译器是否可用 + +3. **构建失败** + - 检查依赖项是否完整 + - 查看详细的错误信息 + +### 调试建议 + +- 使用`--verbose`参数获取详细输出 +- 检查`build/CMakeCache.txt`了解配置详情 +- 查看编译器错误日志 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/examples/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03c/examples/CMakeLists.txt new file mode 100644 index 000000000..9207e0f47 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/examples/CMakeLists.txt @@ -0,0 +1,22 @@ +# examples/CMakeLists.txt +message(STATUS "配置示例程序...") + +# 主示例程序:ENO/WENO对比 +add_executable(example_eno_weno_comparison + run_eno_weno.f90 +) + +target_link_libraries(example_eno_weno_comparison + PRIVATE + solver + infrastructure + core + physics + manager +) + +install(TARGETS example_eno_weno_comparison + RUNTIME DESTINATION bin/examples +) + +message(STATUS "示例程序配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/examples/run_eno_weno.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/examples/run_eno_weno.f90 new file mode 100644 index 000000000..961620386 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/examples/run_eno_weno.f90 @@ -0,0 +1,359 @@ +! examples/run_eno_weno.f90 (修复版) +program run_eno_weno + ! 示例程序:ENO/WENO对比分析 + + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction, config_print + use mesh_module, only: mesh_type + use registry_module, only: registry_init, registry_cleanup, initialize_default_components + use physics_solver_module, only: physics_solver, SOLVER_INITIALIZED, & + SOLVER_COMPLETED, SOLVER_ERROR + + implicit none + + ! 求解器实例 + type(cfd_config) :: config_eno3, config_weno3, config_weno5 + type(mesh_type) :: mesh + type(physics_solver) :: solver_eno3, solver_weno3, solver_weno5 + + ! 保存结果的变量 + real(wp) :: time_eno3, time_weno3, time_weno5 + integer :: steps_eno3, steps_weno3, steps_weno5 + integer :: state_eno3, state_weno3, state_weno5 + character(len=100) :: error_eno3, error_weno3, error_weno5 + + character(len=100) :: status_str + logical :: all_success + + print *, "==========================================" + print *, " ENO/WENO对比分析 - Fortran版本" + print *, "==========================================" + print *, "" + + ! 步骤0: 系统初始化 + print *, "[步骤0] 初始化系统..." + print *, "---------------------" + + ! 初始化注册系统 + call registry_init(verbose=.true.) + + ! 注册默认组件 + print *, "注册默认组件..." + call initialize_default_components() + print *, "" + + ! 步骤1: 创建网格 + print *, "[步骤1] 创建计算网格..." + print *, "------------------------" + + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=40) + call mesh%print_info() + print *, "" + + ! ========== ENO3 求解器 ========== + print *, "[步骤2] 配置并运行ENO3求解器..." + print *, "--------------------------------" + + ! 配置ENO3 + config_eno3%verbose = .true. + config_eno3%ic_type = "step" + config_eno3%wave_speed = 1.0_wp + config_eno3%final_time = 0.625_wp + config_eno3%dt = 0.0025_wp + config_eno3%rk_order = 2 + config_eno3%boundary_type = "periodic" + config_eno3%equation_type = "linear_advection" + config_eno3%problem_type = "linear_advection" + config_eno3%enable_physics = .true. + config_eno3%domain_length = 2.0_wp + + call config_with_reconstruction(config_eno3, "eno", 3) + + print *, "ENO3配置信息:" + call config_print(config_eno3) + print *, "" + + ! 创建并运行ENO3求解器 + print *, "创建ENO3求解器实例..." + solver_eno3 = physics_solver(config_eno3, mesh) + + print *, "初始化ENO3求解器..." + call solver_eno3%initialize() + + print *, "运行ENO3求解器..." + call solver_eno3%run_to_time(config_eno3%final_time) + + ! 立即保存ENO3结果 + state_eno3 = solver_eno3%get_state() + time_eno3 = solver_eno3%current_time + steps_eno3 = solver_eno3%current_step + error_eno3 = solver_eno3%get_error() + + print *, "检查ENO3求解器状态..." + if (state_eno3 == SOLVER_COMPLETED) then + print *, "✓ ENO3求解器运行完成" + print *, " 最终时间: ", time_eno3 + print *, " 总步数: ", steps_eno3 + else if (state_eno3 == SOLVER_ERROR) then + print *, "✗ ENO3求解器运行失败" + print *, " 错误信息: ", trim(error_eno3) + else + print *, "? ENO3求解器状态: ", state_eno3 + end if + print *, "" + + ! ========== WENO3 求解器 ========== + print *, "[步骤3] 配置并运行WENO3求解器..." + print *, "---------------------------------" + + ! 配置WENO3 + config_weno3%verbose = .true. + config_weno3%ic_type = "step" + config_weno3%wave_speed = 1.0_wp + config_weno3%final_time = 0.625_wp + config_weno3%dt = 0.0025_wp + config_weno3%rk_order = 2 + config_weno3%boundary_type = "periodic" + config_weno3%equation_type = "linear_advection" + config_weno3%problem_type = "linear_advection" + config_weno3%enable_physics = .true. + config_weno3%domain_length = 2.0_wp + + call config_with_reconstruction(config_weno3, "weno3", 3) + + print *, "WENO3配置信息:" + call config_print(config_weno3) + print *, "" + + ! 创建并运行WENO3求解器 + print *, "创建WENO3求解器实例..." + solver_weno3 = physics_solver(config_weno3, mesh) + + print *, "初始化WENO3求解器..." + call solver_weno3%initialize() + + print *, "运行WENO3求解器..." + call solver_weno3%run_to_time(config_weno3%final_time) + + ! 立即保存WENO3结果 + state_weno3 = solver_weno3%get_state() + time_weno3 = solver_weno3%current_time + steps_weno3 = solver_weno3%current_step + error_weno3 = solver_weno3%get_error() + + print *, "检查WENO3求解器状态..." + if (state_weno3 == SOLVER_COMPLETED) then + print *, "✓ WENO3求解器运行完成" + print *, " 最终时间: ", time_weno3 + print *, " 总步数: ", steps_weno3 + else if (state_weno3 == SOLVER_ERROR) then + print *, "✗ WENO3求解器运行失败" + print *, " 错误信息: ", trim(error_weno3) + else + print *, "? WENO3求解器状态: ", state_weno3 + end if + print *, "" + + ! ========== WENO5 求解器 ========== + print *, "[步骤4] 配置并运行WENO5求解器..." + print *, "---------------------------------" + + ! 配置WENO5 + config_weno5%verbose = .true. + config_weno5%ic_type = "step" + config_weno5%wave_speed = 1.0_wp + config_weno5%final_time = 0.625_wp + config_weno5%dt = 0.0025_wp + config_weno5%rk_order = 2 + config_weno5%boundary_type = "periodic" + config_weno5%equation_type = "linear_advection" + config_weno5%problem_type = "linear_advection" + config_weno5%enable_physics = .true. + config_weno5%domain_length = 2.0_wp + + call config_with_reconstruction(config_weno5, "weno", 5) + + print *, "WENO5配置信息:" + call config_print(config_weno5) + print *, "" + + ! 创建并运行WENO5求解器 + print *, "创建WENO5求解器实例..." + solver_weno5 = physics_solver(config_weno5, mesh) + + print *, "初始化WENO5求解器..." + call solver_weno5%initialize() + + print *, "运行WENO5求解器..." + call solver_weno5%run_to_time(config_weno5%final_time) + + ! 立即保存WENO5结果 + state_weno5 = solver_weno5%get_state() + time_weno5 = solver_weno5%current_time + steps_weno5 = solver_weno5%current_step + error_weno5 = solver_weno5%get_error() + + print *, "检查WENO5求解器状态..." + if (state_weno5 == SOLVER_COMPLETED) then + print *, "✓ WENO5求解器运行完成" + print *, " 最终时间: ", time_weno5 + print *, " 总步数: ", steps_weno5 + else if (state_weno5 == SOLVER_ERROR) then + print *, "✗ WENO5求解器运行失败" + print *, " 错误信息: ", trim(error_weno5) + else + print *, "? WENO5求解器状态: ", state_weno5 + end if + print *, "" + + ! ========== 清理求解器 ========== + print *, "[步骤5] 清理求解器..." + print *, "----------------------" + + call solver_eno3%cleanup() + call solver_weno3%cleanup() + call solver_weno5%cleanup() + + ! ========== 清理注册系统 ========== + print *, "[步骤6] 清理注册系统..." + print *, "----------------------" + call registry_cleanup() + + ! ========== 结果汇总 ========== + print *, "" + print *, "==========================================" + print *, " 结果汇总" + print *, "==========================================" + print *, "" + + print *, "求解器状态对比:" + print *, "---------------" + + ! ENO3状态 + if (state_eno3 == SOLVER_COMPLETED) then + status_str = "完成 ✓" + else if (state_eno3 == SOLVER_INITIALIZED) then + status_str = "已初始化" + else if (state_eno3 == SOLVER_ERROR) then + status_str = "错误 ✗" + else + write(status_str, '(A, I3)') "未知状态 ", state_eno3 + end if + print *, "ENO3: ", trim(status_str) + print *, " 最终时间: ", time_eno3 + print *, " 总步数: ", steps_eno3 + if (len_trim(error_eno3) > 0) then + print *, " 错误信息: ", trim(error_eno3) + end if + print *, "" + + ! WENO3状态 + if (state_weno3 == SOLVER_COMPLETED) then + status_str = "完成 ✓" + else if (state_weno3 == SOLVER_INITIALIZED) then + status_str = "已初始化" + else if (state_weno3 == SOLVER_ERROR) then + status_str = "错误 ✗" + else + write(status_str, '(A, I3)') "未知状态 ", state_weno3 + end if + print *, "WENO3: ", trim(status_str) + print *, " 最终时间: ", time_weno3 + print *, " 总步数: ", steps_weno3 + if (len_trim(error_weno3) > 0) then + print *, " 错误信息: ", trim(error_weno3) + end if + print *, "" + + ! WENO5状态 + if (state_weno5 == SOLVER_COMPLETED) then + status_str = "完成 ✓" + else if (state_weno5 == SOLVER_INITIALIZED) then + status_str = "已初始化" + else if (state_weno5 == SOLVER_ERROR) then + status_str = "错误 ✗" + else + write(status_str, '(A, I3)') "未知状态 ", state_weno5 + end if + print *, "WENO5: ", trim(status_str) + print *, " 最终时间: ", time_weno5 + print *, " 总步数: ", steps_weno5 + if (len_trim(error_weno5) > 0) then + print *, " 错误信息: ", trim(error_weno5) + end if + print *, "" + + ! ========== 最终判断 ========== + all_success = (state_eno3 == SOLVER_COMPLETED) .and. & + (state_weno3 == SOLVER_COMPLETED) .and. & + (state_weno5 == SOLVER_COMPLETED) + + if (all_success) then + print *, "✓ 所有求解器成功运行!" + print *, "" + print *, "计算参数总结:" + print *, "--------------" + print *, "网格单元数: ", mesh%ncells + print *, "时间步长: ", config_eno3%dt + print *, "最终时间: ", config_eno3%final_time + print *, "波速: ", config_eno3%wave_speed + print *, "边界条件: ", trim(config_eno3%boundary_type) + print *, "初始条件: ", trim(config_eno3%ic_type) + print *, "" + print *, "重构格式对比:" + print *, "--------------" + print *, "ENO3: 3阶本质无振荡" + print *, "WENO3: 3阶加权本质无振荡" + print *, "WENO5: 5阶加权本质无振荡" + print *, "" + print *, "性能对比:" + print *, "----------" + print *, "ENO3: ", steps_eno3, " 步,最终时间 ", time_eno3 + print *, "WENO3: ", steps_weno3, " 步,最终时间 ", time_weno3 + print *, "WENO5: ", steps_weno5, " 步,最终时间 ", time_weno5 + print *, "" + print *, "下一步开发计划:" + print *, "1. 实现真实的ENO/WENO重构算法" + print *, "2. 实现Rusanov通量计算" + print *, "3. 添加RK时间积分器" + print *, "4. 实现结果输出和可视化" + else + print *, "✗ 有求解器运行失败" + print *, "" + print *, "故障排除:" + print *, "----------" + + if (state_eno3 /= SOLVER_COMPLETED) then + print *, "• ENO3失败: 状态=", state_eno3 + if (len_trim(error_eno3) > 0) then + print *, " 错误信息: ", trim(error_eno3) + end if + end if + + if (state_weno3 /= SOLVER_COMPLETED) then + print *, "• WENO3失败: 状态=", state_weno3 + if (len_trim(error_weno3) > 0) then + print *, " 错误信息: ", trim(error_weno3) + end if + end if + + if (state_weno5 /= SOLVER_COMPLETED) then + print *, "• WENO5失败: 状态=", state_weno5 + if (len_trim(error_weno5) > 0) then + print *, " 错误信息: ", trim(error_weno5) + end if + end if + + print *, "" + print *, "可能的原因:" + print *, "1. 配置参数不正确" + print *, "2. 内存分配失败" + print *, "3. 数值计算不稳定" + end if + + print *, "" + print *, "==========================================" + print *, " 分析完成" + print *, "==========================================" + +end program run_eno_weno \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/scripts/build.bat b/example/1d-linear-convection/weno3/fortran/registry/03c/scripts/build.bat new file mode 100644 index 000000000..6fd6dc031 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/scripts/build.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Project Builder +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python build script with full Intel environment support... +echo. + +python build.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Build failed + pause + exit /b 1 +) + +echo. +echo [INFO] Build completed successfully! +echo. +echo [INFO] To run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/scripts/build.py b/example/1d-linear-convection/weno3/fortran/registry/03c/scripts/build.py new file mode 100644 index 000000000..3bf6d5375 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/scripts/build.py @@ -0,0 +1,629 @@ +#!/usr/bin/env python3 +""" +Fortran CFD Project Builder - 完整Python解决方案 +在Python内部处理Intel oneAPI环境配置 +""" + +import os +import sys +import subprocess +import shutil +import argparse +import time +import platform +import tempfile +from pathlib import Path + +class IntelEnvironment: + """Intel oneAPI环境管理器""" + + def __init__(self): + self.setvars_path = None + self.env_vars = {} + + def find_setvars(self): + """查找setvars.bat文件""" + possible_paths = [ + r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat", + r"C:\Program Files\Intel\oneAPI\setvars.bat", + r"C:\Program Files (x86)\Intel\oneAPI\compiler\latest\env\vars.bat", + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\setvars.bat"), + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\compiler\latest\env\vars.bat"), + ] + + for path in possible_paths: + if os.path.exists(path): + self.setvars_path = path + return True + + return False + + def setup_environment(self): + """设置Intel环境""" + if not self.find_setvars(): + return False + + try: + # 创建临时的批处理文件来捕获环境变量 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + f.write(f'@echo off\n') + f.write(f'call "{self.setvars_path}" >nul 2>&1\n') + f.write(f'set\n') # 输出所有环境变量 + temp_bat = f.name + + # 运行批处理文件并捕获输出 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + shell=True + ) + + # 解析环境变量 + for line in result.stdout.split('\n'): + line = line.strip() + if '=' in line: + key, value = line.split('=', 1) + self.env_vars[key.strip()] = value.strip() + + # 清理临时文件 + os.unlink(temp_bat) + + # 更新当前进程的环境变量 + os.environ.update(self.env_vars) + + return True + + except Exception as e: + print(f"设置Intel环境失败: {e}") + return False + + def get_compiler_info(self): + """获取编译器信息""" + info = {} + + # 检查ifx编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + env={**os.environ, **self.env_vars} if self.env_vars else os.environ + ) + + if result.returncode == 0: + for line in result.stdout.split('\n'): + if 'Version' in line or '版本' in line: + info['ifx_version'] = line.strip() + break + except: + pass + + # 检查环境变量 + info['ifx_root'] = self.env_vars.get('IFX_ROOT', '') + info['compiler_root'] = self.env_vars.get('ONEAPI_ROOT', '') + + return info + +class BuildSystem: + """构建系统主类""" + + def __init__(self): + self.project_root = Path(__file__).parent.parent + self.build_dir = self.project_root / "build" + self.intel_env = IntelEnvironment() + + # 设置控制台编码 + if sys.platform == "win32": + try: + import ctypes + # 设置控制台输出为UTF-8 + ctypes.windll.kernel32.SetConsoleOutputCP(65001) + except: + pass + + def print_header(self, text): + """打印标题""" + print(f"\n{'='*70}") + print(f" {text}") + print(f"{'='*70}\n") + + def print_step(self, step, total, message): + """打印步骤""" + print(f"[{step}/{total}] {message}...") + + def print_success(self, message): + """打印成功""" + print(f"\033[92m✓ {message}\033[0m") + + def print_error(self, message): + """打印错误""" + print(f"\033[91m✗ {message}\033[0m") + + def print_warning(self, message): + """打印警告""" + print(f"\033[93m! {message}\033[0m") + + def print_info(self, message): + """打印信息""" + print(f"\033[94mℹ {message}\033[0m") + + def check_prerequisites(self): + """检查前提条件""" + self.print_step(1, 6, "检查前提条件") + + # 检查Python版本 + python_version = sys.version.split()[0] + self.print_info(f"Python版本: {python_version}") + + # 检查平台 + self.print_info(f"平台: {platform.system()} {platform.release()}") + self.print_info(f"处理器核心数: {os.cpu_count()}") + + # 检查CMake + try: + result = subprocess.run( + ["cmake", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + version_line = result.stdout.split('\n')[0] + self.print_success(f"CMake: {version_line}") + else: + self.print_error("CMake未找到") + return False + except FileNotFoundError: + self.print_error("CMake未安装") + return False + + return True + + def setup_intel_environment(self, args): + """设置Intel环境""" + self.print_step(2, 6, "配置Intel oneAPI环境") + + if not self.intel_env.find_setvars(): + self.print_warning("未找到Intel oneAPI setvars.bat") + self.print_info("将尝试使用系统环境中的编译器") + + # 检查是否能直接访问编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + self.print_success("Intel编译器在系统PATH中找到") + return True + else: + self.print_warning("Intel编译器未在PATH中找到") + except: + self.print_warning("无法访问Intel编译器") + + return True # 继续,让CMake自己找编译器 + + # 设置环境 + if self.intel_env.setup_environment(): + compiler_info = self.intel_env.get_compiler_info() + + if compiler_info.get('ifx_version'): + self.print_success(f"Intel Fortran编译器: {compiler_info['ifx_version']}") + elif compiler_info.get('ifx_root'): + self.print_success(f"Intel编译器路径: {compiler_info['ifx_root']}") + else: + self.print_success("Intel oneAPI环境配置完成") + + return True + else: + self.print_warning("Intel环境配置失败,将继续使用系统环境") + return True + + def clean_build_directory(self, args): + """清理构建目录""" + if args.clean and self.build_dir.exists(): + self.print_info("清理构建目录...") + try: + shutil.rmtree(self.build_dir) + self.print_success("构建目录已清理") + except Exception as e: + self.print_error(f"清理失败: {e}") + if not args.force: + return False + return True + + def run_command(self, cmd, cwd=None, check=True, env=None): + """运行命令""" + if isinstance(cmd, list): + cmd_str = ' '.join(str(c) for c in cmd if c) + else: + cmd_str = str(cmd) + + print(f" \033[96m$\033[0m {cmd_str}") + + try: + # 合并环境变量 + exec_env = os.environ.copy() + if env: + exec_env.update(env) + if self.intel_env.env_vars: + exec_env.update(self.intel_env.env_vars) + + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=False, + env=exec_env + ) + + # 处理输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or '完成' in line or '生成' in line: + print(f" \033[92m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + if check and result.returncode != 0: + self.print_error(f"命令执行失败,退出码: {result.returncode}") + return False + + return True + + except Exception as e: + self.print_error(f"命令执行异常: {e}") + return False + + def configure_cmake(self, args): + """配置CMake""" + self.print_step(3, 6, "配置CMake项目") + + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + f"-DCMAKE_BUILD_TYPE={args.build_type}", + ] + + if args.compiler == "ifx": + cmake_cmd.extend(["-T", "fortran=ifx"]) + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + success = self.run_command(cmake_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("CMake配置完成") + else: + self.print_error("CMake配置失败") + + return success + + def build_project(self, args): + """构建项目""" + self.print_step(4, 6, "构建项目") + + build_cmd = [ + "cmake", + "--build", ".", + "--config", args.build_type, + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + success = self.run_command(build_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("项目构建完成") + else: + self.print_error("构建失败") + + return success + + def run_tests_with_environment(self, test_exe): + """运行单个测试,确保有Intel环境""" + try: + # 创建临时的批处理文件来运行测试 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'"{test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'"{test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + return result + + except Exception as e: + print(f"运行测试失败: {e}") + return None + + def run_tests(self, args): + """运行测试""" + self.print_step(5, 6, "运行测试") + + # 查找测试可执行文件 + test_dir = self.build_dir / "bin" / args.build_type + if not test_dir.exists(): + test_dir = self.build_dir / "bin" + if not test_dir.exists(): + test_dir = self.build_dir + + test_files = list(test_dir.glob("test_*.exe")) + + if not test_files: + self.print_warning("未找到测试程序") + return True + + all_passed = True + + for test_exe in sorted(test_files): + test_name = test_exe.stem + self.print_info(f"运行测试: {test_name}") + print(f" {'-'*50}") + + # 运行测试 + result = self.run_tests_with_environment(str(test_exe)) + + if result is None: + self.print_error(f" {test_name} 运行失败") + all_passed = False + continue + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + print(f" {line}") + + if result.returncode == 0: + self.print_success(f" {test_name} 通过") + else: + self.print_error(f" {test_name} 失败 (退出码: {result.returncode})") + all_passed = False + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + print() # 空行 + + return all_passed + + def create_test_runner(self, args): + """创建独立的测试运行器""" + self.print_step(6, 6, "创建测试运行器") + + runner_path = self.build_dir / "run_tests.bat" + + content = f'''@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Test Runner +echo ======================================== +echo. + +REM Setup Intel oneAPI environment +set "SETVARS_PATH={self.intel_env.setvars_path or ''}" +if exist "%SETVARS_PATH%" ( + call "%SETVARS_PATH%" >nul + echo [INFO] Intel environment configured +) else ( + echo [WARNING] Intel environment not found + echo [WARNING] Tests may fail without runtime libraries +) + +echo. + +REM Run all test executables +set "TEST_COUNT=0" +set "PASS_COUNT=0" + +for %%f in ("bin\\{args.build_type}\\test_*.exe") do ( + set /a TEST_COUNT+=1 + echo [TEST %%f] + echo {'-'*50} + + %%f + if errorlevel 1 ( + echo [FAILED] %%f + ) else ( + echo [PASSED] %%f + set /a PASS_COUNT+=1 + ) + echo. +) + +echo ======================================== +echo Tests: %PASS_COUNT%/%TEST_COUNT% passed +if %PASS_COUNT% equ %TEST_COUNT% ( + echo [SUCCESS] All tests passed! +) else ( + echo [FAILURE] Some tests failed +) +echo ======================================== + +pause +''' + + with open(runner_path, 'w', encoding='utf-8') as f: + f.write(content) + + self.print_success(f"测试运行器已创建: {runner_path}") + self.print_info(f"使用方法: cd build && run_tests.bat") + + return runner_path + + def generate_report(self, args, build_time, tests_passed): + """生成构建报告""" + self.print_header("构建完成") + + print(f"项目: {self.project_root.name}") + print(f"构建类型: {args.build_type}") + print(f"编译器: {args.compiler}") + print(f"并行作业: {args.jobs}") + print(f"总耗时: {build_time:.1f}秒") + print(f"测试结果: {'全部通过' if tests_passed else '有失败'}") + + # 显示生成的可执行文件 + bin_dir = self.build_dir / "bin" / args.build_type + if bin_dir.exists(): + print(f"\n生成的可执行文件:") + for exe in sorted(bin_dir.glob("*.exe")): + size_mb = exe.stat().st_size / (1024 * 1024) + print(f" • {exe.name} ({size_mb:.2f} MB)") + + # 显示测试运行器信息 + runner_path = self.build_dir / "run_tests.bat" + if runner_path.exists(): + print(f"\n独立测试运行器:") + print(f" • {runner_path.name}") + print(f" 在Intel oneAPI环境中运行所有测试") + + def run(self): + """运行构建系统""" + parser = argparse.ArgumentParser( + description="Fortran CFD项目构建工具", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认构建 + %(prog)s --clean # 清理后构建 + %(prog)s --build-type Release # Release构建 + %(prog)s --no-tests # 只构建,不运行测试 + %(prog)s -j8 --verbose # 8线程并行构建,详细输出 + """ + ) + + parser.add_argument("--build-type", choices=["Debug", "Release"], + default="Debug", help="构建类型") + parser.add_argument("--compiler", choices=["ifx", "ifort"], + default="ifx", help="Fortran编译器") + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-tests", action="store_true", + help="跳过测试") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + + args = parser.parse_args() + + # 开始构建 + start_time = time.time() + + self.print_header("Fortran CFD 项目构建系统") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 检查前提条件 + if not self.check_prerequisites(): + if not args.force: + return 1 + + # 2. 设置Intel环境 + if not self.setup_intel_environment(args): + if not args.force: + return 1 + + # 3. 清理目录 + if not self.clean_build_directory(args): + if not args.force: + return 1 + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 4. 配置CMake + if not self.configure_cmake(args): + if not args.force: + return 1 + + # 5. 构建项目 + if not self.build_project(args): + if not args.force: + return 1 + + # 6. 运行测试和创建测试运行器 + tests_passed = True + if not args.no_tests: + tests_passed = self.run_tests(args) + + # 创建测试运行器 + self.create_test_runner(args) # 传递 args 参数 + + # 7. 生成报告 + build_time = time.time() - start_time + self.generate_report(args, build_time, tests_passed) + + return 0 if tests_passed else 1 + + except KeyboardInterrupt: + self.print_error("\n构建被用户中断") + return 1 + except Exception as e: + self.print_error(f"构建过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + builder = BuildSystem() + return builder.run() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/scripts/requirements.txt b/example/1d-linear-convection/weno3/fortran/registry/03c/scripts/requirements.txt new file mode 100644 index 000000000..d21a12b45 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/scripts/requirements.txt @@ -0,0 +1,2 @@ +# 构建脚本的Python依赖(可选) +# 目前没有特殊依赖,保持空文件或添加未来可能需要的包 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/scripts/run_all_steps.bat b/example/1d-linear-convection/weno3/fortran/registry/03c/scripts/run_all_steps.bat new file mode 100644 index 000000000..d506149b2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/scripts/run_all_steps.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo CFD Project: All Steps +echo ======================================== +echo. + +echo [INFO] Starting Step 1: Physics Modules Test... +call run_step1.bat + +if errorlevel 1 ( + echo [ERROR] Step 1 failed + pause + exit /b 1 +) + +echo. +echo [INFO] Starting Step 2: Configuration Physics Update... +call run_step2.bat + +if errorlevel 1 ( + echo [ERROR] Step 2 failed + pause + exit /b 1 +) + +echo. +echo ======================================== +echo All Steps Completed Successfully! +echo ======================================== +echo. +echo [INFO] Next: Update component manager for physics support +echo [INFO] Run: run_step3.bat (to be created) +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/scripts/run_example.py b/example/1d-linear-convection/weno3/fortran/registry/03c/scripts/run_example.py new file mode 100644 index 000000000..d7c199178 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/scripts/run_example.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# scripts/run_example.py +""" +运行ENO/WENO示例程序 +""" + +import os +import sys +import subprocess +from pathlib import Path + +# 添加当前目录到路径 +sys.path.insert(0, str(Path(__file__).parent)) + +try: + from build import BuildSystem +except ImportError: + print("错误: 找不到build.py,请确保在scripts目录中运行") + sys.exit(1) + +def run_example(): + """运行示例程序""" + builder = BuildSystem() + + print("\n" + "="*70) + print(" 运行ENO/WENO对比示例程序") + print("="*70 + "\n") + + # 检查是否已构建 + exe_path = builder.build_dir / "bin" / "Debug" / "example_eno_weno_comparison.exe" + + if not exe_path.exists(): + print("示例程序未构建,先构建项目...") + print("-"*50) + + # 使用简化的构建 + result = subprocess.run( + ["python", "build.py", "--no-tests", "--clean"], + cwd=builder.project_root / "scripts", + capture_output=True, + text=True + ) + + if result.returncode != 0: + print("构建失败:") + print(result.stderr) + return False + + # 运行示例程序 + print("运行示例程序...") + print("-"*50) + + try: + result = subprocess.run( + [str(exe_path)], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace' + ) + + print(result.stdout) + + if result.stderr: + print("标准错误输出:") + print(result.stderr) + + return result.returncode == 0 + + except Exception as e: + print(f"运行示例程序失败: {e}") + return False + +def main(): + """主函数""" + success = run_example() + + if success: + print("\n" + "="*70) + print(" ✓ 示例程序运行成功") + print("="*70) + return 0 + else: + print("\n" + "="*70) + print(" ✗ 示例程序运行失败") + print("="*70) + return 1 + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/scripts/run_step1.bat b/example/1d-linear-convection/weno3/fortran/registry/03c/scripts/run_step1.bat new file mode 100644 index 000000000..0b6b1f17e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/scripts/run_step1.bat @@ -0,0 +1,39 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Step 1: Physics Modules Test +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python step1 script with full Intel environment support... +echo. + +python run_step1.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Step 1 failed + pause + exit /b 1 +) + +echo. +echo [INFO] Step 1 completed successfully! +echo. +echo [INFO] Next step: Update config to include physics settings +echo [INFO] Run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/scripts/run_step1.py b/example/1d-linear-convection/weno3/fortran/registry/03c/scripts/run_step1.py new file mode 100644 index 000000000..5e087a690 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/scripts/run_step1.py @@ -0,0 +1,319 @@ +#!/usr/bin/env python3 +""" +Step 1: Physics Modules Test +扩展build.py,专门用于测试物理模块 +""" + +import os +import sys +import subprocess +import time +from pathlib import Path + +# 添加当前目录到路径,以便导入build.py的类 +sys.path.insert(0, str(Path(__file__).parent)) + +try: + from build import IntelEnvironment, BuildSystem +except ImportError: + print("Error: Cannot import build.py. Make sure build.py is in the same directory.") + sys.exit(1) + +class Step1System(BuildSystem): + """Step 1 测试系统,继承自BuildSystem""" + + def __init__(self): + super().__init__() + self.test_name = "test_physics_minimal" + self.test_exe = None + + def find_test_executable(self): + """查找测试可执行文件""" + possible_paths = [ + self.build_dir / "bin" / "Debug" / f"{self.test_name}.exe", + self.build_dir / "Debug" / f"{self.test_name}.exe", + self.build_dir / f"{self.test_name}.exe", + self.build_dir / "bin" / f"{self.test_name}.exe", + ] + + for path in possible_paths: + if path.exists(): + self.test_exe = path + self.print_success(f"Found test executable: {path}") + return True + + # 如果没有找到,尝试搜索 + self.print_warning(f"Could not find {self.test_name}.exe") + self.print_info("Searching for test executables...") + + try: + result = subprocess.run( + ["dir", str(self.build_dir), "/s", "/b", "*.exe"], + capture_output=True, + text=True, + encoding='utf-8', + shell=True + ) + + if result.returncode == 0: + test_files = [line.strip() for line in result.stdout.split('\n') + if line and 'test_' in line.lower()] + + if test_files: + self.print_info("Found test files:") + for test_file in test_files: + self.print_info(f" {test_file}") + return False + except: + pass + + return False + + def run_test_with_intel_env(self): + """在Intel环境下运行测试""" + if not self.test_exe: + self.print_error("No test executable found") + return False + + self.print_step(1, 2, f"Running test: {self.test_exe.name}") + + try: + # 创建临时的批处理文件来运行测试(包含Intel环境) + import tempfile + + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + # 设置Intel环境 + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'echo [INFO] Intel environment configured\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'echo [WARNING] Intel environment not found\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + self.print_info(f"Command: {temp_bat}") + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower() or 'fail' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or 'pass' in line.lower() or '✓' in line: + print(f" \033[92m{line}\033[0m") + elif '=' in line or '---' in line: + print(f" \033[96m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + self.print_warning("Test stderr output:") + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + self.print_step(2, 2, "Test execution completed") + + if result.returncode == 0: + self.print_success("Test passed") + return True + else: + self.print_error(f"Test failed (exit code: {result.returncode})") + return False + + except Exception as e: + self.print_error(f"Failed to run test: {e}") + return False + + def build_project_if_needed(self, args): + """如果需要,构建项目""" + if args.no_build: + self.print_info("Skipping build (--no-build flag)") + return True + + self.print_step(1, 3, "Building project") + + # 调用父类的构建方法 + build_args = argparse.Namespace() + build_args.clean = args.clean + build_args.build_type = "Debug" + build_args.compiler = "ifx" + build_args.no_tests = True # 不运行所有测试 + build_args.jobs = os.cpu_count() + build_args.verbose = args.verbose + build_args.force = args.force + + # 清理构建目录 + if args.clean and self.build_dir.exists(): + self.print_info("Cleaning build directory...") + import shutil + try: + shutil.rmtree(self.build_dir) + self.print_success("Build directory cleaned") + except Exception as e: + self.print_error(f"Clean failed: {e}") + if not args.force: + return False + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 配置CMake + self.print_step(2, 3, "Configuring CMake") + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + "-DCMAKE_BUILD_TYPE=Debug", + "-T", "fortran=ifx", + ] + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + if not self.run_command(cmake_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + # 构建项目 + self.print_step(3, 3, "Building project") + build_cmd = [ + "cmake", + "--build", ".", + "--config", "Debug", + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + if not self.run_command(build_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + self.print_success("Build completed") + return True + + def run(self): + """运行Step 1测试""" + parser = argparse.ArgumentParser( + description="Step 1: Physics Modules Test", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认运行 + %(prog)s --clean # 清理后构建并测试 + %(prog)s --no-build # 只运行测试,不重新构建 + %(prog)s --verbose # 详细输出 + %(prog)s -j4 # 使用4个并行作业构建 + """ + ) + + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-build", action="store_true", + help="不重新构建,直接运行测试") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + + args = parser.parse_args() + + # 开始测试 + start_time = time.time() + + self.print_header("Step 1: Physics Modules Implementation Test") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 设置Intel环境 + self.print_step(1, 4, "Setting up Intel oneAPI environment") + if not self.setup_intel_environment(args): + if not args.force: + self.print_error("Intel environment setup failed") + return 1 + self.print_warning("Intel environment setup failed, continuing...") + + # 2. 构建项目(如果需要) + self.print_step(2, 4, "Building project if needed") + if not self.build_project_if_needed(args): + if not args.force: + return 1 + + # 3. 查找测试可执行文件 + self.print_step(3, 4, "Finding test executable") + if not self.find_test_executable(): + if not args.force: + return 1 + self.print_warning("Test executable not found, but continuing due to --force") + return 0 + + # 4. 运行测试 + self.print_step(4, 4, "Running physics module test") + test_passed = self.run_test_with_intel_env() + + # 生成报告 + test_time = time.time() - start_time + self.print_header("Step 1 Complete") + + print(f"测试: {'通过 ✓' if test_passed else '失败 ✗'}") + print(f"测试程序: {self.test_exe.name if self.test_exe else '未找到'}") + print(f"总耗时: {test_time:.1f}秒") + + if test_passed: + print(f"\n下一步: 更新配置以包含物理设置") + print(f"建议: 修改config.f90,添加physics相关字段") + return 0 + else: + if args.force: + self.print_warning("测试失败,但由于--force标志继续执行") + return 0 + return 1 + + except KeyboardInterrupt: + self.print_error("\n测试被用户中断") + return 1 + except Exception as e: + self.print_error(f"测试过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + system = Step1System() + return system.run() + +if __name__ == "__main__": + # 需要导入argparse + import argparse + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/scripts/run_step2.bat b/example/1d-linear-convection/weno3/fortran/registry/03c/scripts/run_step2.bat new file mode 100644 index 000000000..9c1f62de4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/scripts/run_step2.bat @@ -0,0 +1,39 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Step 2: Configuration Physics Update +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python step2 script with full Intel environment support... +echo. + +python run_step2.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Step 2 failed + pause + exit /b 1 +) + +echo. +echo [INFO] Step 2 completed successfully! +echo. +echo [INFO] Next step: Update component manager to support physics +echo [INFO] Run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/scripts/run_step2.py b/example/1d-linear-convection/weno3/fortran/registry/03c/scripts/run_step2.py new file mode 100644 index 000000000..c16b76088 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/scripts/run_step2.py @@ -0,0 +1,284 @@ +#!/usr/bin/env python3 +""" +Step 2: Configuration Physics Update +测试配置模块的物理功能更新 +""" + +import os +import sys +import subprocess +import time +from pathlib import Path + +# 添加当前目录到路径,以便导入build.py的类 +sys.path.insert(0, str(Path(__file__).parent)) + +try: + from build import IntelEnvironment, BuildSystem +except ImportError: + print("Error: Cannot import build.py. Make sure build.py is in the same directory.") + sys.exit(1) + +class Step2System(BuildSystem): + """Step 2 测试系统,继承自BuildSystem""" + + def __init__(self): + super().__init__() + self.test_name = "test_config_physics" + self.test_exe = None + + def find_test_executable(self): + """查找测试可执行文件""" + possible_paths = [ + self.build_dir / "bin" / "Debug" / f"{self.test_name}.exe", + self.build_dir / "Debug" / f"{self.test_name}.exe", + self.build_dir / f"{self.test_name}.exe", + self.build_dir / "bin" / f"{self.test_name}.exe", + ] + + for path in possible_paths: + if path.exists(): + self.test_exe = path + self.print_success(f"Found test executable: {path}") + return True + + return False + + def run_test_with_intel_env(self): + """在Intel环境下运行测试""" + if not self.test_exe: + self.print_error("No test executable found") + return False + + self.print_step(1, 2, f"Running test: {self.test_exe.name}") + + try: + # 创建临时的批处理文件来运行测试(包含Intel环境) + import tempfile + + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + # 设置Intel环境 + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'echo [INFO] Intel environment configured\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'echo [WARNING] Intel environment not found\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + self.print_info(f"Command: {temp_bat}") + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower() or 'fail' in line.lower() or '✗' in line: + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or 'pass' in line.lower() or '✓' in line: + print(f" \033[92m{line}\033[0m") + elif '=' in line or '---' in line or '===' in line: + print(f" \033[96m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + self.print_warning("Test stderr output:") + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + self.print_step(2, 2, "Test execution completed") + + if result.returncode == 0: + self.print_success("Test passed") + return True + else: + self.print_error(f"Test failed (exit code: {result.returncode})") + return False + + except Exception as e: + self.print_error(f"Failed to run test: {e}") + return False + + def build_project(self, args): + """构建项目""" + if args.no_build: + self.print_info("Skipping build (--no-build flag)") + return True + + self.print_step(1, 3, "Building project") + + # 清理构建目录 + if args.clean and self.build_dir.exists(): + self.print_info("Cleaning build directory...") + import shutil + try: + shutil.rmtree(self.build_dir) + self.print_success("Build directory cleaned") + except Exception as e: + self.print_error(f"Clean failed: {e}") + if not args.force: + return False + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 配置CMake + self.print_step(2, 3, "Configuring CMake") + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + "-DCMAKE_BUILD_TYPE=Debug", + "-T", "fortran=ifx", + ] + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + if not self.run_command(cmake_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + # 构建项目 + self.print_step(3, 3, "Building project") + build_cmd = [ + "cmake", + "--build", ".", + "--config", "Debug", + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + if not self.run_command(build_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + self.print_success("Build completed") + return True + + def run(self): + """运行Step 2测试""" + import argparse + + parser = argparse.ArgumentParser( + description="Step 2: Configuration Physics Update", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认运行 + %(prog)s --clean # 清理后构建并测试 + %(prog)s --no-build # 只运行测试,不重新构建 + %(prog)s --verbose # 详细输出 + """ + ) + + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-build", action="store_true", + help="不重新构建,直接运行测试") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + + args = parser.parse_args() + + # 开始测试 + start_time = time.time() + + self.print_header("Step 2: Configuration Physics Update") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 设置Intel环境 + self.print_step(1, 4, "Setting up Intel oneAPI environment") + if not self.setup_intel_environment(args): + if not args.force: + self.print_error("Intel environment setup failed") + return 1 + self.print_warning("Intel environment setup failed, continuing...") + + # 2. 构建项目(如果需要) + self.print_step(2, 4, "Building project if needed") + if not self.build_project(args): + if not args.force: + return 1 + + # 3. 查找测试可执行文件 + self.print_step(3, 4, "Finding test executable") + if not self.find_test_executable(): + self.print_error(f"Test executable {self.test_name}.exe not found") + if not args.force: + return 1 + self.print_warning("Test executable not found, but continuing due to --force") + return 0 + + # 4. 运行测试 + self.print_step(4, 4, "Running configuration physics test") + test_passed = self.run_test_with_intel_env() + + # 生成报告 + test_time = time.time() - start_time + self.print_header("Step 2 Complete") + + print(f"测试: {'通过 ✓' if test_passed else '失败 ✗'}") + print(f"测试程序: {self.test_exe.name if self.test_exe else '未找到'}") + print(f"总耗时: {test_time:.1f}秒") + + if test_passed: + print(f"\n下一步: 更新组件管理器以支持物理模块") + print(f"建议: 修改component_manager.f90,添加physics组件创建") + return 0 + else: + if args.force: + self.print_warning("测试失败,但由于--flag继续执行") + return 0 + return 1 + + except KeyboardInterrupt: + self.print_error("\n测试被用户中断") + return 1 + except Exception as e: + self.print_error(f"测试过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + system = Step2System() + return system.run() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03c/src/CMakeLists.txt new file mode 100644 index 000000000..59ad3c3f2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/CMakeLists.txt @@ -0,0 +1,31 @@ +# ==================== src\CMakeLists.txt ==================== + +# src/CMakeLists.txt +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(base) +add_subdirectory(core) +add_subdirectory(infrastructure) +add_subdirectory(numerics) +add_subdirectory(physics) # ← 新增物理模块目录 +add_subdirectory(manager) +add_subdirectory(solver) + +message(STATUS "源代码目录配置完成") + +add_executable(run_eno_weno + ${CMAKE_CURRENT_SOURCE_DIR}/run_eno_weno.f90 +) + +target_link_libraries(run_eno_weno + PRIVATE + solver + infrastructure + core + physics + manager +) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/base/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03c/src/base/CMakeLists.txt new file mode 100644 index 000000000..74f4aa65f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/base/CMakeLists.txt @@ -0,0 +1,16 @@ +# src/base/CMakeLists.txt +message(STATUS "Configuring base module...") + +add_library(base STATIC + modules.f90 + precision.f90 # 新增 +) + +set_target_properties(base PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Base module configured") + +# src/core/CMakeLists.txt +message(STATUS "Configuring core module...") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/base/modules.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/src/base/modules.f90 new file mode 100644 index 000000000..43aaee241 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/base/modules.f90 @@ -0,0 +1,36 @@ +! src/base/modules.f90 +module base_modules + use, intrinsic :: iso_fortran_env, only: real64, int32 + implicit none + + public :: wp, ip, max_name_len, string_len, cfd_config_base, component_info + + integer, parameter :: wp = real64 + integer, parameter :: ip = int32 + integer, parameter :: string_len = 100 + integer, parameter :: max_name_len = 32 + + ! 基础配置类型 + type :: cfd_config_base + character(len=max_name_len) :: ic_type = "step" + character(len=max_name_len) :: recon_scheme = "eno" + character(len=max_name_len) :: flux_type = "rusanov" + integer(ip) :: rk_order = 1 + real(wp) :: wave_speed = 1.0_wp + real(wp) :: final_time = 0.625_wp + real(wp) :: dt = 0.025_wp + character(len=max_name_len) :: boundary_type = "periodic" + integer(ip) :: spatial_order = 2 + character(len=max_name_len) :: equation_type = "linear_advection" + character(len=max_name_len) :: problem_type = "linear_advection" + logical :: verbose = .true. + end type cfd_config_base + + ! 组件信息类型 + type :: component_info + character(len=max_name_len) :: category = "" + character(len=max_name_len) :: name = "" + integer(ip) :: order = 0 + end type component_info + +end module base_modules \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/base/precision.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/src/base/precision.f90 new file mode 100644 index 000000000..4ac5fd7ef --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/base/precision.f90 @@ -0,0 +1,9 @@ +! src/base/precision.f90(简单版本) +module precision_module + use base_modules, only: wp, ip + implicit none + + ! 重新导出,确保兼容 + public :: wp, ip + +end module precision_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03c/src/core/CMakeLists.txt new file mode 100644 index 000000000..d8b8df064 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/core/CMakeLists.txt @@ -0,0 +1,14 @@ +# src/core/CMakeLists.txt +message(STATUS "Configuring core module...") + +add_library(core STATIC + registry.f90 +) + +target_link_libraries(core PRIVATE base) + +set_target_properties(core PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Core module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/core/factory_base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/src/core/factory_base.f90 new file mode 100644 index 000000000..302418a15 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/core/factory_base.f90 @@ -0,0 +1,57 @@ +! src/core/factory_base.f90 +module factory_base_module + use base_modules, only: wp, ip + use registry_module, only: create_component, has_component + + implicit none + private + public :: wp, ip, factory_base, factory_create + + ! 工厂基类 + type :: factory_base + character(len=max_name_length) :: category = "" + contains + procedure :: create => factory_base_create + procedure :: get_available => factory_base_get_available + end type factory_base + + ! 便捷函数类型 + abstract interface + function factory_function_interface(category, name) result(instance) + import :: wp + character(len=*), intent(in) :: category, name + class(*), allocatable :: instance + end function factory_function_interface + end interface + +contains + + ! 创建工厂实例 + function factory_create(category) result(factory) + character(len=*), intent(in) :: category + type(factory_base) :: factory + factory%category = trim(category) + end function factory_create + + ! 工厂创建方法 + function factory_base_create(this, name) result(instance) + class(factory_base), intent(in) :: this + character(len=*), intent(in) :: name + class(*), allocatable :: instance + + instance = create_component(this%category, name) + end function factory_base_create + + ! 获取可用组件列表(简化版) + subroutine factory_base_get_available(this, names, count) + class(factory_base), intent(in) :: this + character(len=*), allocatable, intent(out) :: names(:) + integer(ip), intent(out) :: count + + ! 这里需要实现从注册表获取列表的逻辑 + ! 暂时返回空列表 + count = 0 + allocate(character(len=max_name_length) :: names(0)) + end subroutine factory_base_get_available + +end module factory_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..a960167c3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/core/factory_interfaces.f90 @@ -0,0 +1,17 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, factory_procedure + + ! Factory procedure interface + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/src/core/registry.f90 new file mode 100644 index 000000000..d155aa19b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/core/registry.f90 @@ -0,0 +1,257 @@ +! src/core/registry.f90 (更新版) +module registry_module + use base_modules, only: wp, ip, max_name_len, component_info + + implicit none + private + + ! 明确公开所有需要的接口 + public :: wp, ip ! 类型参数 + public :: component_info ! 类型 + public :: registry_init, registry_cleanup ! 初始化/清理 + public :: register_component_simple ! 注册组件 + public :: has_component_simple ! 检查组件 + public :: list_components ! 列出组件 + public :: registry_is_initialized ! 检查初始化状态 + public :: registry_get_size ! 获取大小 + public :: initialize_default_components ! 新增:初始化默认组件 + + ! 全局注册表 + type :: component_registry + type(component_info), allocatable :: components(:) + integer(ip) :: count = 0 + integer(ip) :: capacity = 100 + logical :: initialized = .false. + logical :: verbose = .true. + logical :: default_components_added = .false. ! 新增:标记是否已添加默认组件 + end type component_registry + + type(component_registry) :: registry + +contains + + ! ==================== 公共API ==================== + + subroutine registry_init(verbose) + logical, optional, intent(in) :: verbose + + if (registry%initialized) then + if (registry%verbose) then + print *, "[REGISTRY] Already initialized" + end if + return + end if + + if (present(verbose)) then + registry%verbose = verbose + end if + + allocate(registry%components(registry%capacity)) + registry%initialized = .true. + + if (registry%verbose) then + print *, "[REGISTRY] Initialized with capacity:", registry%capacity + end if + end subroutine registry_init + + subroutine registry_cleanup() + if (allocated(registry%components)) then + deallocate(registry%components) + end if + registry%initialized = .false. + registry%count = 0 + registry%default_components_added = .false. ! 重置标记 + + if (registry%verbose) then + print *, "[REGISTRY] Cleaned up" + end if + end subroutine registry_cleanup + + ! 新增:初始化默认组件 + subroutine initialize_default_components() + if (.not. registry%initialized) then + call registry_init() + end if + + if (registry%default_components_added) then + if (registry%verbose) then + print *, "[REGISTRY] Default components already added" + end if + return + end if + + ! 注册重构器 + call register_component_simple("reconstructor", "eno", order=3) + call register_component_simple("reconstructor", "weno3", order=3) + call register_component_simple("reconstructor", "weno5", order=5) + + ! 注册通量计算器 + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + + ! 注册边界条件 + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + + ! 注册时间积分器 + call register_component_simple("integrator", "rk1", order=1) + call register_component_simple("integrator", "rk2", order=2) + call register_component_simple("integrator", "rk3", order=3) + + ! 注册方程 + call register_component_simple("equation", "linear_advection") + + ! 注册问题 + call register_component_simple("problem", "linear_advection") + + ! 注册初始条件 + call register_component_simple("initial_condition", "step") + call register_component_simple("initial_condition", "sin") + call register_component_simple("initial_condition", "gaussian") + + registry%default_components_added = .true. + + if (registry%verbose) then + print *, "[REGISTRY] Default components registered" + print *, "[REGISTRY] Total components:", registry%count + end if + end subroutine initialize_default_components + + subroutine register_component_simple(category, name, order) + character(len=*), intent(in) :: category, name + integer(ip), optional, intent(in) :: order + + integer(ip) :: i + type(component_info) :: info + + if (.not. registry%initialized) then + call registry_init() + end if + + ! 检查是否已存在 + do i = 1, registry%count + if (trim(registry%components(i)%category) == trim(category) .and. & + trim(registry%components(i)%name) == trim(name)) then + if (registry%verbose) then + print *, "[WARN] Overwriting component: ", trim(category), ".", trim(name) + end if + + ! 更新 + if (present(order)) then + registry%components(i)%order = order + else + registry%components(i)%order = 0 + end if + return + end if + end do + + ! 扩展数组 + if (registry%count >= registry%capacity) then + call expand_registry() + end if + + ! 添加新组件 + registry%count = registry%count + 1 + + info%category = trim(category) + info%name = trim(name) + info%order = 0 + if (present(order)) then + info%order = order + end if + + registry%components(registry%count) = info + + if (registry%verbose) then + print *, "[OK] Registered simple: ", trim(category), ".", trim(name) + end if + end subroutine register_component_simple + + logical function has_component_simple(category, name) + character(len=*), intent(in) :: category, name + + integer(ip) :: i + + has_component_simple = .false. + + if (.not. registry%initialized) return + + do i = 1, registry%count + if (trim(registry%components(i)%category) == trim(category) .and. & + trim(registry%components(i)%name) == trim(name)) then + has_component_simple = .true. + return + end if + end do + end function has_component_simple + + subroutine list_components(category) + character(len=*), optional, intent(in) :: category + + integer(ip) :: i, count + + if (.not. registry%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + if (registry%count == 0) then + print *, "[INFO] No components registered" + return + end if + + count = 0 + print *, "=== Registry Contents ===" + do i = 1, registry%count + if (.not. present(category) .or. & + trim(registry%components(i)%category) == trim(category)) then + call print_component_info(registry%components(i)) + count = count + 1 + end if + end do + + print *, "Total:", count, "components" + print *, "==========================" + end subroutine list_components + + ! ==================== 新增函数 ==================== + + logical function registry_is_initialized() + ! 检查注册表是否已初始化 + registry_is_initialized = registry%initialized + end function registry_is_initialized + + integer(ip) function registry_get_size() + ! 获取注册表中的组件数量 + registry_get_size = registry%count + end function registry_get_size + + ! ==================== 内部辅助函数 ==================== + + subroutine expand_registry() + type(component_info), allocatable :: temp(:) + + registry%capacity = registry%capacity * 2 + allocate(temp(registry%capacity)) + temp(1:registry%count) = registry%components(1:registry%count) + call move_alloc(temp, registry%components) + + if (registry%verbose) then + print *, "[INFO] Registry expanded to capacity:", registry%capacity + end if + end subroutine expand_registry + + subroutine print_component_info(info) + type(component_info), intent(in) :: info + + if (info%order > 0) then + print *, " [", trim(info%category), ".", trim(info%name), & + " (order:", info%order, ")]" + else + print *, " [", trim(info%category), ".", trim(info%name), "]" + end if + end subroutine print_component_info + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/core/registry_initializer.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/src/core/registry_initializer.f90 new file mode 100644 index 000000000..44023d1dd --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/core/registry_initializer.f90 @@ -0,0 +1,39 @@ +! src/core/registry_initializer.f90 (新增文件) +module registry_initializer_module + use registry_module, only: register_component_simple + implicit none + private + public :: initialize_default_registry + +contains + + subroutine initialize_default_registry() + ! 注册重构器 + call register_component_simple("reconstructor", "eno", order=3) + call register_component_simple("reconstructor", "weno3", order=3) + call register_component_simple("reconstructor", "weno5", order=5) + + ! 注册通量计算器 + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + + ! 注册边界条件 + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + + ! 注册时间积分器 + call register_component_simple("integrator", "rk1", order=1) + call register_component_simple("integrator", "rk2", order=2) + call register_component_simple("integrator", "rk3", order=3) + + ! 注册方程 + call register_component_simple("equation", "linear_advection") + + ! 注册问题 + call register_component_simple("problem", "linear_advection") + + print *, "[REGISTRY] Default components registered" + end subroutine initialize_default_registry + +end module registry_initializer_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03c/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..70cbbd2f3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/infrastructure/CMakeLists.txt @@ -0,0 +1,17 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "Configuring infrastructure module...") + +add_library(infrastructure STATIC + config.f90 + mesh.f90 + domain.f90 # 新增 + solution.f90 # 新增 +) + +target_link_libraries(infrastructure PRIVATE base) + +set_target_properties(infrastructure PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Infrastructure module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/src/infrastructure/config.f90 new file mode 100644 index 000000000..7586a1a50 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/infrastructure/config.f90 @@ -0,0 +1,144 @@ +! src/infrastructure/config.f90 (修复版) +module config_module + use base_modules, only: wp, ip, max_name_len, cfd_config_base + + implicit none + public :: wp, ip, cfd_config, config_print, config_with_reconstruction + + ! 扩展配置类型 - 添加物理相关字段 + type, extends(cfd_config_base) :: cfd_config + ! 物理参数 + real(wp) :: left_boundary_value = 1.0_wp + real(wp) :: right_boundary_value = 2.0_wp + real(wp) :: domain_length = 2.0_wp + + ! 新增:物理模块相关配置 + real(wp) :: pulse_center = 0.5_wp ! 高斯脉冲中心 + real(wp) :: pulse_width = 0.1_wp ! 高斯脉冲宽度 + logical :: enable_physics = .true. ! 是否启用物理模块 + contains + ! 新增:物理相关配置方法 + procedure :: set_physics_parameters + procedure :: get_physics_info + end type cfd_config + +contains + + subroutine config_print(cfg) + type(cfd_config), intent(in) :: cfg + + print *, "=== CFD Configuration ===" + print *, "Initial condition: ", trim(cfg%ic_type) + print *, "Reconstruction: ", trim(cfg%recon_scheme), " (order:", cfg%spatial_order, ")" + print *, "Flux type: ", trim(cfg%flux_type) + print *, "Time integration: RK", cfg%rk_order + print *, "Wave speed: ", cfg%wave_speed + print *, "Final time: ", cfg%final_time + print *, "Time step: ", cfg%dt + print *, "Boundary: ", trim(cfg%boundary_type) + + ! 新增:物理配置信息 + print *, "--- Physics Configuration ---" + print *, "Equation type: ", trim(cfg%equation_type) + print *, "Problem type: ", trim(cfg%problem_type) + print *, "Domain length: ", cfg%domain_length + print *, "Physics enabled: ", cfg%enable_physics + + if (cfg%ic_type == "gaussian") then + print *, "Pulse center: ", cfg%pulse_center + print *, "Pulse width: ", cfg%pulse_width + end if + + print *, "===============================" + end subroutine config_print + + subroutine config_with_reconstruction(cfg, scheme, order) + type(cfd_config), intent(inout) :: cfg + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + integer :: i + + ! 转换为小写 + cfg%recon_scheme = scheme + do i = 1, len_trim(cfg%recon_scheme) + if (cfg%recon_scheme(i:i) >= 'A' .and. cfg%recon_scheme(i:i) <= 'Z') then + cfg%recon_scheme(i:i) = char(ichar(cfg%recon_scheme(i:i)) + 32) + end if + end do + + ! 设置阶数 + if (present(order)) then + cfg%spatial_order = order + else + if (index(cfg%recon_scheme, 'weno') > 0) then + cfg%spatial_order = 5 + else if (trim(cfg%recon_scheme) == 'eno') then + cfg%spatial_order = 3 + end if + end if + + if (cfg%verbose) then + print *, "[CONFIG] Reconstruction: ", trim(cfg%recon_scheme), & + " Order: ", cfg%spatial_order + end if + end subroutine config_with_reconstruction + + ! ========== 新增:物理参数设置方法 ========== + + subroutine set_physics_parameters(this, equation_type, problem_type, & + domain_length, enable_physics) + class(cfd_config), intent(inout) :: this + character(len=*), intent(in), optional :: equation_type, problem_type + real(wp), intent(in), optional :: domain_length + logical, intent(in), optional :: enable_physics + + if (present(equation_type)) then + this%equation_type = trim(equation_type) + if (this%verbose) then + print *, "[CONFIG] Set equation type: ", trim(this%equation_type) + end if + end if + + if (present(problem_type)) then + this%problem_type = trim(problem_type) + if (this%verbose) then + print *, "[CONFIG] Set problem type: ", trim(this%problem_type) + end if + end if + + if (present(domain_length)) then + this%domain_length = domain_length + if (this%verbose) then + print *, "[CONFIG] Set domain length: ", this%domain_length + end if + end if + + if (present(enable_physics)) then + this%enable_physics = enable_physics + if (this%verbose) then + print *, "[CONFIG] Physics module enabled: ", this%enable_physics + end if + end if + end subroutine set_physics_parameters + + subroutine get_physics_info(this) + class(cfd_config), intent(in) :: this + + print *, "=== Physics Configuration Info ===" + print *, "Equation type: ", trim(this%equation_type) + print *, "Problem type: ", trim(this%problem_type) + print *, "Domain length: ", this%domain_length + print *, "Wave speed: ", this%wave_speed + print *, "Physics enabled: ", this%enable_physics + + if (this%ic_type == "gaussian") then + print *, "Pulse parameters:" + print *, " Center: ", this%pulse_center + print *, " Width: ", this%pulse_width + end if + + print *, "==================================" + end subroutine get_physics_info + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/infrastructure/domain.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/src/infrastructure/domain.f90 new file mode 100644 index 000000000..c3662f039 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/infrastructure/domain.f90 @@ -0,0 +1,102 @@ +! src/infrastructure/domain.f90 +module domain_module + use base_modules, only: wp, ip, max_name_len + use config_module, only: cfd_config + use mesh_module, only: mesh_type + + implicit none + private + public :: wp, ip, domain_type, domain_create, is_physical_cell + + type :: domain_type + type(cfd_config), pointer :: config => null() + type(mesh_type), pointer :: mesh => null() + integer(ip) :: nghosts = 0 + integer(ip) :: ist = 1 ! 物理区域起始索引(1-based) + integer(ip) :: ied = 1 ! 物理区域结束索引(exclusive) + integer(ip) :: ntcells = 0 ! 总单元数(含ghost) + contains + procedure :: print_info => domain_print_info + procedure :: get_physical_indices => domain_get_physical_indices + end type domain_type + +contains + + function domain_create(config, mesh) result(domain) + type(cfd_config), target, intent(in) :: config + type(mesh_type), target, intent(in) :: mesh + type(domain_type) :: domain + + domain%config => config + domain%mesh => mesh + + ! 计算ghost层数(参考Julia的_calc_nghosts) + domain%nghosts = calc_nghosts(config) + domain%ist = domain%nghosts + 1 + domain%ied = domain%ist + mesh%ncells + domain%ntcells = mesh%ncells + 2 * domain%nghosts + + if (config%verbose) then + print *, "[DOMAIN] Created:" + print *, " Ghost layers: ", domain%nghosts + print *, " Physical cells: ", domain%ist, " to ", domain%ied - 1 + print *, " Total cells: ", domain%ntcells + end if + end function domain_create + + function calc_nghosts(config) result(nghosts) + type(cfd_config), intent(in) :: config + integer(ip) :: nghosts + + character(len=max_name_len) :: scheme + + scheme = config%recon_scheme + + if (scheme == "eno") then + nghosts = config%spatial_order + else if (index(scheme, "weno") > 0) then + nghosts = config%spatial_order / 2 + 1 + else + print *, "[WARNING] Unknown scheme, using default nghosts=2" + nghosts = 2 + end if + + if (nghosts <= 0) then + print *, "[ERROR] Invalid nghosts: ", nghosts + nghosts = 2 + end if + end function calc_nghosts + + logical function is_physical_cell(this, idx) + class(domain_type), intent(in) :: this + integer(ip), intent(in) :: idx + is_physical_cell = (idx >= this%ist .and. idx < this%ied) + end function is_physical_cell + + function domain_get_physical_indices(this) result(indices) + class(domain_type), intent(in) :: this + integer(ip), allocatable :: indices(:) + integer(ip) :: i, count + + count = this%ied - this%ist + allocate(indices(count)) + + do i = 1, count + indices(i) = this%ist + i - 1 + end do + end function domain_get_physical_indices + + subroutine domain_print_info(this) + class(domain_type), intent(in) :: this + + print *, "=== Domain Information ===" + print *, "Configuration: ", trim(this%config%recon_scheme), & + " order ", this%config%spatial_order + print *, "Ghost layers: ", this%nghosts + print *, "Physical cells: ", this%ist, " to ", this%ied - 1 + print *, "Total cells: ", this%ntcells + print *, "Mesh cells: ", this%mesh%ncells + print *, "==========================" + end subroutine domain_print_info + +end module domain_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..f810f3a1b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/infrastructure/mesh.f90 @@ -0,0 +1,73 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use base_modules, only: wp, ip + + implicit none + public :: wp, ip, mesh_type, mesh_init, mesh_print_info + + ! 网格类型 + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer(ip) :: ncells = 40 + integer(ip) :: nnodes + integer(ip) :: nx + real(wp), allocatable :: x(:) ! 节点坐标 + real(wp), allocatable :: xcc(:) ! 单元中心坐标 + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer(ip), optional, intent(in) :: ncells + + integer(ip) :: i + + ! 设置参数 + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! 计算 + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! 分配内存 + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! 生成节点坐标 + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! 生成单元中心坐标 + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== Mesh Information ===" + print *, "Domain: [", this%xmin, ", ", this%xmax, "]" + print *, "Cells: ", this%ncells + print *, "Nodes: ", this%nnodes + print *, "dx: ", this%dx + print *, "L: ", this%L + print *, "========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/infrastructure/solution.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/src/infrastructure/solution.f90 new file mode 100644 index 000000000..ce88fd8a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/infrastructure/solution.f90 @@ -0,0 +1,131 @@ +! src/infrastructure/solution.f90 +module solution_module + use base_modules, only: wp, ip + use domain_module, only: domain_type + + implicit none + private + public :: wp, ip, solution_type, solution_create, solution_reset + + type :: solution_type + type(domain_type), pointer :: domain => null() + real(wp), allocatable :: u(:) ! 当前解(含ghost) + real(wp), allocatable :: un(:) ! 旧解 + real(wp), allocatable :: q_face_left(:) ! 左界面值 + real(wp), allocatable :: q_face_right(:)! 右界面值 + real(wp), allocatable :: flux(:) ! 通量 + real(wp), allocatable :: res(:) ! 残差 + contains + procedure :: initialize => solution_initialize + procedure :: update_old_field => solution_update_old_field + procedure :: print_info => solution_print_info + procedure :: reset => solution_reset_instance + end type solution_type + +contains + + function solution_create(domain) result(solution) + type(domain_type), target, intent(in) :: domain + type(solution_type) :: solution + + integer(ip) :: ncells, nnodes, ntcells + + solution%domain => domain + + ncells = domain%mesh%ncells + nnodes = domain%mesh%nnodes + ntcells = domain%ntcells + + ! 分配数组(与Julia solution.jl一致) + allocate(solution%u(ntcells), source=0.0_wp) + allocate(solution%un(ntcells), source=0.0_wp) + allocate(solution%q_face_left(nnodes), source=0.0_wp) + allocate(solution%q_face_right(nnodes), source=0.0_wp) + allocate(solution%flux(nnodes), source=0.0_wp) + allocate(solution%res(ncells), source=0.0_wp) + + if (domain%config%verbose) then + print *, "[SOLUTION] Created:" + print *, " u size: ", size(solution%u), " (with ghosts)" + print *, " flux size: ", size(solution%flux) + print *, " res size: ", size(solution%res) + end if + end function solution_create + + subroutine solution_initialize(this, initial_values) + class(solution_type), intent(inout) :: this + real(wp), intent(in), optional :: initial_values(:) + + integer(ip) :: i, idx + type(domain_type), pointer :: domain + + domain => this%domain + + if (present(initial_values)) then + ! 应用初始值到物理区域 + do i = domain%ist, domain%ied - 1 + idx = i - domain%ist + 1 + if (idx <= size(initial_values)) then + this%u(i) = initial_values(idx) + end if + end do + else + ! 默认为0 + this%u = 0.0_wp + end if + + ! 同步旧场(与Julia的update_old_field一致) + call this%update_old_field() + + if (domain%config%verbose) then + print *, "[SOLUTION] Initialized" + print *, " u range: ", minval(this%u), " to ", maxval(this%u) + end if + end subroutine solution_initialize + + subroutine solution_update_old_field(this) + class(solution_type), intent(inout) :: this + this%un = this%u ! 与Julia的 un .= u 一致 + end subroutine solution_update_old_field + + subroutine solution_reset_instance(this) + class(solution_type), intent(inout) :: this + call solution_reset(this) + end subroutine solution_reset_instance + + subroutine solution_reset(solution) + type(solution_type), intent(inout) :: solution + + if (allocated(solution%u)) solution%u = 0.0_wp + if (allocated(solution%un)) solution%un = 0.0_wp + if (allocated(solution%q_face_left)) solution%q_face_left = 0.0_wp + if (allocated(solution%q_face_right)) solution%q_face_right = 0.0_wp + if (allocated(solution%flux)) solution%flux = 0.0_wp + if (allocated(solution%res)) solution%res = 0.0_wp + + if (associated(solution%domain) .and. solution%domain%config%verbose) then + print *, "[SOLUTION] Reset" + end if + end subroutine solution_reset + + subroutine solution_print_info(this) + class(solution_type), intent(in) :: this + + print *, "=== Solution Information ===" + print *, "Arrays:" + print *, " u: ", size(this%u), " elements" + print *, " un: ", size(this%un), " elements" + print *, " q_face_left: ", size(this%q_face_left), " elements" + print *, " q_face_right: ", size(this%q_face_right), " elements" + print *, " flux: ", size(this%flux), " elements" + print *, " res: ", size(this%res), " elements" + + if (allocated(this%u)) then + print *, "Values:" + print *, " u min/max: ", minval(this%u), maxval(this%u) + print *, " un min/max: ", minval(this%un), maxval(this%un) + end if + print *, "============================" + end subroutine solution_print_info + +end module solution_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/manager/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03c/src/manager/CMakeLists.txt new file mode 100644 index 000000000..00c8bf49b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/manager/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/manager/CMakeLists.txt +message(STATUS "配置管理器模块...") + +# 创建管理器库 +add_library(manager STATIC + component_manager.f90 + component_factory.f90 +) + +# 明确依赖关系:管理器依赖所有其他模块 +target_link_libraries(manager + PRIVATE + core + infrastructure + reconstructor + flux +) + +# 设置模块输出目录 +set_target_properties(manager PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "管理器模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/manager/component_factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/src/manager/component_factory.f90 new file mode 100644 index 000000000..de8cbf1a8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/manager/component_factory.f90 @@ -0,0 +1,142 @@ +! src/manager/component_factory.f90 (完整文件) +module component_factory_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use config_module, only: cfd_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + + use eno_reconstructor_module, only: eno_reconstructor, create_eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor, create_weno3_reconstructor + use weno5_reconstructor_module, only: weno5_reconstructor, create_weno5_reconstructor + use rusanov_flux_module, only: rusanov_flux, create_rusanov_flux + + implicit none + private + public :: wp, create_reconstructor, create_flux_calculator + + ! 错误代码 + integer, parameter :: CM_SUCCESS = 0 + integer, parameter :: CM_ERROR_UNKNOWN_SCHEME = 1 + integer, parameter :: CM_ERROR_UNKNOWN_FLUX = 2 + integer, parameter :: CM_ERROR_INVALID_ORDER = 3 + +contains + + ! ==================== 重构器创建 ==================== + + function create_reconstructor(config, status) result(recon) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(reconstructor_base), allocatable :: recon + + character(len=20) :: scheme + integer :: order, error_code + + scheme = trim(adjustl(config%recon_scheme)) + order = config%spatial_order + + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating reconstructor: ", scheme, " order=", order + end if + + ! 处理"weno"作为WENO5的别名(与Julia一致) + if (scheme == "weno" .and. order == 5) then + scheme = "weno5" + end if + + select case(scheme) + case('eno') + allocate(eno_reconstructor :: recon) + select type(recon) + type is(eno_reconstructor) + recon = create_eno_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case('weno3') + allocate(weno3_reconstructor :: recon) + select type(recon) + type is(weno3_reconstructor) + recon = create_weno3_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case('weno5') + allocate(weno5_reconstructor :: recon) + select type(recon) + type is(weno5_reconstructor) + recon = create_weno5_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case default + error_code = CM_ERROR_UNKNOWN_SCHEME + if (config%verbose) then + print *, "[ERROR] Unknown reconstructor scheme: ", scheme + print *, " Available: eno, weno3, weno5" + end if + end select + + ! 检查阶数有效性 + if (error_code == CM_SUCCESS) then + if (order < 1) then + error_code = CM_ERROR_INVALID_ORDER + if (config%verbose) then + print *, "[ERROR] Invalid spatial order: ", order + end if + end if + end if + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Reconstructor creation failed" + end if + end function create_reconstructor + + ! ==================== 通量计算器创建 ==================== + + function create_flux_calculator(config, status) result(flux) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(flux_calculator_base), allocatable :: flux + + character(len=20) :: flux_type + integer :: error_code + + flux_type = trim(adjustl(config%flux_type)) + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating flux calculator: ", flux_type + end if + + select case(flux_type) + case('rusanov') + allocate(rusanov_flux :: flux) + select type(flux) + type is(rusanov_flux) + flux = create_rusanov_flux() + flux%wave_speed_default = config%wave_speed + end select + + case default + error_code = CM_ERROR_UNKNOWN_FLUX + if (config%verbose) then + print *, "[ERROR] Unknown flux type: ", flux_type + print *, " Available: rusanov" + end if + end select + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Flux calculator creation failed" + end if + end function create_flux_calculator + +end module component_factory_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/manager/component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/src/manager/component_manager.f90 new file mode 100644 index 000000000..25eac29be --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/manager/component_manager.f90 @@ -0,0 +1,76 @@ +! src/manager/component_manager.f90 (完整文件) +module component_manager_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use config_module, only: cfd_config + use component_factory_module, only: create_reconstructor, create_flux_calculator + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + + implicit none + private + public :: wp, component_manager_info, validate_config + public :: create_reconstructor, create_flux_calculator + +contains + + ! ==================== 配置验证 ==================== + + function validate_config(config) result(is_valid) + type(cfd_config), intent(in) :: config + logical :: is_valid + + integer :: status + class(reconstructor_base), allocatable :: test_recon + class(flux_calculator_base), allocatable :: test_flux + + is_valid = .false. + + ! 测试创建重构器 + test_recon = create_reconstructor(config, status) + if (status /= 0) then + if (config%verbose) then + print *, "[CONFIG VALIDATION] Invalid reconstructor configuration" + end if + return + end if + + ! 测试创建通量计算器 + test_flux = create_flux_calculator(config, status) + if (status /= 0) then + if (config%verbose) then + print *, "[CONFIG VALIDATION] Invalid flux configuration" + end if + return + end if + + ! 清理测试组件 + if (allocated(test_recon)) deallocate(test_recon) + if (allocated(test_flux)) deallocate(test_flux) + + is_valid = .true. + + if (config%verbose) then + print *, "[CONFIG VALIDATION] Configuration is valid" + end if + end function validate_config + + ! ==================== 信息显示 ==================== + + subroutine component_manager_info() + print *, "=== Component Manager ===" + print *, "Available reconstructors:" + print *, " - eno (orders: 1-7)" + print *, " - weno3 (order: 3)" + print *, " - weno5 (order: 5)" + print *, "" + print *, "Available flux calculators:" + print *, " - rusanov" + print *, "" + print *, "Features:" + print *, " - Configuration validation" + print *, " - Component creation from config" + print *, " - Error handling with status codes" + print *, "=========================" + end subroutine component_manager_info + +end module component_manager_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..66874a7ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/CMakeLists.txt @@ -0,0 +1,7 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..3fde7746b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,28 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +# Start with just the base module +add_library(flux STATIC + base.f90 + rusanov.f90 +) + +#target_link_libraries(flux PRIVATE core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 明确设置不使用 /Qm64 选项 +if(CMAKE_Fortran_COMPILER_ID MATCHES "IntelLLVM|Intel") + set_target_properties(flux PROPERTIES + Fortran_FLAGS "${CMAKE_Fortran_FLAGS} /Qm64" # 明确设置,避免继承问题 + ) +endif() + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/flux/base.f90 new file mode 100644 index 000000000..7080a7ae4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/flux/base.f90 @@ -0,0 +1,30 @@ +!src/numerics/flux/base.f90 +module flux_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, flux_calculator_base + + ! Base flux calculator type + type :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure :: info => flux_info + procedure :: print_basic_info => flux_print_basic ! 添加辅助方法 + end type flux_calculator_base + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "Flux calculator information:" + print *, " Name: ", trim(this%name) + end subroutine flux_info + + subroutine flux_print_basic(this) + class(flux_calculator_base), intent(in) :: this + print *, " Name: ", trim(this%name) + end subroutine flux_print_basic + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/flux/rusanov.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/flux/rusanov.f90 new file mode 100644 index 000000000..daa9e3bb5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/flux/rusanov.f90 @@ -0,0 +1,41 @@ +! src/numerics/flux/rusanov.f90 +module rusanov_flux_module + use, intrinsic :: iso_fortran_env, only: real64 + use flux_base_module, only: flux_calculator_base + implicit none + + private + public :: real64, rusanov_flux, create_rusanov_flux + + type, extends(flux_calculator_base) :: rusanov_flux + real(real64) :: wave_speed_default = 1.0_real64 + contains + procedure :: info => rusanov_info + end type rusanov_flux + + ! 构造函数接口 + interface rusanov_flux + module procedure create_rusanov_flux + end interface + +contains + + ! 构造函数 + type(rusanov_flux) function create_rusanov_flux() result(this) + this%name = "Rusanov" + this%wave_speed_default = 1.0_real64 + end function create_rusanov_flux + + subroutine rusanov_info(this) + class(rusanov_flux), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Flux calculator information:" + call this%print_basic_info() + + ! 添加Rusanov特有信息 + print *, " Type: Rusanov flux" + print *, " Default wave speed: ", this%wave_speed_default + end subroutine rusanov_info + +end module rusanov_flux_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..c88ea647b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,23 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "Configuring reconstructor module...") + +# Start with just the base module +add_library(reconstructor STATIC + base.f90 + eno.f90 + weno3.f90 + weno5.f90 +) + +target_link_libraries(reconstructor PRIVATE core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Reconstructor module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..53798d021 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/reconstructor/base.f90 @@ -0,0 +1,33 @@ +!src/numerics/reconstructor/base.f90 +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, reconstructor_base + + ! Base reconstructor type + type :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + contains + procedure :: info => reconstructor_info + procedure :: print_basic_info => reconstructor_print_basic ! 添加一个辅助方法 + end type reconstructor_base + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + print *, "Reconstructor information:" + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_info + + subroutine reconstructor_print_basic(this) + class(reconstructor_base), intent(in) :: this + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_print_basic + +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/reconstructor/eno.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/reconstructor/eno.f90 new file mode 100644 index 000000000..f973e8b32 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/reconstructor/eno.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/eno.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, eno_reconstructor, create_eno_reconstructor ! ← 添加这个 + + type, extends(reconstructor_base) :: eno_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => eno_info + end type eno_reconstructor + + ! 构造函数接口 + interface eno_reconstructor + module procedure create_eno_reconstructor + end interface + +contains + + ! 构造函数 + type(eno_reconstructor) function create_eno_reconstructor() result(this) + this%name = "ENO" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_eno_reconstructor + + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加ENO特有信息 + print *, " Type: ENO reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/reconstructor/weno3.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/reconstructor/weno3.f90 new file mode 100644 index 000000000..d5b7a7477 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/reconstructor/weno3.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/weno3.f90 +module weno3_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno3_reconstructor, create_weno3_reconstructor + + type, extends(reconstructor_base) :: weno3_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno3_info + end type weno3_reconstructor + + ! 构造函数接口 + interface weno3_reconstructor + module procedure create_weno3_reconstructor + end interface + +contains + + ! 构造函数 + type(weno3_reconstructor) function create_weno3_reconstructor() result(this) + this%name = "WENO3" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_weno3_reconstructor + + subroutine weno3_info(this) + class(weno3_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加WENO3特有信息 + print *, " Type: WENO-3 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno3_info + +end module weno3_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/reconstructor/weno5.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/reconstructor/weno5.f90 new file mode 100644 index 000000000..a869c67d0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/numerics/reconstructor/weno5.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/weno5.f90(新增) +module weno5_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno5_reconstructor, create_weno5_reconstructor + + type, extends(reconstructor_base) :: weno5_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno5_info + end type weno5_reconstructor + + ! 构造函数接口 + interface weno5_reconstructor + module procedure create_weno5_reconstructor + end interface + +contains + + ! 构造函数 + type(weno5_reconstructor) function create_weno5_reconstructor() result(this) + this%name = "WENO5" + this%order = 5 + this%epsilon = 1.0e-6_real64 + end function create_weno5_reconstructor + + subroutine weno5_info(this) + class(weno5_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加WENO5特有信息 + print *, " Type: WENO-5 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno5_info + +end module weno5_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/physics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03c/src/physics/CMakeLists.txt new file mode 100644 index 000000000..cc4e233ab --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/physics/CMakeLists.txt @@ -0,0 +1,19 @@ +# src/physics/CMakeLists.txt +message(STATUS "配置物理模块...") + +# 创建物理模块库 +add_library(physics STATIC + physics_interface.f90 + equations/linear_convection.f90 + problems/linear_convection_problem.f90 +) + +# 链接依赖 +target_link_libraries(physics PRIVATE base) + +# 设置模块输出目录 +set_target_properties(physics PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "物理模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/physics/equations/linear_convection.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/src/physics/equations/linear_convection.f90 new file mode 100644 index 000000000..fff7be55d --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/physics/equations/linear_convection.f90 @@ -0,0 +1,49 @@ +! src/physics/equations/linear_convection.f90 +module linear_convection_equation + use precision_module, only: wp, ip + use physics_interface, only: physics_equation + implicit none + private + + ! 具体方程类型 - 先声明 + type, extends(physics_equation) :: linear_convection_eq + real(wp) :: wave_speed = 1.0_wp + contains + procedure :: flux => lc_flux + procedure :: speed => lc_speed + end type linear_convection_eq + + ! 公开接口 + public :: wp, ip + public :: linear_convection_eq, create_linear_convection_eq + +contains + + ! 构造函数 + function create_linear_convection_eq(wave_speed) result(eq) + real(wp), intent(in), optional :: wave_speed + type(linear_convection_eq) :: eq + + eq%name = "Linear Convection" + if (present(wave_speed)) then + eq%wave_speed = wave_speed + else + eq%wave_speed = 1.0_wp + end if + end function create_linear_convection_eq + + ! 方法实现 + pure function lc_flux(this, u) result(f) + class(linear_convection_eq), intent(in) :: this + real(wp), intent(in) :: u + real(wp) :: f + f = this%wave_speed * u + end function lc_flux + + pure function lc_speed(this) result(a) + class(linear_convection_eq), intent(in) :: this + real(wp) :: a + a = this%wave_speed + end function lc_speed + +end module linear_convection_equation \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/physics/physics_interface.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/src/physics/physics_interface.f90 new file mode 100644 index 000000000..45002da6c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/physics/physics_interface.f90 @@ -0,0 +1,64 @@ +! src/physics/physics_interface.f90 +module physics_interface + use precision_module, only: wp, ip + implicit none + private + + ! 定义抽象基类型 - 先声明为私有,然后在公开部分导出 + type, abstract :: physics_equation + character(len=:), allocatable :: name + contains + procedure(eq_flux_abs), deferred :: flux + procedure(eq_speed_abs), deferred :: speed + end type physics_equation + + type, abstract :: physics_problem + character(len=:), allocatable :: name + contains + procedure(prob_ic_abs), deferred :: initial_condition + procedure(prob_bc_abs), deferred :: boundary_condition + procedure(prob_exact_abs), deferred :: exact_solution + end type physics_problem + + ! 抽象接口定义 + abstract interface + pure function eq_flux_abs(this, u) result(f) + import :: physics_equation, wp + class(physics_equation), intent(in) :: this + real(wp), intent(in) :: u + real(wp) :: f + end function eq_flux_abs + + pure function eq_speed_abs(this) result(a) + import :: physics_equation, wp + class(physics_equation), intent(in) :: this + real(wp) :: a + end function eq_speed_abs + + subroutine prob_ic_abs(this, x, u) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(in) :: x(:) + real(wp), intent(out) :: u(:) + end subroutine prob_ic_abs + + subroutine prob_bc_abs(this, u, t) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(inout) :: u(:) + real(wp), intent(in), optional :: t + end subroutine prob_bc_abs + + function prob_exact_abs(this, x, t) result(u) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(in) :: x(:), t + real(wp), dimension(size(x)) :: u + end function prob_exact_abs + end interface + + ! 公开接口 - 使用独立的public语句 + public :: wp, ip + public :: physics_equation, physics_problem + +end module physics_interface \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/physics/problems/linear_convection_problem.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/src/physics/problems/linear_convection_problem.f90 new file mode 100644 index 000000000..06226ed13 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/physics/problems/linear_convection_problem.f90 @@ -0,0 +1,118 @@ +! src/physics/problems/linear_convection_problem.f90 +module linear_convection_problem + use precision_module, only: wp, ip + use physics_interface, only: physics_problem + implicit none + private + + ! 具体问题类型 - 先声明 + type, extends(physics_problem) :: linear_convection_prob + real(wp) :: wave_speed = 1.0_wp + real(wp) :: domain_length = 2.0_wp + character(len=20) :: ic_type = "step" + character(len=20) :: boundary_type = "periodic" + contains + procedure :: initial_condition => lc_initial_condition + procedure :: boundary_condition => lc_boundary_condition + procedure :: exact_solution => lc_exact_solution + end type linear_convection_prob + + ! 公开接口 + public :: wp, ip + public :: linear_convection_prob, create_linear_convection_prob + +contains + + ! 构造函数 + function create_linear_convection_prob(wave_speed, domain_length, & + ic_type, boundary_type) result(prob) + real(wp), intent(in), optional :: wave_speed, domain_length + character(len=*), intent(in), optional :: ic_type, boundary_type + type(linear_convection_prob) :: prob + + prob%name = "Linear Convection Problem" + + if (present(wave_speed)) prob%wave_speed = wave_speed + if (present(domain_length)) prob%domain_length = domain_length + if (present(ic_type)) prob%ic_type = ic_type + if (present(boundary_type)) prob%boundary_type = boundary_type + end function create_linear_convection_prob + + ! 初始条件 + subroutine lc_initial_condition(this, x, u) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(in) :: x(:) + real(wp), intent(out) :: u(:) + + integer :: i + + select case (trim(this%ic_type)) + case ("step") + do i = 1, size(x) + if (x(i) >= 0.5_wp .and. x(i) <= 1.0_wp) then + u(i) = 2.0_wp + else + u(i) = 1.0_wp + end if + end do + + case ("sin", "sine") + do i = 1, size(x) + u(i) = sin(2.0_wp * 3.141592653589793_wp * x(i) / this%domain_length) + end do + + case ("gaussian") + do i = 1, size(x) + u(i) = exp(-((x(i) - 0.5_wp) / 0.1_wp)**2) + end do + + case default + ! 默认阶跃函数 + do i = 1, size(x) + if (x(i) >= 0.5_wp .and. x(i) <= 1.0_wp) then + u(i) = 2.0_wp + else + u(i) = 1.0_wp + end if + end do + end select + end subroutine lc_initial_condition + + ! 边界条件(虚拟实现,实际在boundary模块) + subroutine lc_boundary_condition(this, u, t) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(inout) :: u(:) + real(wp), intent(in), optional :: t + + ! 边界条件将在独立模块实现 + print *, "[PROBLEM] Boundary condition placeholder" + if (present(t)) then + print *, " Time = ", t + end if + end subroutine lc_boundary_condition + + ! 精确解(周期性平移) + function lc_exact_solution(this, x, t) result(u) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(in) :: x(:), t + real(wp), dimension(size(x)) :: u + real(wp), dimension(size(x)) :: x_shifted + integer :: i + + ! 周期性平移 + do i = 1, size(x) + x_shifted(i) = x(i) - this%wave_speed * t + ! 确保在 [0, domain_length) 范围内 + do while (x_shifted(i) < 0.0_wp) + x_shifted(i) = x_shifted(i) + this%domain_length + end do + do while (x_shifted(i) >= this%domain_length) + x_shifted(i) = x_shifted(i) - this%domain_length + end do + end do + + ! 重用初始条件函数 + call this%initial_condition(x_shifted, u) + end function lc_exact_solution + +end module linear_convection_problem \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/run_eno_weno.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/src/run_eno_weno.f90 new file mode 100644 index 000000000..5821c5a0a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/run_eno_weno.f90 @@ -0,0 +1,171 @@ +! src/run_eno_weno.f90 +program run_eno_weno + ! 使用所有需要的模块 + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction, config_print + use mesh_module, only: mesh_type + use solver_base_module, only: SOLVER_INITIALIZED, SOLVER_COMPLETED + + use physics_solver_module, only: physics_solver + use registry_module, only: registry_init, registry_cleanup + + implicit none + + type(cfd_config) :: config_eno3, config_weno3, config_weno5 + type(mesh_type) :: mesh + type(physics_solver) :: solver_eno3, solver_weno3, solver_weno5 + + character(len=100) :: output_file + integer :: status + + print *, "=== ENO/WENO对比分析 (Fortran版本) ===" + print *, "" + + ! 初始化注册系统 + call registry_init(verbose=.true.) + print *, "" + + ! 步骤1: 初始化网格 + print *, "步骤1: 初始化网格..." + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=40) + call mesh%print_info() + print *, "" + + ! 步骤2: 配置并运行ENO3求解器 + print *, "步骤2: 运行ENO3求解器..." + print *, "-------------------------" + + ! 创建ENO3配置 + config_eno3%verbose = .true. + call config_with_reconstruction(config_eno3, "eno", 3) + config_eno3%dt = 0.0025_wp + config_eno3%rk_order = 2 + config_eno3%wave_speed = 1.0_wp + config_eno3%final_time = 0.625_wp + config_eno3%ic_type = "step" + + print *, "ENO3配置:" + call config_print(config_eno3) + + ! 创建ENO3求解器 + print *, "创建ENO3求解器..." + solver_eno3 = physics_solver(config_eno3, mesh) + call solver_eno3%print_info() + print *, "" + + ! 初始化并运行ENO3求解器 + print *, "运行ENO3求解器..." + call solver_eno3%initialize() + call solver_eno3%run_to_time(config_eno3%final_time) + + if (solver_eno3%get_state() == SOLVER_COMPLETED) then + print *, "✓ ENO3求解器运行完成" + print *, " 最终时间: ", solver_eno3%current_time + print *, " 总步数: ", solver_eno3%current_step + else + print *, "✗ ENO3求解器运行失败" + print *, " 错误信息: ", trim(solver_eno3%get_error()) + end if + print *, "" + + ! 步骤3: 配置并运行WENO3求解器 + print *, "步骤3: 运行WENO3求解器..." + print *, "--------------------------" + + ! 创建WENO3配置 + config_weno3%verbose = .true. + call config_with_reconstruction(config_weno3, "weno3", 3) + config_weno3%dt = 0.0025_wp + config_weno3%rk_order = 2 + config_weno3%wave_speed = 1.0_wp + config_weno3%final_time = 0.625_wp + config_weno3%ic_type = "step" + + print *, "WENO3配置:" + call config_print(config_weno3) + + ! 创建WENO3求解器 + print *, "创建WENO3求解器..." + solver_weno3 = physics_solver(config_weno3, mesh) + call solver_weno3%print_info() + print *, "" + + ! 初始化并运行WENO3求解器 + print *, "运行WENO3求解器..." + call solver_weno3%initialize() + call solver_weno3%run_to_time(config_weno3%final_time) + + if (solver_weno3%get_state() == SOLVER_COMPLETED) then + print *, "✓ WENO3求解器运行完成" + print *, " 最终时间: ", solver_weno3%current_time + print *, " 总步数: ", solver_weno3%current_step + else + print *, "✗ WENO3求解器运行失败" + print *, " 错误信息: ", trim(solver_weno3%get_error()) + end if + print *, "" + + ! 步骤4: 配置并运行WENO5求解器 + print *, "步骤4: 运行WENO5求解器..." + print *, "--------------------------" + + ! 创建WENO5配置 + config_weno5%verbose = .true. + call config_with_reconstruction(config_weno5, "weno", 5) + config_weno5%dt = 0.0025_wp + config_weno5%rk_order = 2 + config_weno5%wave_speed = 1.0_wp + config_weno5%final_time = 0.625_wp + config_weno5%ic_type = "step" + + print *, "WENO5配置:" + call config_print(config_weno5) + + ! 创建WENO5求解器 + print *, "创建WENO5求解器..." + solver_weno5 = physics_solver(config_weno5, mesh) + call solver_weno5%print_info() + print *, "" + + ! 初始化并运行WENO5求解器 + print *, "运行WENO5求解器..." + call solver_weno5%initialize() + call solver_weno5%run_to_time(config_weno5%final_time) + + if (solver_weno5%get_state() == SOLVER_COMPLETED) then + print *, "✓ WENO5求解器运行完成" + print *, " 最终时间: ", solver_weno5%current_time + print *, " 总步数: ", solver_weno5%current_step + else + print *, "✗ WENO5求解器运行失败" + print *, " 错误信息: ", trim(solver_weno5%get_error()) + end if + print *, "" + + ! 清理注册系统 + call registry_cleanup() + + ! 清理求解器 + call solver_eno3%cleanup() + call solver_weno3%cleanup() + call solver_weno5%cleanup() + + print *, "=== 分析完成 ===" + print *, "总结:" + print *, " ENO3: 时间=", solver_eno3%current_time, " 步数=", solver_eno3%current_step + print *, " WENO3: 时间=", solver_weno3%current_time, " 步数=", solver_weno3%current_step + print *, " WENO5: 时间=", solver_weno5%current_time, " 步数=", solver_weno5%current_step + + if (solver_eno3%get_state() == SOLVER_COMPLETED .and. & + solver_weno3%get_state() == SOLVER_COMPLETED .and. & + solver_weno5%get_state() == SOLVER_COMPLETED) then + print *, "" + print *, "✓ 所有求解器成功运行!" + print *, "下一步: 添加边界条件模块" + else + print *, "" + print *, "✗ 有求解器运行失败" + print *, "需要先调试现有代码" + end if + +end program run_eno_weno \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/solver/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03c/src/solver/CMakeLists.txt new file mode 100644 index 000000000..f8499eecd --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/solver/CMakeLists.txt @@ -0,0 +1,21 @@ +# src/solver/CMakeLists.txt +message(STATUS "配置求解器模块...") + +add_library(solver STATIC + base.f90 + physics_solver.f90 +) + +target_link_libraries(solver + PRIVATE + infrastructure + core + physics + manager +) + +set_target_properties(solver PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "求解器模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/solver/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/src/solver/base.f90 new file mode 100644 index 000000000..cfd78c475 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/solver/base.f90 @@ -0,0 +1,264 @@ +! src/solver/base.f90 +module solver_base_module + use base_modules, only: wp => wp, ip => ip ! 重命名以避免冲突 + + use config_module, only: cfd_config + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create + + implicit none + private + + ! 明确导出列表 + public :: wp, ip ! 类型参数 + public :: solver_base, create_solver_base ! 类型和构造函数 + public :: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, SOLVER_RUNNING + public :: SOLVER_COMPLETED, SOLVER_ERROR ! 状态常量 + + ! 求解器状态枚举 + integer, parameter :: SOLVER_UNINITIALIZED = 0 + integer, parameter :: SOLVER_INITIALIZED = 1 + integer, parameter :: SOLVER_RUNNING = 2 + integer, parameter :: SOLVER_COMPLETED = 3 + integer, parameter :: SOLVER_ERROR = 4 + + ! 求解器基类 + type :: solver_base + ! 基本组件 + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + + ! 状态管理 + integer :: state = SOLVER_UNINITIALIZED + character(len=100) :: error_message = "" + real(wp) :: current_time = 0.0_wp + integer(ip) :: current_step = 0 + + ! 时间控制 + real(wp) :: dt_original = 0.0_wp + contains + procedure :: initialize => solver_base_initialize + procedure :: step => solver_base_step + procedure :: run_to_time => solver_base_run_to_time + procedure :: cleanup => solver_base_cleanup + procedure :: get_state => solver_base_get_state + procedure :: get_error => solver_base_get_error + procedure :: print_info => solver_base_print_info + end type solver_base + + ! 构造函数接口 + interface solver_base + module procedure create_solver_base + end interface + +contains + + ! ==================== 构造函数 ==================== + + function create_solver_base(config, mesh) result(solver) + type(cfd_config), intent(in) :: config + type(mesh_type), intent(in) :: mesh + type(solver_base) :: solver + + solver%config = config + solver%mesh = mesh + + ! 创建域 + solver%domain = domain_create(config, mesh) + + ! 创建解 + solver%solution = solution_create(solver%domain) + + ! 保存原始时间步长 + solver%dt_original = config%dt + + if (config%verbose) then + print *, "[SOLVER] Base solver created" + print *, " Mesh cells: ", mesh%ncells + print *, " Domain total cells: ", solver%domain%ntcells + end if + end function create_solver_base + + ! ==================== 初始化 ==================== + + subroutine solver_base_initialize(this) + class(solver_base), intent(inout) :: this + + if (this%state == SOLVER_INITIALIZED) then + if (this%config%verbose) then + print *, "[SOLVER] Already initialized" + end if + return + end if + + ! 初始化解(通过配置) + ! 这里暂时简化,实际需要调用初始条件工厂 + print *, "[INFO] Base solver initialized (simplified)" + + ! 更新状态 + this%state = SOLVER_INITIALIZED + this%current_time = 0.0_wp + this%current_step = 0 + + if (this%config%verbose) then + print *, "[SOLVER] Initialized at t = ", this%current_time + end if + end subroutine solver_base_initialize + + ! ==================== 单步计算(虚方法) ==================== + + subroutine solver_base_step(this, dt) + class(solver_base), intent(inout) :: this + real(wp), intent(in) :: dt + + ! 基类中这只是虚方法,需要在子类中实现 + print *, "[INFO] Base solver step (virtual method)" + print *, " dt = ", dt + print *, " t = ", this%current_time + + ! 更新时间 + this%current_time = this%current_time + dt + this%current_step = this%current_step + 1 + + ! 简单模拟:只是更新状态 + if (this%config%verbose) then + print *, "[SOLVER] Step completed: t = ", this%current_time, & + ", step = ", this%current_step + end if + end subroutine solver_base_step + + ! ==================== 运行到指定时间 ==================== + + subroutine solver_base_run_to_time(this, final_time) + class(solver_base), intent(inout) :: this + real(wp), intent(in) :: final_time + + real(wp) :: dt, t_remaining + integer :: step_count + + if (this%state /= SOLVER_INITIALIZED) then + this%error_message = "Solver not initialized" + this%state = SOLVER_ERROR + if (this%config%verbose) then + print *, "[SOLVER BASE ERROR] Not initialized: ", trim(this%error_message) + end if + return + end if + + this%state = SOLVER_RUNNING + step_count = 0 + + if (this%config%verbose) then + print *, "[SOLVER BASE] Running from t = ", this%current_time, & + " to t = ", final_time + print *, " Time step: ", this%config%dt + end if + + do while (this%current_time < final_time - 1e-12_wp) + ! 计算时间步长 + t_remaining = final_time - this%current_time + dt = min(this%config%dt, t_remaining) + + ! 执行时间步 + call this%step(dt) + + step_count = step_count + 1 + + ! 每50步输出一次进度 + if (mod(step_count, 50) == 0 .and. this%config%verbose) then + print *, "[SOLVER BASE] Progress: t = ", this%current_time, & + " / ", final_time, " (step ", step_count, ")" + end if + end do + + ! 恢复原始时间步长 + this%config%dt = this%dt_original + + ! 更新状态 + this%state = SOLVER_COMPLETED + + if (this%config%verbose) then + print *, "[SOLVER BASE] Run completed:" + print *, " Final time: ", this%current_time + print *, " Total steps: ", this%current_step + print *, " State: ", this%state + end if + end subroutine solver_base_run_to_time + + ! ==================== 清理 ==================== + + subroutine solver_base_cleanup(this) + class(solver_base), intent(inout) :: this + + ! 重置状态 + this%state = SOLVER_UNINITIALIZED + this%current_time = 0.0_wp + this%current_step = 0 + this%error_message = "" + + if (this%config%verbose) then + print *, "[SOLVER] Cleaned up" + end if + end subroutine solver_base_cleanup + + ! ==================== 状态查询 ==================== + + function solver_base_get_state(this) result(state) + class(solver_base), intent(in) :: this + integer :: state + state = this%state + end function solver_base_get_state + + function solver_base_get_error(this) result(error_msg) + class(solver_base), intent(in) :: this + character(len=100) :: error_msg + error_msg = trim(this%error_message) + end function solver_base_get_error + + ! ==================== 信息打印 ==================== + + subroutine solver_base_print_info(this) + class(solver_base), intent(in) :: this + + character(len=20) :: state_str + + ! 状态字符串 + select case (this%state) + case (SOLVER_UNINITIALIZED) + state_str = "Uninitialized" + case (SOLVER_INITIALIZED) + state_str = "Initialized" + case (SOLVER_RUNNING) + state_str = "Running" + case (SOLVER_COMPLETED) + state_str = "Completed" + case (SOLVER_ERROR) + state_str = "Error" + case default + state_str = "Unknown" + end select + + print *, "=== Solver Information ===" + print *, "State: ", trim(state_str) + print *, "Current time: ", this%current_time + print *, "Current step: ", this%current_step + print *, "Error message: '", trim(this%error_message), "'" + + ! 配置信息 + print *, "Configuration:" + print *, " Scheme: ", trim(this%config%recon_scheme) + print *, " Order: ", this%config%spatial_order + print *, " dt: ", this%config%dt + + ! 域信息 + print *, "Domain:" + print *, " Ghost layers: ", this%domain%nghosts + print *, " Physical cells: ", this%domain%ist, " to ", this%domain%ied - 1 + + print *, "=========================" + end subroutine solver_base_print_info + +end module solver_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/src/solver/physics_solver.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/src/solver/physics_solver.f90 new file mode 100644 index 000000000..f41ac89fd --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/src/solver/physics_solver.f90 @@ -0,0 +1,503 @@ +! src/solver/physics_solver.f90 (简化版) +module physics_solver_module + use base_modules, only: wp => wp, ip => ip + use config_module, only: cfd_config + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create + + use physics_interface, only: physics_equation, physics_problem + use linear_convection_equation, only: linear_convection_eq, create_linear_convection_eq + use linear_convection_problem, only: linear_convection_prob, create_linear_convection_prob + + use component_manager_module, only: create_reconstructor, create_flux_calculator + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + + implicit none + private + + ! 明确导出列表 + public :: wp, ip, physics_solver, create_physics_solver + public :: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, SOLVER_RUNNING + public :: SOLVER_COMPLETED, SOLVER_ERROR + + ! 求解器状态枚举 + integer, parameter :: SOLVER_UNINITIALIZED = 0 + integer, parameter :: SOLVER_INITIALIZED = 1 + integer, parameter :: SOLVER_RUNNING = 2 + integer, parameter :: SOLVER_COMPLETED = 3 + integer, parameter :: SOLVER_ERROR = 4 + + ! 物理求解器类型(不继承,独立实现) + type :: physics_solver + ! 基本组件 + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + + ! 物理组件 + class(physics_equation), allocatable :: equation + class(physics_problem), allocatable :: problem + + ! 数值组件 + class(reconstructor_base), allocatable :: reconstructor + class(flux_calculator_base), allocatable :: flux_calculator + + ! 状态管理 + integer :: state = SOLVER_UNINITIALIZED + character(len=100) :: error_message = "" + real(wp) :: current_time = 0.0_wp + integer(ip) :: current_step = 0 + + ! 时间控制 + real(wp) :: dt_original = 0.0_wp + logical :: physics_initialized = .false. + contains + procedure :: initialize => physics_solver_initialize + procedure :: step => physics_solver_step + procedure :: run_to_time => physics_solver_run_to_time + procedure :: cleanup => physics_solver_cleanup + procedure :: get_state => physics_solver_get_state + procedure :: get_error => physics_solver_get_error + procedure :: print_info => physics_solver_print_info + procedure, private :: create_physics_components + procedure, private :: create_numerical_components + end type physics_solver + + ! 构造函数接口 + interface physics_solver + module procedure create_physics_solver + end interface + +contains + + ! ==================== 构造函数 ==================== + + function create_physics_solver(config, mesh) result(solver) + type(cfd_config), intent(in) :: config + type(mesh_type), intent(in) :: mesh + type(physics_solver) :: solver + + solver%config = config + solver%mesh = mesh + + ! 创建域 + solver%domain = domain_create(config, mesh) + + ! 创建解 + solver%solution = solution_create(solver%domain) + + ! 保存原始时间步长 + solver%dt_original = config%dt + + ! 创建组件 + call solver%create_physics_components() + call solver%create_numerical_components() + + if (config%verbose) then + print *, "[PHYSICS SOLVER] Created:" + print *, " Mesh cells: ", mesh%ncells + print *, " Domain total cells: ", solver%domain%ntcells + end if + end function create_physics_solver + + ! ==================== 创建物理组件 ==================== + + subroutine create_physics_components(this) + class(physics_solver), intent(inout) :: this + + ! 检查是否启用物理模块 + if (.not. this%config%enable_physics) then + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Physics module disabled" + end if + return + end if + + ! 创建物理方程 + select case (trim(this%config%equation_type)) + case ("linear_advection") + allocate(linear_convection_eq :: this%equation) + select type(eq => this%equation) + type is(linear_convection_eq) + eq = create_linear_convection_eq(wave_speed=this%config%wave_speed) + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Created linear convection equation" + print *, " Wave speed: ", eq%wave_speed + end if + end select + + case default + if (this%config%verbose) then + print *, "[WARNING] Unknown equation type: ", trim(this%config%equation_type) + print *, " Using linear convection as default" + end if + + allocate(linear_convection_eq :: this%equation) + select type(eq => this%equation) + type is(linear_convection_eq) + eq = create_linear_convection_eq(wave_speed=this%config%wave_speed) + end select + end select + + ! 创建物理问题 + select case (trim(this%config%problem_type)) + case ("linear_advection") + allocate(linear_convection_prob :: this%problem) + select type(prob => this%problem) + type is(linear_convection_prob) + prob = create_linear_convection_prob( & + wave_speed=this%config%wave_speed, & + domain_length=this%config%domain_length, & + ic_type=this%config%ic_type, & + boundary_type=this%config%boundary_type) + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Created linear convection problem" + print *, " IC type: ", trim(prob%ic_type) + end if + end select + + case default + if (this%config%verbose) then + print *, "[WARNING] Unknown problem type: ", trim(this%config%problem_type) + print *, " Using linear convection as default" + end if + + allocate(linear_convection_prob :: this%problem) + select type(prob => this%problem) + type is(linear_convection_prob) + prob = create_linear_convection_prob( & + wave_speed=this%config%wave_speed, & + domain_length=this%config%domain_length, & + ic_type=this%config%ic_type, & + boundary_type=this%config%boundary_type) + end select + end select + + this%physics_initialized = .true. + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Physics components created" + end if + end subroutine create_physics_components + + ! ==================== 创建数值组件 ==================== + + subroutine create_numerical_components(this) + class(physics_solver), intent(inout) :: this + integer :: status + + ! 创建重构器 + this%reconstructor = create_reconstructor(this%config, status) + if (status /= 0) then + print *, "[ERROR] Failed to create reconstructor" + this%state = SOLVER_ERROR + this%error_message = "Failed to create reconstructor" + return + end if + + ! 创建通量计算器 + this%flux_calculator = create_flux_calculator(this%config, status) + if (status /= 0) then + print *, "[ERROR] Failed to create flux calculator" + this%state = SOLVER_ERROR + this%error_message = "Failed to create flux calculator" + return + end if + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Numerical components created" + end if + end subroutine create_numerical_components + + ! ==================== 初始化 ==================== + + subroutine physics_solver_initialize(this) + class(physics_solver), intent(inout) :: this + + if (this%state == SOLVER_INITIALIZED) then + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Already initialized" + end if + return + end if + + ! 如果启用了物理模块,应用初始条件 + if (this%physics_initialized .and. allocated(this%problem)) then + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Applying initial condition" + end if + + select type(prob => this%problem) + type is(linear_convection_prob) + ! 获取网格单元中心坐标 + call prob%initial_condition(this%mesh%xcc, & + this%solution%u(this%domain%ist:this%domain%ied-1)) + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Initial condition applied" + end if + end select + else + ! 简化的初始化:阶跃函数 + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Using simplified initialization" + end if + + ! 在 [0.5, 1.0] 区域内设为 2.0,其他区域为 1.0 + where (this%mesh%xcc >= 0.5_wp .and. this%mesh%xcc <= 1.0_wp) + this%solution%u(this%domain%ist:this%domain%ied-1) = 2.0_wp + elsewhere + this%solution%u(this%domain%ist:this%domain%ied-1) = 1.0_wp + end where + end if + + ! 同步旧场 + call this%solution%update_old_field() + + ! 更新状态 + this%state = SOLVER_INITIALIZED + this%current_time = 0.0_wp + this%current_step = 0 + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Initialized at t = ", this%current_time + end if + end subroutine physics_solver_initialize + + ! ==================== 时间步进 ==================== + + subroutine physics_solver_step(this, dt) + class(physics_solver), intent(inout) :: this + real(wp), intent(in) :: dt + + integer :: i + real(wp) :: u_val, f_val + + if (this%config%verbose .and. mod(this%current_step, 100) == 0) then + print *, "[PHYSICS SOLVER] Step ", this%current_step + 1, & + " dt = ", dt, " t = ", this%current_time + end if + + ! 更新旧场 + call this%solution%update_old_field() + + ! 简化的数值方法 + do i = this%domain%ist, this%domain%ied - 1 + u_val = this%solution%un(i) ! 使用旧值 + + ! 简单的线性对流:u_t + a*u_x = 0 + ! 使用一阶迎风格式 + this%solution%u(i) = u_val - dt * this%config%wave_speed * & + (u_val - this%solution%un(i-1)) / this%mesh%dx + end do + + ! 更新时间 + this%current_time = this%current_time + dt + this%current_step = this%current_step + 1 + + ! 每100步输出一次进度 + if (this%config%verbose .and. mod(this%current_step, 100) == 0) then + print *, "[PHYSICS SOLVER] Step ", this%current_step, & + " completed, t = ", this%current_time + end if + end subroutine physics_solver_step + + ! ==================== 运行到指定时间 ==================== + + subroutine physics_solver_run_to_time(this, final_time) + class(physics_solver), intent(inout) :: this + real(wp), intent(in) :: final_time + + real(wp) :: dt, t_remaining + integer :: step_count + + if (this%state /= SOLVER_INITIALIZED) then + this%error_message = "Solver not initialized" + this%state = SOLVER_ERROR + if (this%config%verbose) then + print *, "[PHYSICS SOLVER ERROR] Not initialized" + end if + return + end if + + this%state = SOLVER_RUNNING + step_count = 0 + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Running from t = ", this%current_time, & + " to t = ", final_time + end if + + do while (this%current_time < final_time - 1e-12_wp) + ! 计算时间步长 + t_remaining = final_time - this%current_time + dt = min(this%config%dt, t_remaining) + + ! 执行时间步 + call this%step(dt) + + step_count = step_count + 1 + + ! 每100步输出一次进度 + if (mod(step_count, 100) == 0 .and. this%config%verbose) then + print *, "[PHYSICS SOLVER] Progress: t = ", this%current_time, & + " / ", final_time + end if + end do + + ! 恢复原始时间步长 + this%config%dt = this%dt_original + + ! 更新状态 + this%state = SOLVER_COMPLETED + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Run completed:" + print *, " Final time: ", this%current_time + print *, " Total steps: ", this%current_step + print *, " Final u range: ", minval(this%solution%u), " to ", maxval(this%solution%u) + end if + end subroutine physics_solver_run_to_time + + ! ==================== 清理 ==================== + + subroutine physics_solver_cleanup(this) + class(physics_solver), intent(inout) :: this + + integer :: old_state + real(wp) :: old_time + integer(ip) :: old_step + + ! 保存清理前的状态用于调试 + old_state = this%state + old_time = this%current_time + old_step = this%current_step + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Cleaning up..." + print *, " Before cleanup - State: ", old_state + print *, " Before cleanup - Time: ", old_time + print *, " Before cleanup - Steps: ", old_step + end if + + ! 清理物理组件 + if (allocated(this%equation)) then + deallocate(this%equation) + if (this%config%verbose) then + print *, " Deallocated equation" + end if + end if + + if (allocated(this%problem)) then + deallocate(this%problem) + if (this%config%verbose) then + print *, " Deallocated problem" + end if + end if + + ! 清理数值组件 + if (allocated(this%reconstructor)) then + deallocate(this%reconstructor) + if (this%config%verbose) then + print *, " Deallocated reconstructor" + end if + end if + + if (allocated(this%flux_calculator)) then + deallocate(this%flux_calculator) + if (this%config%verbose) then + print *, " Deallocated flux calculator" + end if + end if + + ! 重置状态 - 但不重置解数组(保持分配) + this%state = SOLVER_UNINITIALIZED + this%current_time = 0.0_wp + this%current_step = 0 + this%error_message = "" + this%physics_initialized = .false. + + if (this%config%verbose) then + print *, " After cleanup - State: ", this%state + print *, " After cleanup - Time: ", this%current_time + print *, " After cleanup - Steps: ", this%current_step + print *, "[PHYSICS SOLVER] Cleanup completed" + end if + + end subroutine physics_solver_cleanup + + ! ==================== 状态查询 ==================== + + function physics_solver_get_state(this) result(state) + class(physics_solver), intent(in) :: this + integer :: state + state = this%state + end function physics_solver_get_state + + function physics_solver_get_error(this) result(error_msg) + class(physics_solver), intent(in) :: this + character(len=100) :: error_msg + error_msg = trim(this%error_message) + end function physics_solver_get_error + + ! ==================== 信息打印 ==================== + + subroutine physics_solver_print_info(this) + class(physics_solver), intent(in) :: this + + character(len=20) :: state_str + + ! 状态字符串 + select case (this%state) + case (SOLVER_UNINITIALIZED) + state_str = "Uninitialized" + case (SOLVER_INITIALIZED) + state_str = "Initialized" + case (SOLVER_RUNNING) + state_str = "Running" + case (SOLVER_COMPLETED) + state_str = "Completed" + case (SOLVER_ERROR) + state_str = "Error" + case default + write(state_str, '(A, I3)') "Unknown ", this%state + end select + + print *, "=== Physics Solver Information ===" + print *, "State: ", trim(state_str), " (", this%state, ")" + print *, "Current time: ", this%current_time + print *, "Current step: ", this%current_step + print *, "Error message: '", trim(this%error_message), "'" + + ! 配置信息 + print *, "Configuration:" + print *, " Scheme: ", trim(this%config%recon_scheme) + print *, " Order: ", this%config%spatial_order + print *, " dt: ", this%config%dt + print *, " Final time: ", this%config%final_time + + ! 域信息 + print *, "Domain:" + print *, " Ghost layers: ", this%domain%nghosts + print *, " Physical cells: ", this%domain%ist, " to ", this%domain%ied - 1 + + ! 物理信息 + print *, "Physics:" + print *, " Initialized: ", this%physics_initialized + print *, " Equation type: ", trim(this%config%equation_type) + print *, " Problem type: ", trim(this%config%problem_type) + + ! 解信息 + if (allocated(this%solution%u)) then + print *, "Solution:" + print *, " u size: ", size(this%solution%u) + print *, " u physical size: ", this%domain%ied - this%domain%ist + end if + + print *, "===================================" + end subroutine physics_solver_print_info + +end module physics_solver_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/test_physics_solver_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/test_physics_solver_simple.f90 new file mode 100644 index 000000000..ff659bac2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/test_physics_solver_simple.f90 @@ -0,0 +1,102 @@ +! tests/test_domain_solution.f90 +program test_domain_solution + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create, solution_reset + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + real(wp), allocatable :: initial_values(:) + integer :: i + + print *, "=== Domain and Solution Test ===" + print *, "" + + ! 测试1: 不同重构方案的ghost层计算 + print *, "1. Testing ghost layer calculation..." + print *, "--------------------------------------" + + ! ENO3 + call config_with_reconstruction(config, "eno", 3) + config%verbose = .false. + call mesh%init(ncells=10) + domain = domain_create(config, mesh) + print *, "ENO3: nghosts = ", domain%nghosts, " (expected: 3)" + + ! WENO3 + call config_with_reconstruction(config, "weno3", 3) + domain = domain_create(config, mesh) + print *, "WENO3: nghosts = ", domain%nghosts, " (expected: 2)" + + ! WENO5 + call config_with_reconstruction(config, "weno", 5) + domain = domain_create(config, mesh) + print *, "WENO5: nghosts = ", domain%nghosts, " (expected: 3)" + print *, "" + + ! 测试2: Solution数组 + print *, "2. Testing solution arrays..." + print *, "------------------------------" + + call config_with_reconstruction(config, "eno", 3) + config%verbose = .true. + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=10) + + domain = domain_create(config, mesh) + call domain%print_info() + print *, "" + + solution = solution_create(domain) + call solution%print_info() + print *, "" + + ! 测试3: 初始化和更新 + print *, "3. Testing initialization and update..." + print *, "----------------------------------------" + + allocate(initial_values(mesh%ncells)) + do i = 1, mesh%ncells + initial_values(i) = sin(2.0_wp * 3.14159265358979_wp * mesh%xcc(i) / mesh%L) + end do + + call solution%initialize(initial_values) + print *, "After initialization:" + print *, " u range: ", minval(solution%u), " to ", maxval(solution%u) + print *, " un range: ", minval(solution%un), " to ", maxval(solution%un) + + ! 修改当前解,测试更新 + solution%u = solution%u * 2.0_wp + call solution%update_old_field() + print *, "After update: max|u - un| = ", maxval(abs(solution%u - solution%un)) + print *, "" + + ! 测试4: 重置 + print *, "4. Testing reset..." + print *, "-------------------" + + call solution_reset(solution) + print *, "After reset:" + print *, " u max: ", maxval(abs(solution%u)) + print *, " un max: ", maxval(abs(solution%un)) + print *, " flux max: ", maxval(abs(solution%flux)) + print *, "" + + deallocate(initial_values) + + print *, "=== Test Summary ===" + print *, "✓ Ghost layer calculation works" + print *, "✓ Domain creation works" + print *, "✓ Solution arrays work" + print *, "✓ Initialization works" + print *, "✓ Field update works" + print *, "✓ Reset works" + print *, "" + print *, "Ready for next step: Implementing Physics modules" + +end program test_domain_solution \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/CMakeLists.txt new file mode 100644 index 000000000..b609e28d9 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/CMakeLists.txt @@ -0,0 +1,106 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +# 基础设施测试 +add_executable(test_infrastructure test_infrastructure.f90) +target_link_libraries(test_infrastructure + PRIVATE + infrastructure + core +) + +# 注册系统测试 +add_executable(test_registry test_registry.f90) +target_link_libraries(test_registry + PRIVATE + core + infrastructure +) + +# 物理模块测试 +add_executable(test_physics test_physics.f90) +target_link_libraries(test_physics + PRIVATE + physics + base +) + +# 组件管理器测试 +add_executable(test_component_manager test_component_manager.f90) +target_link_libraries(test_component_manager + PRIVATE + manager + infrastructure +) + +# 配置物理测试 +add_executable(test_config_physics test_config_physics.f90) +target_link_libraries(test_config_physics + PRIVATE + infrastructure + core +) + +# 求解器基础测试 +add_executable(test_solver_base test_solver_base.f90) +target_link_libraries(test_solver_base + PRIVATE + solver + infrastructure + core +) + +# 物理求解器测试 +add_executable(test_physics_solver test_physics_solver.f90) +target_link_libraries(test_physics_solver + PRIVATE + solver + infrastructure + core + physics + manager +) + +# 新增:简单物理求解器测试 +add_executable(test_physics_solver_simple test_physics_solver_simple.f90) +target_link_libraries(test_physics_solver_simple + PRIVATE + solver + infrastructure + core + physics + manager +) + +add_executable(test_simple_link test_simple_link.f90) +target_link_libraries(test_simple_link + PRIVATE + reconstructor + flux +) + + +add_executable(test_factory_simple test_factory_simple.f90) +target_link_libraries(test_factory_simple + PRIVATE + core + infrastructure + reconstructor + flux +) + +add_executable(test_domain_solution test_domain_solution.f90) +target_link_libraries(test_domain_solution + PRIVATE + infrastructure + core +) + +add_executable(test_component_manager_physics test_component_manager_physics.f90) +target_link_libraries(test_component_manager_physics + PRIVATE + manager + infrastructure + physics + core +) diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_architecture.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_architecture.f90 new file mode 100644 index 000000000..1c40d4677 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_architecture.f90 @@ -0,0 +1,117 @@ +! tests/test_architecture.f90 +program test_architecture + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module + use config_module + use mesh_module + use component_manager_module + + implicit none + + print *, "=== CFD架构测试 ===" + print *, "" + + ! 测试1: 基本系统 + call test_basic_systems() + + ! 测试2: 组件注册 + call test_registry_integration() + + ! 测试3: 配置验证 + call test_config_validation() + + ! 测试4: 完整流程 + call test_full_workflow() + + print *, "" + print *, "=== 架构测试完成 ===" + +contains + + subroutine test_basic_systems() + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "1. 测试基本系统..." + print *, "-------------------" + + ! 测试配置 + call config_print(config) + print *, "✓ 配置创建成功" + + ! 测试网格 + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "✓ 网格创建成功" + print *, "" + end subroutine test_basic_systems + + subroutine test_registry_integration() + print *, "2. 测试注册系统集成..." + print *, "------------------------" + + call initialize_registry(verbose=.true.) + + ! 注册核心组件 + print *, "注册核心组件:" + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + ! 显示注册内容 + call component_registry%list_simple() + print *, "注册表大小: ", registry_get_size() + + call cleanup_registry() + print *, "✓ 注册系统测试完成" + print *, "" + end subroutine test_registry_integration + + subroutine test_config_validation() + type(cfd_config) :: config + logical :: is_valid + + print *, "3. 测试配置验证..." + print *, "-------------------" + + ! 测试有效配置 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + print *, "测试配置:" + print *, " - 重构器: ", trim(config%recon_scheme), " 阶数: ", config%spatial_order + print *, " - 通量: ", trim(config%flux_type) + print *, " - 波速: ", config%wave_speed + + ! 这里调用验证函数(需要先实现) + ! is_valid = validate_config(config) + print *, "✓ 配置验证接口准备就绪" + print *, "" + end subroutine test_config_validation + + subroutine test_full_workflow() + print *, "4. 测试完整流程..." + print *, "-------------------" + + print *, "步骤1: 初始化系统 ✓" + print *, "步骤2: 创建网格 ✓" + print *, "步骤3: 设置配置 ✓" + print *, "步骤4: 创建组件 (待实现)" + print *, "步骤5: 初始化解 (待实现)" + print *, "步骤6: 时间推进 (待实现)" + print *, "步骤7: 输出结果 (待实现)" + print *, "" + + print *, "✓ 流程框架定义完成" + end subroutine test_full_workflow + +end program test_architecture \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_cfd_architecture.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_cfd_architecture.f90 new file mode 100644 index 000000000..1c40d4677 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_cfd_architecture.f90 @@ -0,0 +1,117 @@ +! tests/test_architecture.f90 +program test_architecture + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module + use config_module + use mesh_module + use component_manager_module + + implicit none + + print *, "=== CFD架构测试 ===" + print *, "" + + ! 测试1: 基本系统 + call test_basic_systems() + + ! 测试2: 组件注册 + call test_registry_integration() + + ! 测试3: 配置验证 + call test_config_validation() + + ! 测试4: 完整流程 + call test_full_workflow() + + print *, "" + print *, "=== 架构测试完成 ===" + +contains + + subroutine test_basic_systems() + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "1. 测试基本系统..." + print *, "-------------------" + + ! 测试配置 + call config_print(config) + print *, "✓ 配置创建成功" + + ! 测试网格 + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "✓ 网格创建成功" + print *, "" + end subroutine test_basic_systems + + subroutine test_registry_integration() + print *, "2. 测试注册系统集成..." + print *, "------------------------" + + call initialize_registry(verbose=.true.) + + ! 注册核心组件 + print *, "注册核心组件:" + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + ! 显示注册内容 + call component_registry%list_simple() + print *, "注册表大小: ", registry_get_size() + + call cleanup_registry() + print *, "✓ 注册系统测试完成" + print *, "" + end subroutine test_registry_integration + + subroutine test_config_validation() + type(cfd_config) :: config + logical :: is_valid + + print *, "3. 测试配置验证..." + print *, "-------------------" + + ! 测试有效配置 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + print *, "测试配置:" + print *, " - 重构器: ", trim(config%recon_scheme), " 阶数: ", config%spatial_order + print *, " - 通量: ", trim(config%flux_type) + print *, " - 波速: ", config%wave_speed + + ! 这里调用验证函数(需要先实现) + ! is_valid = validate_config(config) + print *, "✓ 配置验证接口准备就绪" + print *, "" + end subroutine test_config_validation + + subroutine test_full_workflow() + print *, "4. 测试完整流程..." + print *, "-------------------" + + print *, "步骤1: 初始化系统 ✓" + print *, "步骤2: 创建网格 ✓" + print *, "步骤3: 设置配置 ✓" + print *, "步骤4: 创建组件 (待实现)" + print *, "步骤5: 初始化解 (待实现)" + print *, "步骤6: 时间推进 (待实现)" + print *, "步骤7: 输出结果 (待实现)" + print *, "" + + print *, "✓ 流程框架定义完成" + end subroutine test_full_workflow + +end program test_architecture \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_component_manager.f90 new file mode 100644 index 000000000..f60c35056 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_component_manager.f90 @@ -0,0 +1,111 @@ +! tests/test_component_manager.f90 +program test_component_manager + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use component_manager_module, only: create_reconstructor, create_flux_calculator + use component_manager_module, only: component_manager_info, validate_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + implicit none + + type(cfd_config) :: config + class(reconstructor_base), allocatable :: recon + class(flux_calculator_base), allocatable :: flux + integer :: status + logical :: is_valid + + print *, "=== Component Manager Test ===" + print *, "" + + ! 显示组件管理器信息 + call component_manager_info() + print *, "" + + ! 测试1: 基本配置 + print *, "1. Testing basic ENO3 + Rusanov configuration..." + print *, "-----------------------------------------------" + + config%verbose = .true. + call config_print(config) + + ! 配置ENO3重构 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + call config_print(config) + print *, "" + + ! 验证配置 + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Configuration is valid" + else + print *, "[ERROR] Configuration is invalid" + end if + print *, "" + + ! 测试2: 创建组件 + print *, "2. Testing component creation..." + print *, "--------------------------------" + + ! 创建重构器(带状态检查) + recon = create_reconstructor(config, status) + if (status == 0) then + print *, "[OK] Reconstructor created successfully" + call recon%info() + else + print *, "[ERROR] Failed to create reconstructor, code:", status + end if + print *, "" + + ! 创建通量计算器 + flux = create_flux_calculator(config, status) + if (status == 0) then + print *, "[OK] Flux calculator created successfully" + call flux%info() + else + print *, "[ERROR] Failed to create flux calculator, code:", status + end if + print *, "" + + ! 测试3: WENO3重构测试 + print *, "3. Testing WENO3 configuration..." + print *, "---------------------------------" + + call config_with_reconstruction(config, "weno3", 3) + + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] WENO3 configuration is valid" + + ! 创建WENO3重构器 + recon = create_reconstructor(config) + call recon%info() + else + print *, "[ERROR] WENO3 configuration is invalid" + end if + print *, "" + + ! 测试4: 错误配置测试 + print *, "4. Testing invalid configuration..." + print *, "-----------------------------------" + + config%recon_scheme = "unknown_scheme" + config%flux_type = "unknown_flux" + + is_valid = validate_config(config) + if (.not. is_valid) then + print *, "[OK] Invalid configuration correctly rejected" + else + print *, "[ERROR] Invalid configuration should have been rejected" + end if + + ! 清理 + if (allocated(recon)) deallocate(recon) + if (allocated(flux)) deallocate(flux) + + print *, "" + print *, "=== Component manager test completed successfully ===" + +end program test_component_manager \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_component_manager_physics.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_component_manager_physics.f90 new file mode 100644 index 000000000..f2becca95 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_component_manager_physics.f90 @@ -0,0 +1,120 @@ +! tests/test_component_manager_physics.f90 (简化版) +program test_component_manager_physics + use base_modules, only: wp + use config_module, only: cfd_config, config_print, config_with_reconstruction + use component_manager_module, only: component_manager_info, validate_config + + implicit none + + type(cfd_config) :: config + logical :: is_valid + + print *, "=== Component Manager Physics Test (Simplified) ===" + print *, "" + + ! 测试1: 显示组件管理器信息 + print *, "1. Testing component manager info..." + print *, "-------------------------------------" + call component_manager_info() + print *, "" + + ! 测试2: 物理模块测试(默认) + print *, "2. Testing physics module with default configuration..." + print *, "------------------------------------------------------" + + config%verbose = .true. + call config_print(config) + + ! 验证配置 + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Default configuration is valid" + else + print *, "[ERROR] Default configuration is invalid" + end if + print *, "" + + ! 测试3: 测试物理配置 + print *, "3. Testing physics configuration..." + print *, "------------------------------------" + + ! 修改物理参数 + config%equation_type = "linear_advection" + config%problem_type = "linear_advection" + config%wave_speed = 2.5_wp + config%domain_length = 3.0_wp + + print *, "Modified physics configuration:" + print *, " Equation type: ", trim(config%equation_type) + print *, " Problem type: ", trim(config%problem_type) + print *, " Wave speed: ", config%wave_speed + print *, " Domain length: ", config%domain_length + print *, " Physics enabled: ", config%enable_physics + + ! 验证修改后的配置 + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Modified physics configuration is valid" + else + print *, "[ERROR] Modified physics configuration is invalid" + end if + print *, "" + + ! 测试4: 数值组件测试 + print *, "4. Testing numerical components with physics..." + print *, "-----------------------------------------------" + + call config_with_reconstruction(config, "weno3", 3) + config%flux_type = "rusanov" + + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Combined physics+numerics configuration is valid" + else + print *, "[ERROR] Combined configuration is invalid" + end if + print *, "" + + ! 测试5: 物理模块禁用测试 + print *, "5. Testing physics module disabled..." + print *, "---------------------------------------" + + config%enable_physics = .false. + config%verbose = .false. + + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Configuration valid even with physics disabled" + else + print *, "[ERROR] Configuration should be valid with physics disabled" + end if + print *, "" + + ! 测试6: 错误配置测试 + print *, "6. Testing error handling..." + print *, "-----------------------------" + + config%verbose = .true. + config%enable_physics = .true. + config%recon_scheme = "unknown_scheme" + config%flux_type = "unknown_flux" + config%equation_type = "unknown_equation" + config%problem_type = "unknown_problem" + + is_valid = validate_config(config) + if (.not. is_valid) then + print *, "[OK] Invalid configuration correctly rejected" + else + print *, "[ERROR] Invalid configuration should have been rejected" + end if + print *, "" + + print *, "=== Component Manager Physics Test Summary ===" + print *, "✓ Component manager info works" + print *, "✓ Configuration validation works with physics" + print *, "✓ Error handling works correctly" + print *, "✓ Combined physics+numerics validation works" + print *, "" + print *, "下一步: 集成物理模块到求解器框架" + +end program test_component_manager_physics \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_config_physics.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_config_physics.f90 new file mode 100644 index 000000000..c6fef5c0b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_config_physics.f90 @@ -0,0 +1,141 @@ +! tests/test_config_physics.f90 (修复版) +program test_config_physics + use base_modules, only: wp + use config_module, only: cfd_config, config_print, config_with_reconstruction + + implicit none + + type(cfd_config) :: config + + print *, "=== Configuration Physics Test (Simplified) ===" + print *, "" + + ! 测试1: 默认配置 + print *, "1. Testing default configuration..." + print *, "-----------------------------------" + call config_print(config) + print *, "" + + ! 测试2: 验证基础物理字段 + print *, "2. Testing basic physics fields..." + print *, "----------------------------------" + + print *, "Verifying default physics fields:" + + if (trim(config%equation_type) == "linear_advection") then + print *, " ✓ Default equation type: linear_advection" + else + print *, " ✗ Unexpected equation type: ", trim(config%equation_type) + end if + + if (trim(config%problem_type) == "linear_advection") then + print *, " ✓ Default problem type: linear_advection" + else + print *, " ✗ Unexpected problem type: ", trim(config%problem_type) + end if + + if (abs(config%domain_length - 2.0_wp) < 1e-10_wp) then + print *, " ✓ Default domain length: 2.0" + else + print *, " ✗ Unexpected domain length: ", config%domain_length + end if + + if (config%enable_physics) then + print *, " ✓ Physics enabled by default" + else + print *, " ✗ Physics not enabled by default" + end if + + print *, "" + + ! 测试3: 使用类型绑定的方法(正确的方法名) + print *, "3. Testing type-bound procedures..." + print *, "--------------------------------------" + + call config%set_physics_parameters( & + equation_type="burgers_equation", & + problem_type="sod_shock_tube", & + domain_length=3.0_wp, & + enable_physics=.false.) + + print *, "After set_physics_parameters:" + print *, " Equation type: ", trim(config%equation_type) + print *, " Problem type: ", trim(config%problem_type) + print *, " Domain length: ", config%domain_length + print *, " Physics enabled: ", config%enable_physics + + if (trim(config%equation_type) == "burgers_equation") then + print *, " ✓ Equation type modified successfully via set_physics_parameters" + end if + + if (trim(config%problem_type) == "sod_shock_tube") then + print *, " ✓ Problem type modified successfully via set_physics_parameters" + end if + + if (abs(config%domain_length - 3.0_wp) < 1e-10_wp) then + print *, " ✓ Domain length modified successfully via set_physics_parameters" + end if + + if (.not. config%enable_physics) then + print *, " ✓ Physics disabled successfully via set_physics_parameters" + end if + + print *, "" + + ! 测试4: 调用get_physics_info方法 + print *, "4. Testing get_physics_info method..." + print *, "--------------------------------------" + call config%get_physics_info() + print *, "" + + ! 测试5: 高斯脉冲配置 + print *, "5. Testing Gaussian pulse configuration..." + print *, "-----------------------------------------" + + config%ic_type = "gaussian" + config%pulse_center = 0.6_wp + config%pulse_width = 0.15_wp + + print *, "Gaussian pulse parameters:" + print *, " IC type: ", trim(config%ic_type) + print *, " Center: ", config%pulse_center + print *, " Width: ", config%pulse_width + + if (trim(config%ic_type) == "gaussian") then + print *, " ✓ Gaussian IC type set" + end if + + if (abs(config%pulse_center - 0.6_wp) < 1e-10_wp) then + print *, " ✓ Pulse center set" + end if + + if (abs(config%pulse_width - 0.15_wp) < 1e-10_wp) then + print *, " ✓ Pulse width set" + end if + + print *, "" + + ! 测试6: 重构配置 + print *, "6. Testing reconstruction configuration..." + print *, "------------------------------------------" + + call config_with_reconstruction(config, "weno", 5) + + print *, "Reconstruction configuration:" + print *, " Scheme: ", trim(config%recon_scheme) + print *, " Order: ", config%spatial_order + + if (trim(config%recon_scheme) == "weno" .and. config%spatial_order == 5) then + print *, " ✓ WENO5 configuration successful" + else + print *, " ✗ Reconstruction configuration failed" + end if + + print *, "" + + print *, "=== Configuration Physics Test Complete ===" + print *, "✓ Config module updated with physics support" + print *, "✓ Fields can be directly accessed and modified" + print *, "✓ Type-bound procedures work correctly" + +end program test_config_physics \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_domain_solution.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_domain_solution.f90 new file mode 100644 index 000000000..ff659bac2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_domain_solution.f90 @@ -0,0 +1,102 @@ +! tests/test_domain_solution.f90 +program test_domain_solution + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create, solution_reset + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + real(wp), allocatable :: initial_values(:) + integer :: i + + print *, "=== Domain and Solution Test ===" + print *, "" + + ! 测试1: 不同重构方案的ghost层计算 + print *, "1. Testing ghost layer calculation..." + print *, "--------------------------------------" + + ! ENO3 + call config_with_reconstruction(config, "eno", 3) + config%verbose = .false. + call mesh%init(ncells=10) + domain = domain_create(config, mesh) + print *, "ENO3: nghosts = ", domain%nghosts, " (expected: 3)" + + ! WENO3 + call config_with_reconstruction(config, "weno3", 3) + domain = domain_create(config, mesh) + print *, "WENO3: nghosts = ", domain%nghosts, " (expected: 2)" + + ! WENO5 + call config_with_reconstruction(config, "weno", 5) + domain = domain_create(config, mesh) + print *, "WENO5: nghosts = ", domain%nghosts, " (expected: 3)" + print *, "" + + ! 测试2: Solution数组 + print *, "2. Testing solution arrays..." + print *, "------------------------------" + + call config_with_reconstruction(config, "eno", 3) + config%verbose = .true. + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=10) + + domain = domain_create(config, mesh) + call domain%print_info() + print *, "" + + solution = solution_create(domain) + call solution%print_info() + print *, "" + + ! 测试3: 初始化和更新 + print *, "3. Testing initialization and update..." + print *, "----------------------------------------" + + allocate(initial_values(mesh%ncells)) + do i = 1, mesh%ncells + initial_values(i) = sin(2.0_wp * 3.14159265358979_wp * mesh%xcc(i) / mesh%L) + end do + + call solution%initialize(initial_values) + print *, "After initialization:" + print *, " u range: ", minval(solution%u), " to ", maxval(solution%u) + print *, " un range: ", minval(solution%un), " to ", maxval(solution%un) + + ! 修改当前解,测试更新 + solution%u = solution%u * 2.0_wp + call solution%update_old_field() + print *, "After update: max|u - un| = ", maxval(abs(solution%u - solution%un)) + print *, "" + + ! 测试4: 重置 + print *, "4. Testing reset..." + print *, "-------------------" + + call solution_reset(solution) + print *, "After reset:" + print *, " u max: ", maxval(abs(solution%u)) + print *, " un max: ", maxval(abs(solution%un)) + print *, " flux max: ", maxval(abs(solution%flux)) + print *, "" + + deallocate(initial_values) + + print *, "=== Test Summary ===" + print *, "✓ Ghost layer calculation works" + print *, "✓ Domain creation works" + print *, "✓ Solution arrays work" + print *, "✓ Initialization works" + print *, "✓ Field update works" + print *, "✓ Reset works" + print *, "" + print *, "Ready for next step: Implementing Physics modules" + +end program test_domain_solution \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_factory_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_factory_simple.f90 new file mode 100644 index 000000000..db65da7cf --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_factory_simple.f90 @@ -0,0 +1,58 @@ +! tests/test_factory_simple.f90 (修复版) +program test_factory_simple + use base_modules, only: wp ! ← 添加这行 + use config_module, only: cfd_config, config_print + use mesh_module, only: mesh_type + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(eno_reconstructor) :: eno + type(weno3_reconstructor) :: weno3 + type(rusanov_flux) :: rusanov + + print *, "=== Factory Pattern Simple Test ===" + print *, "" + + ! Test 1: Basic systems + print *, "1. Testing basic systems..." + print *, "-----------------------------" + call config_print(config) + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 2: Creating reconstructors + print *, "2. Testing reconstructors..." + print *, "------------------------------" + + ! 创建并测试ENO重构器 + print *, "Creating ENO reconstructor..." + eno = eno_reconstructor() ! 使用构造函数 + call eno%info() ! 必须调用info方法 + + print *, "" + print *, "Creating WENO3 reconstructor..." + weno3 = weno3_reconstructor() ! 使用构造函数 + call weno3%info() ! 必须调用info方法 + print *, "" + + ! Test 3: Creating flux calculator + print *, "3. Testing flux calculator..." + print *, "-------------------------------" + + print *, "Creating Rusanov flux calculator..." + rusanov = rusanov_flux() ! 使用构造函数 + call rusanov%info() ! 必须调用info方法 + print *, "" + + print *, "=== Factory pattern simple test completed successfully ===" + +end program test_factory_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_infrastructure.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_infrastructure.f90 new file mode 100644 index 000000000..22fa92d1a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_infrastructure.f90 @@ -0,0 +1,56 @@ +! tests/test_infrastructure.f90 (原test_basic_only.f90) +program test_infrastructure + use base_modules, only: wp + use config_module, only: cfd_config, config_print + use mesh_module, only: mesh_type + use registry_module, only: registry_init, registry_cleanup, & + register_component_simple, list_components + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "=== 基础设施测试 ===" + print *, "" + + ! 测试1: 配置 + print *, "1. 测试配置模块..." + print *, "-------------------" + call config_print(config) + print *, "" + + ! 测试2: 网格 + print *, "2. 测试网格模块..." + print *, "------------------" + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=5) + print *, "网格初始化:" + print *, " 单元数: ", mesh%ncells + print *, " 节点数: ", mesh%nnodes + print *, " 网格间距: ", mesh%dx + print *, "" + + ! 测试3: 注册系统 + print *, "3. 测试注册系统..." + print *, "------------------" + + call registry_init() + + ! 注册组件(使用简化版本) + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("flux", "rusanov") + + ! 列出组件 + call list_components() + print *, "" + + ! 清理 + call registry_cleanup() + + print *, "=== 基础设施测试通过 ===" + print *, "✓ 配置模块工作正常" + print *, "✓ 网格模块工作正常" + print *, "✓ 注册系统工作正常" + +end program test_infrastructure \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_physics.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_physics.f90 new file mode 100644 index 000000000..3dababb6a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_physics.f90 @@ -0,0 +1,91 @@ +! tests/test_physics.f90 (原test_physics_minimal.f90) +program test_physics + use base_modules, only: wp, ip + use linear_convection_equation, only: linear_convection_eq, create_linear_convection_eq + use linear_convection_problem, only: linear_convection_prob, create_linear_convection_prob + + implicit none + + type(linear_convection_eq) :: eq + type(linear_convection_prob) :: prob + real(wp) :: u, f, a + real(wp), allocatable :: x(:), u_ic(:), u_exact(:) + integer :: i, nx = 10 + + print *, "=== 物理模块基础测试 ===" + print *, "" + + ! 测试1: 方程功能 + print *, "1. 测试方程功能..." + print *, "-------------------" + + eq = create_linear_convection_eq(wave_speed=2.0_wp) + print *, "方程: ", eq%name + print *, "波速: ", eq%wave_speed + + u = 1.5_wp + f = eq%flux(u) + a = eq%speed() + + print *, "u = ", u + print *, "F(u) = ", f, " (期望: 3.0)" + print *, "波速 a = ", a, " (期望: 2.0)" + + if (abs(f - 3.0_wp) < 1e-10_wp .and. abs(a - 2.0_wp) < 1e-10_wp) then + print *, "✓ 方程功能正常" + else + print *, "✗ 方程功能异常" + end if + print *, "" + + ! 测试2: 问题功能 + print *, "2. 测试问题功能..." + print *, "-------------------" + + prob = create_linear_convection_prob(ic_type="step", domain_length=2.0_wp) + print *, "问题: ", prob%name + print *, "初始条件类型: ", trim(prob%ic_type) + print *, "域长度: ", prob%domain_length + + allocate(x(nx), u_ic(nx), u_exact(nx)) + do i = 1, nx + x(i) = 0.0_wp + (i-1) * 0.2_wp + end do + + ! 测试初始条件 + call prob%initial_condition(x, u_ic) + print *, "初始条件范围: ", minval(u_ic), " 到 ", maxval(u_ic) + + ! 测试精确解 + u_exact = prob%exact_solution(x, 0.0_wp) + print *, "t=0时精确解范围: ", minval(u_exact), " 到 ", maxval(u_exact) + + ! 检查阶跃函数 + if (abs(u_ic(1) - 1.0_wp) < 1e-10_wp .and. & + abs(u_ic(6) - 2.0_wp) < 1e-10_wp) then + print *, "✓ 阶跃初始条件正确" + else + print *, "✗ 阶跃初始条件错误" + end if + + ! 检查精确解与初始条件一致 + if (maxval(abs(u_ic - u_exact)) < 1e-10_wp) then + print *, "✓ t=0时精确解与初始条件一致" + else + print *, "✗ 精确解计算错误" + end if + print *, "" + + ! 测试3: 边界条件接口 + print *, "3. 测试边界条件接口..." + print *, "----------------------" + call prob%boundary_condition(u_ic, 0.0_wp) + print *, "✓ 边界条件接口正常" + print *, "" + + deallocate(x, u_ic, u_exact) + + print *, "=== 物理模块测试完成 ===" + print *, "下一步: 将物理模块集成到现有系统中" + +end program test_physics \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_physics_solver.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_physics_solver.f90 new file mode 100644 index 000000000..ba49ffbac --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_physics_solver.f90 @@ -0,0 +1,133 @@ +! tests/test_physics_solver.f90 (修复版) +program test_physics_solver + use base_modules, only: wp ! 使用一致的wp定义 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use mesh_module, only: mesh_type + use solver_base_module, only: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, & + SOLVER_COMPLETED, SOLVER_ERROR + use physics_solver_module, only: physics_solver + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(physics_solver) :: psolver + character(len=100) :: error_msg + integer :: state + + print *, "=== Physics Solver Test ===" + print *, "" + + ! 测试1: 创建物理求解器(默认物理配置) + print *, "1. Creating physics solver (default physics)..." + print *, "------------------------------------------------" + + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%dt = 0.01_wp + config%enable_physics = .true. + + call config_print(config) + + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=10) + + psolver = physics_solver(config, mesh) + call psolver%print_info() + print *, "" + + ! 测试2: 初始化 + print *, "2. Initializing physics solver..." + print *, "----------------------------------" + + call psolver%initialize() + state = psolver%get_state() + error_msg = psolver%get_error() + print *, "State after initialization: ", state + print *, "Expected: ", SOLVER_INITIALIZED + print *, "Match? ", state == SOLVER_INITIALIZED + print *, "Error message: '", trim(error_msg), "'" + print *, "" + + ! 测试3: 运行一小段时间 + print *, "3. Running physics solver (short time)..." + print *, "------------------------------------------" + + call psolver%run_to_time(0.02_wp) + state = psolver%get_state() + error_msg = psolver%get_error() + print *, "State after short run: ", state + print *, "Expected: ", SOLVER_COMPLETED + print *, "Match? ", state == SOLVER_COMPLETED + print *, "Current time: ", psolver%current_time + print *, "Current step: ", psolver%current_step + print *, "" + + ! 测试4: 禁用物理模块 + print *, "4. Testing physics solver with physics disabled..." + print *, "--------------------------------------------------" + + config%enable_physics = .false. + config%verbose = .false. + + psolver = physics_solver(config, mesh) + call psolver%initialize() + call psolver%run_to_time(0.01_wp) + + state = psolver%get_state() + error_msg = psolver%get_error() + print *, "State with physics disabled: ", state + print *, "Expected: ", SOLVER_COMPLETED + print *, "Match? ", state == SOLVER_COMPLETED + print *, "" + + ! 测试5: 不同物理配置 + print *, "5. Testing different physics configurations..." + print *, "----------------------------------------------" + + config%verbose = .true. + config%enable_physics = .true. + config%equation_type = "linear_advection" + config%problem_type = "linear_advection" + config%wave_speed = 2.5_wp + config%domain_length = 3.0_wp + config%ic_type = "gaussian" + + psolver = physics_solver(config, mesh) + call psolver%initialize() + + print *, "Physics configuration test completed" + print *, "" + + ! 测试6: 清理和错误处理 + print *, "6. Testing cleanup and error handling..." + print *, "----------------------------------------" + + call psolver%cleanup() + state = psolver%get_state() + error_msg = psolver%get_error() + print *, "State after cleanup: ", state + print *, "Expected: ", SOLVER_UNINITIALIZED + print *, "Match? ", state == SOLVER_UNINITIALIZED + + ! 尝试运行已清理的求解器 + call psolver%run_to_time(0.01_wp) + state = psolver%get_state() + error_msg = psolver%get_error() + print *, "State after error attempt: ", state + print *, "Expected: ", SOLVER_ERROR + print *, "Match? ", state == SOLVER_ERROR + print *, "Error message: '", trim(error_msg), "'" + print *, "" + + ! 最终信息 + print *, "=== Physics Solver Test Complete ===" + print *, "✓ Physics solver creation works" + print *, "✓ Physics component initialization works" + print *, "✓ Physics-enabled time stepping works" + print *, "✓ Physics disabled mode works" + print *, "✓ Different physics configurations work" + print *, "✓ Cleanup and error handling work" + print *, "" + print *, "下一步: 实现完整的数值方法(重构、通量、时间积分)" + +end program test_physics_solver \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_physics_solver_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_physics_solver_simple.f90 new file mode 100644 index 000000000..13312efde --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_physics_solver_simple.f90 @@ -0,0 +1,161 @@ +! tests/test_physics_solver_simple.f90 +program test_physics_solver_simple + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction + use mesh_module, only: mesh_type + use physics_solver_module, only: physics_solver, SOLVER_COMPLETED + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(physics_solver) :: solver + real(wp) :: final_time, final_step + integer :: state + + print *, "=========================================" + print *, " 简单物理求解器测试" + print *, "=========================================" + print *, "" + + ! 步骤1: 配置 + print *, "[步骤1] 配置求解器..." + print *, "---------------------" + + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%dt = 0.01_wp + config%final_time = 0.1_wp + config%wave_speed = 1.0_wp + config%ic_type = "step" + config%boundary_type = "periodic" + config%equation_type = "linear_advection" + config%problem_type = "linear_advection" + config%enable_physics = .true. + config%domain_length = 1.0_wp + + print *, "配置参数:" + print *, " 重构格式: ", trim(config%recon_scheme) + print *, " 时间步长: ", config%dt + print *, " 最终时间: ", config%final_time + print *, " 波速: ", config%wave_speed + print *, "" + + ! 步骤2: 创建网格 + print *, "[步骤2] 创建网格..." + print *, "-------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + + print *, "网格信息:" + print *, " 单元数: ", mesh%ncells + print *, " 节点数: ", mesh%nnodes + print *, " 网格间距: ", mesh%dx + print *, "" + + ! 步骤3: 创建求解器 + print *, "[步骤3] 创建求解器..." + print *, "---------------------" + + solver = physics_solver(config, mesh) + + print *, "求解器创建成功" + print *, " 初始状态: ", solver%get_state() + print *, "" + + ! 步骤4: 初始化 + print *, "[步骤4] 初始化求解器..." + print *, "-----------------------" + + call solver%initialize() + + state = solver%get_state() + print *, "初始化完成" + print *, " 状态: ", state + print *, " 当前时间: ", solver%current_time + print *, " 当前步数: ", solver%current_step + print *, "" + + ! 步骤5: 运行求解器 + print *, "[步骤5] 运行求解器..." + print *, "---------------------" + + call solver%run_to_time(config%final_time) + + state = solver%get_state() + print *, "运行完成" + print *, " 状态: ", state + print *, " 最终时间: ", solver%current_time + print *, " 总步数: ", solver%current_step + print *, "" + + ! 步骤6: 保存结果 + print *, "[步骤6] 保存结果..." + print *, "-------------------" + + final_time = solver%current_time + final_step = real(solver%current_step, wp) + state = solver%get_state() + + print *, "保存的结果:" + print *, " 状态: ", state + print *, " 时间: ", final_time + print *, " 步数: ", final_step + print *, "" + + ! 步骤7: 清理求解器 + print *, "[步骤7] 清理求解器..." + print *, "---------------------" + + call solver%cleanup() + + print *, "清理后状态:" + print *, " 状态: ", solver%get_state() + print *, " 时间: ", solver%current_time + print *, " 步数: ", solver%current_step + print *, "" + + ! 步骤8: 验证结果 + print *, "[步骤8] 验证结果..." + print *, "-------------------" + + print *, "验证标准:" + print *, " 1. 运行后状态应为 COMPLETED (", SOLVER_COMPLETED, ")" + print *, " 2. 最终时间应接近 ", config%final_time + print *, " 3. 步数应大于 0" + print *, "" + + if (state == SOLVER_COMPLETED) then + print *, "✓ 状态验证通过: COMPLETED" + else + print *, "✗ 状态验证失败: 期望 ", SOLVER_COMPLETED, ", 实际 ", state + end if + + if (abs(final_time - config%final_time) < 1e-5_wp) then + print *, "✓ 时间验证通过: ", final_time, " ≈ ", config%final_time + else + print *, "✗ 时间验证失败: ", final_time, " ≠ ", config%final_time + end if + + if (final_step > 0) then + print *, "✓ 步数验证通过: ", final_step, " > 0" + else + print *, "✗ 步数验证失败: ", final_step, " ≤ 0" + end if + + print *, "" + + ! 最终判断 + if (state == SOLVER_COMPLETED .and. & + abs(final_time - config%final_time) < 1e-5_wp .and. & + final_step > 0) then + print *, "=========================================" + print *, " 所有测试通过! ✓" + print *, "=========================================" + else + print *, "=========================================" + print *, " 测试失败 ✗" + print *, "=========================================" + end if + +end program test_physics_solver_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_registry.f90 new file mode 100644 index 000000000..e82651ffb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_registry.f90 @@ -0,0 +1,87 @@ +! tests/test_registry.f90 (原test_minimal_simple.f90) +program test_registry + use base_modules, only: wp + use registry_module + use config_module + use mesh_module + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== 注册系统功能测试 ===" + print *, "" + + ! 测试1: 配置系统 + print *, "1. 测试配置系统" + print *, "--------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! 测试2: 网格系统 + print *, "2. 测试网格系统" + print *, "--------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! 测试3: 注册系统 + print *, "3. 测试注册系统" + print *, "--------------" + + call registry_init() + + ! 注册组件 + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call list_components() + print *, "注册表大小: ", registry_get_size() + print *, "" + + ! 测试组件查找 + print *, "4. 测试组件查找" + print *, "--------------" + + if (has_component_simple("reconstructor", "eno")) then + print *, "找到: reconstructor.eno" + else + print *, "未找到: reconstructor.eno" + end if + + if (has_component_simple("reconstructor", "unknown")) then + print *, "找到: reconstructor.unknown" + else + print *, "未找到: reconstructor.unknown" + end if + print *, "" + + ! 测试获取可用组件 + print *, "5. 测试注册系统功能" + print *, "------------------" + print *, "注册表已初始化: ", registry_is_initialized() + print *, "组件数量: ", registry_get_size() + print *, "" + + ! 清理 + call registry_cleanup() + + print *, "=== 注册系统测试完成 ===" + +end program test_registry \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_simple_link.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_simple_link.f90 new file mode 100644 index 000000000..71cc614e8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_simple_link.f90 @@ -0,0 +1,78 @@ +! tests/test_simple_link.f90 +program test_simple_link + use base_modules, only: wp ! ← 添加这行 + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call registry_init() + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call list_components() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component_simple("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component_simple("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Cleanup + call registry_cleanup() + + print *, "=== Minimal test completed successfully ===" + +end program test_simple_link \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_solver_base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_solver_base.f90 new file mode 100644 index 000000000..6cfe47e41 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_solver_base.f90 @@ -0,0 +1,99 @@ +! tests/test_solver_base.f90 (修复版) +program test_solver_base + ! 所有 USE 语句必须在程序开始处 + use base_modules, only: wp + use config_module, only: cfd_config, config_print, config_with_reconstruction + use mesh_module, only: mesh_type + use solver_base_module, only: solver_base, SOLVER_UNINITIALIZED, & + SOLVER_INITIALIZED, SOLVER_COMPLETED, SOLVER_ERROR + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(solver_base) :: solver + integer :: state + + print *, "=== Solver Base Test ===" + print *, "" + + ! 测试1: 创建求解器 + print *, "1. Creating solver..." + print *, "----------------------" + + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%dt = 0.01_wp + + call config_print(config) + + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=10) + + solver = solver_base(config, mesh) + call solver%print_info() + print *, "" + + ! 测试2: 初始化 + print *, "2. Initializing solver..." + print *, "-------------------------" + + call solver%initialize() + state = solver%get_state() + print *, "State after initialization: ", state + print *, "Expected: ", SOLVER_INITIALIZED + print *, "Match? ", state == SOLVER_INITIALIZED + print *, "Error message: '", trim(solver%get_error()), "'" + print *, "" + + ! 测试3: 运行求解器 + print *, "3. Running solver..." + print *, "--------------------" + + call solver%run_to_time(0.05_wp) + state = solver%get_state() + print *, "State after run: ", state + print *, "Expected: ", SOLVER_COMPLETED + print *, "Match? ", state == SOLVER_COMPLETED + print *, "Current time: ", solver%current_time + print *, "Current step: ", solver%current_step + print *, "" + + ! 测试4: 再次运行(从已完成状态) + print *, "4. Running again from completed state..." + print *, "----------------------------------------" + + ! 需要先清理才能重新运行 + call solver%cleanup() + call solver%initialize() + call solver%run_to_time(0.1_wp) + + call solver%print_info() + print *, "" + + ! 测试5: 错误处理 + print *, "5. Testing error states..." + print *, "--------------------------" + + ! 创建一个未初始化的求解器 + call solver%cleanup() + state = solver%get_state() + print *, "Uninitialized state: ", state + print *, "Expected: ", SOLVER_UNINITIALIZED + print *, "Match? ", state == SOLVER_UNINITIALIZED + + ! 尝试运行未初始化的求解器 + call solver%run_to_time(0.01_wp) + state = solver%get_state() + print *, "State after error: ", state + print *, "Expected: ", SOLVER_ERROR + print *, "Match? ", state == SOLVER_ERROR + print *, "Error message: '", trim(solver%get_error()), "'" + print *, "" + + print *, "=== Solver Base Test Complete ===" + print *, "✓ Solver base class works" + print *, "✓ State management works" + print *, "✓ Time stepping framework works" + print *, "✓ Error handling works" + +end program test_solver_base \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_solver_framework.f90 b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_solver_framework.f90 new file mode 100644 index 000000000..6754323d0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03c/tests/test_solver_framework.f90 @@ -0,0 +1,91 @@ +! tests/test_solver_framework.f90 +program test_solver_framework + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use mesh_module, only: mesh_type + use solver_module, only: cfd_solver, solver_create, solver_run, solver_cleanup + use solver_module, only: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, & + SOLVER_COMPLETED, SOLVER_ERROR + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(cfd_solver) :: solver + + print *, "=== 求解器框架测试 ===" + print *, "" + + ! 测试1: 基本创建 + print *, "1. 测试求解器创建..." + print *, "----------------------" + + ! 创建配置 + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.0_real64 + config%dt = 0.01_real64 + + call config_print(config) + print *, "" + + ! 创建网格 + call mesh%init(xmin=0.0_real64, xmax=2.0_real64, ncells=20) + call mesh%print_info() + print *, "" + + ! 创建求解器 + solver = solver_create(config, mesh) + print *, "✓ 求解器创建成功" + print *, " 状态: ", solver%get_state() + print *, "" + + ! 测试2: 求解器初始化 + print *, "2. 测试求解器初始化..." + print *, "------------------------" + + call solver%initialize() + print *, "✓ 求解器初始化完成" + print *, " 状态: ", solver%get_state() + print *, " 错误信息: '", trim(solver%get_error()), "'" + print *, "" + + ! 测试3: 简单运行 + print *, "3. 测试求解器运行..." + print *, "----------------------" + + call solver_run(solver, 0.05_real64) ! 运行到0.05秒 + print *, "✓ 求解器运行完成" + print *, " 最终状态: ", solver%get_state() + print *, "" + + ! 测试4: 清理 + print *, "4. 测试求解器清理..." + print *, "----------------------" + + call solver_cleanup(solver) + print *, "✓ 求解器清理完成" + print *, " 状态: ", solver%get_state() + print *, "" + + ! 测试5: 错误处理 + print *, "5. 测试错误处理..." + print *, "-------------------" + + ! 尝试重复初始化 + call solver%initialize() + print *, " 重复初始化状态: ", solver%get_state() + print *, " 错误信息: '", trim(solver%get_error()), "'" + + call solver_cleanup(solver) + print *, "" + + print *, "=== 框架测试总结 ===" + print *, "✓ 求解器创建/初始化/运行/清理流程验证完成" + print *, "✓ 状态管理正常工作" + print *, "✓ 错误处理机制就绪" + print *, "" + print *, "下一步: 添加实际数值计算功能" + +end program test_solver_framework \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03d/CMakeLists.txt new file mode 100644 index 000000000..55859dc2a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) +add_subdirectory(examples) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/README.md b/example/1d-linear-convection/weno3/fortran/registry/03d/README.md new file mode 100644 index 000000000..c4889757b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/README.md @@ -0,0 +1,221 @@ +# Fortran CFD Project + +基于工厂模式和注册系统的CFD求解器框架。 + +## 项目结构 + +``` +fortran_cfd/ +├── CMakeLists.txt # 根CMake配置 +├── scripts/ # 构建脚本 +│ ├── build.py # Python构建脚本(推荐) +│ ├── build.bat # Batch包装脚本 +│ ├── build.ps1 # PowerShell包装脚本 +│ └── requirements.txt # Python依赖 +├── src/ # 源代码 +│ ├── CMakeLists.txt +│ ├── core/ # 核心模块(注册系统、工厂接口) +│ ├── infrastructure/ # 基础设施(配置、网格) +│ └── numerics/ # 数值方法 +├── tests/ # 测试 +│ ├── CMakeLists.txt +│ ├── test_minimal_simple.f90 # 简单测试 +│ └── test_factory.f90 # 工厂模式测试 +└── README.md # 项目说明(本文件) +``` + +## 快速开始 + +### 使用Python脚本(推荐) + +```bash +# 进入脚本目录 +cd scripts + +# 基本构建 +python build.py + +# 清理并构建 +python build.py --clean + +# 发布构建 +python build.py --build-type Release --clean + +# 并行构建(使用所有核心) +python build.py -j + +# 不运行测试 +python build.py --no-tests + +# 查看帮助 +python build.py --help +``` + +### 使用批处理脚本 + +```bash +cd scripts +.\build.bat +``` + +### 使用PowerShell脚本 + +```powershell +cd scripts +.\build.ps1 +``` + +## 手动构建 + +```bash +# 设置Intel环境 +cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && powershell' + +# 配置项目 +mkdir build +cd build +cmake -G "Visual Studio 17 2022" -A x64 -T fortran=ifx .. + +# 构建项目 +cmake --build . --config Debug + +# 运行测试 +Debug\test_simple.exe +Debug\test_factory.exe +``` + +## 功能特性 + +✅ 组件注册系统 +✅ 工厂模式 +✅ ENO/WENO重构器 +✅ Rusanov通量计算器 +✅ 可扩展架构 +✅ 自动化测试 + +## 依赖 + +- Intel oneAPI(包含ifx编译器) +- CMake 3.12+ +- Python 3.6+(仅用于构建脚本) + +## 源代码文件 + +### 核心模块 +- `src/core/registry.f90` - 组件注册系统 +- `src/core/factory_interfaces.f90` - 工厂接口 + +### 基础设施 +- `src/infrastructure/config.f90` - 配置管理 +- `src/infrastructure/mesh.f90` - 网格生成 + +### 数值方法 +- `src/numerics/reconstructor/base.f90` - 重构器基类 +- `src/numerics/reconstructor/eno.f90` - ENO重构器 +- `src/numerics/reconstructor/weno3.f90` - WENO-3重构器 +- `src/numerics/flux/base.f90` - 通量计算器基类 +- `src/numerics/flux/rusanov.f90` - Rusanov通量 + +### 测试 +- `tests/test_minimal_simple.f90` - 简单功能测试 +- `tests/test_factory.f90` - 工厂模式测试 + +## 构建脚本 + +### Python脚本 (build.py) +主构建脚本,支持: +- 自动设置Intel oneAPI环境 +- 参数化构建选项 +- 并行编译 +- 自动化测试 +- 彩色输出 + +### Batch脚本 (build.bat) +Windows批处理包装器,调用Python脚本。 + +### PowerShell脚本 (build.ps1) +PowerShell包装器,调用Python脚本。 + +## 示例输出 + +成功构建后,会看到类似输出: + +``` +============================================================ + Fortran CFD Project Builder +============================================================ + +[1/4] Setting up Intel Fortran compiler... +✓ Intel oneAPI environment configured + +[2/4] Preparing build directory... +✓ Cleaned build directory + +[3/4] Configuring project... + $ cmake -G Visual Studio 17 2022 -A x64 -T fortran=ifx -DCMAKE_BUILD_TYPE=Debug .. +✓ CMake configuration successful + +[4/4] Building project... + $ cmake --build . --config Debug +✓ Build completed in 12.3 seconds + +============================================================ + Running Tests +============================================================ + +[TEST] Simple functionality test... +✓ Simple test passed + +[TEST] Factory pattern test... +✓ Factory test passed + +============================================================ + Build Summary +============================================================ +✓ Build directory: D:\path\to\build +✓ Build type: Debug +✓ Total time: 15.2s +``` + +## 开发指南 + +### 添加新组件 + +1. 在相应模块中创建Fortran源文件 +2. 实现工厂函数 +3. 使用`register_component_with_factory`注册组件 +4. 添加测试 + +### 扩展架构 + +项目采用模块化设计: +1. **注册系统** - 管理所有可用的组件 +2. **工厂模式** - 动态创建组件实例 +3. **抽象接口** - 确保组件兼容性 +4. **配置驱动** - 通过配置文件控制行为 + +## 故障排除 + +### 常见问题 + +1. **Intel环境未找到** + - 检查Intel oneAPI安装路径 + - 手动运行`setvars.bat` + +2. **CMake配置失败** + - 确保使用正确的生成器:`Visual Studio 17 2022` + - 检查Fortran编译器是否可用 + +3. **构建失败** + - 检查依赖项是否完整 + - 查看详细的错误信息 + +### 调试建议 + +- 使用`--verbose`参数获取详细输出 +- 检查`build/CMakeCache.txt`了解配置详情 +- 查看编译器错误日志 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/examples/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03d/examples/CMakeLists.txt new file mode 100644 index 000000000..9207e0f47 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/examples/CMakeLists.txt @@ -0,0 +1,22 @@ +# examples/CMakeLists.txt +message(STATUS "配置示例程序...") + +# 主示例程序:ENO/WENO对比 +add_executable(example_eno_weno_comparison + run_eno_weno.f90 +) + +target_link_libraries(example_eno_weno_comparison + PRIVATE + solver + infrastructure + core + physics + manager +) + +install(TARGETS example_eno_weno_comparison + RUNTIME DESTINATION bin/examples +) + +message(STATUS "示例程序配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/examples/run_eno_weno.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/examples/run_eno_weno.f90 new file mode 100644 index 000000000..a9c14d6a0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/examples/run_eno_weno.f90 @@ -0,0 +1,282 @@ +! examples/run_eno_weno.f90 +program run_eno_weno + ! Example program: ENO/WENO comparison analysis + + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction, config_print + use mesh_module, only: mesh_type + use registry_module, only: registry_init, registry_cleanup, initialize_default_components + use physics_solver_module, only: physics_solver, SOLVER_COMPLETED + + implicit none + + type(cfd_config) :: config_eno3, config_weno3, config_weno5 + type(mesh_type) :: mesh + type(physics_solver) :: solver_eno3, solver_weno3, solver_weno5 + + ! Variables to save results + real(wp) :: time_eno3, time_weno3, time_weno5 + integer :: steps_eno3, steps_weno3, steps_weno5 + integer :: state_eno3, state_weno3, state_weno5 + logical :: all_success + + ! Debug: print start marker + print *, "==========================================" + print *, "START: ENO/WENO Comparison Analysis" + print *, "==========================================" + print *, "" + + ! Step 0: Initialize system + print *, "[STEP 0] Initializing system..." + print *, "--------------------------------" + + call registry_init(verbose=.true.) + print *, "Registry initialized" + + call initialize_default_components() + print *, "Default components registered" + print *, "" + + ! Step 1: Create mesh + print *, "[STEP 1] Creating computational mesh..." + print *, "----------------------------------------" + + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=40) + call mesh%print_info() + print *, "" + + ! ========== ENO3 Solver ========== + print *, "[STEP 2] Configuring and running ENO3 solver..." + print *, "-----------------------------------------------" + + ! Configure ENO3 + config_eno3%verbose = .true. + config_eno3%ic_type = "step" + config_eno3%wave_speed = 1.0_wp + config_eno3%final_time = 0.625_wp + config_eno3%dt = 0.0025_wp + config_eno3%rk_order = 2 + config_eno3%boundary_type = "periodic" + config_eno3%equation_type = "linear_advection" + config_eno3%problem_type = "linear_advection" + config_eno3%enable_physics = .true. + config_eno3%domain_length = 2.0_wp + + call config_with_reconstruction(config_eno3, "eno", 3) + + print *, "ENO3 configuration:" + call config_print(config_eno3) + print *, "" + + ! Create and run ENO3 solver + print *, "Creating ENO3 solver instance..." + solver_eno3 = physics_solver(config_eno3, mesh) + + print *, "Initializing ENO3 solver..." + call solver_eno3%initialize() + + print *, "Running ENO3 solver..." + call solver_eno3%run_to_time(config_eno3%final_time) + + ! Immediately save ENO3 results + time_eno3 = solver_eno3%current_time + steps_eno3 = solver_eno3%current_step + state_eno3 = solver_eno3%get_state() + + print *, "ENO3 solver completed" + print *, " Final time: ", time_eno3 + print *, " Total steps: ", steps_eno3 + print *, " State: ", state_eno3 + print *, "" + + ! ========== WENO3 Solver ========== + print *, "[STEP 3] Configuring and running WENO3 solver..." + print *, "------------------------------------------------" + + ! Configure WENO3 + config_weno3%verbose = .true. + config_weno3%ic_type = "step" + config_weno3%wave_speed = 1.0_wp + config_weno3%final_time = 0.625_wp + config_weno3%dt = 0.0025_wp + config_weno3%rk_order = 2 + config_weno3%boundary_type = "periodic" + config_weno3%equation_type = "linear_advection" + config_weno3%problem_type = "linear_advection" + config_weno3%enable_physics = .true. + config_weno3%domain_length = 2.0_wp + + call config_with_reconstruction(config_weno3, "weno3", 3) + + print *, "WENO3 configuration:" + call config_print(config_weno3) + print *, "" + + ! Create and run WENO3 solver + print *, "Creating WENO3 solver instance..." + solver_weno3 = physics_solver(config_weno3, mesh) + + print *, "Initializing WENO3 solver..." + call solver_weno3%initialize() + + print *, "Running WENO3 solver..." + call solver_weno3%run_to_time(config_weno3%final_time) + + ! Immediately save WENO3 results + time_weno3 = solver_weno3%current_time + steps_weno3 = solver_weno3%current_step + state_weno3 = solver_weno3%get_state() + + print *, "WENO3 solver completed" + print *, " Final time: ", time_weno3 + print *, " Total steps: ", steps_weno3 + print *, " State: ", state_weno3 + print *, "" + + ! ========== WENO5 Solver ========== + print *, "[STEP 4] Configuring and running WENO5 solver..." + print *, "------------------------------------------------" + + ! Configure WENO5 + config_weno5%verbose = .true. + config_weno5%ic_type = "step" + config_weno5%wave_speed = 1.0_wp + config_weno5%final_time = 0.625_wp + config_weno5%dt = 0.0025_wp + config_weno5%rk_order = 2 + config_weno5%boundary_type = "periodic" + config_weno5%equation_type = "linear_advection" + config_weno5%problem_type = "linear_advection" + config_weno5%enable_physics = .true. + config_weno5%domain_length = 2.0_wp + + call config_with_reconstruction(config_weno5, "weno", 5) + + print *, "WENO5 configuration:" + call config_print(config_weno5) + print *, "" + + ! Create and run WENO5 solver + print *, "Creating WENO5 solver instance..." + solver_weno5 = physics_solver(config_weno5, mesh) + + print *, "Initializing WENO5 solver..." + call solver_weno5%initialize() + + print *, "Running WENO5 solver..." + call solver_weno5%run_to_time(config_weno5%final_time) + + ! Immediately save WENO5 results + time_weno5 = solver_weno5%current_time + steps_weno5 = solver_weno5%current_step + state_weno5 = solver_weno5%get_state() + + print *, "WENO5 solver completed" + print *, " Final time: ", time_weno5 + print *, " Total steps: ", steps_weno5 + print *, " State: ", state_weno5 + print *, "" + + ! ========== Results Summary (BEFORE cleanup!) ========== + print *, "==========================================" + print *, " RESULTS SUMMARY" + print *, "==========================================" + print *, "" + + print *, "Solver Performance Comparison:" + print *, "------------------------------" + + print *, "ENO3:" + print *, " Final time: ", time_eno3 + print *, " Total steps: ", steps_eno3 + print *, " State: ", state_eno3 + print *, "" + + print *, "WENO3:" + print *, " Final time: ", time_weno3 + print *, " Total steps: ", steps_weno3 + print *, " State: ", state_weno3 + print *, "" + + print *, "WENO5:" + print *, " Final time: ", time_weno5 + print *, " Total steps: ", steps_weno5 + print *, " State: ", state_weno5 + print *, "" + + ! ========== Final Judgment ========== + print *, "==========================================" + print *, " FINAL JUDGMENT" + print *, "==========================================" + print *, "" + + all_success = (state_eno3 == SOLVER_COMPLETED) .and. & + (state_weno3 == SOLVER_COMPLETED) .and. & + (state_weno5 == SOLVER_COMPLETED) + + if (all_success) then + print *, "✓ ALL SOLVERS SUCCESSFULLY COMPLETED!" + print *, "" + print *, "Parameter Summary:" + print *, " Grid cells: ", mesh%ncells + print *, " Time step: ", config_eno3%dt + print *, " Final time: ", config_eno3%final_time + print *, " Wave speed: ", config_eno3%wave_speed + print *, " IC type: ", trim(config_eno3%ic_type) + print *, "" + print *, "Performance Comparison:" + print *, " ENO3: ", steps_eno3, " steps" + print *, " WENO3: ", steps_weno3, " steps" + print *, " WENO5: ", steps_weno5, " steps" + else + print *, "✗ SOME SOLVERS FAILED" + print *, "" + print *, "Failure Analysis:" + if (state_eno3 /= SOLVER_COMPLETED) then + print *, " • ENO3 failed with state: ", state_eno3 + end if + if (state_weno3 /= SOLVER_COMPLETED) then + print *, " • WENO3 failed with state: ", state_weno3 + end if + if (state_weno5 /= SOLVER_COMPLETED) then + print *, " • WENO5 failed with state: ", state_weno5 + end if + end if + + print *, "" + print *, "==========================================" + print *, " ANALYSIS COMPLETE" + print *, "==========================================" + + ! ========== Cleanup (AFTER printing results!) ========== + print *, "" + print *, "[STEP 5] Cleaning up system..." + print *, "--------------------------------" + + call solver_eno3%cleanup() + call solver_weno3%cleanup() + call solver_weno5%cleanup() + call registry_cleanup() + + print *, "All solvers cleaned up" + print *, "Registry cleaned up" + print *, "" + + ! ========== Wait for user input before exit ========== + print *, "==========================================" + print *, "Press ENTER to exit..." + print *, "==========================================" + + ! Wait for user input (uncomment if needed) + ! read(*,*) + + ! Alternative: add a small delay + print *, "Program will exit in 3 seconds..." + call sleep(3) + + print *, "" + print *, "==========================================" + print *, " PROGRAM END" + print *, "==========================================" + +end program run_eno_weno \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/scripts/build.bat b/example/1d-linear-convection/weno3/fortran/registry/03d/scripts/build.bat new file mode 100644 index 000000000..6fd6dc031 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/scripts/build.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Project Builder +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python build script with full Intel environment support... +echo. + +python build.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Build failed + pause + exit /b 1 +) + +echo. +echo [INFO] Build completed successfully! +echo. +echo [INFO] To run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/scripts/build.py b/example/1d-linear-convection/weno3/fortran/registry/03d/scripts/build.py new file mode 100644 index 000000000..3bf6d5375 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/scripts/build.py @@ -0,0 +1,629 @@ +#!/usr/bin/env python3 +""" +Fortran CFD Project Builder - 完整Python解决方案 +在Python内部处理Intel oneAPI环境配置 +""" + +import os +import sys +import subprocess +import shutil +import argparse +import time +import platform +import tempfile +from pathlib import Path + +class IntelEnvironment: + """Intel oneAPI环境管理器""" + + def __init__(self): + self.setvars_path = None + self.env_vars = {} + + def find_setvars(self): + """查找setvars.bat文件""" + possible_paths = [ + r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat", + r"C:\Program Files\Intel\oneAPI\setvars.bat", + r"C:\Program Files (x86)\Intel\oneAPI\compiler\latest\env\vars.bat", + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\setvars.bat"), + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\compiler\latest\env\vars.bat"), + ] + + for path in possible_paths: + if os.path.exists(path): + self.setvars_path = path + return True + + return False + + def setup_environment(self): + """设置Intel环境""" + if not self.find_setvars(): + return False + + try: + # 创建临时的批处理文件来捕获环境变量 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + f.write(f'@echo off\n') + f.write(f'call "{self.setvars_path}" >nul 2>&1\n') + f.write(f'set\n') # 输出所有环境变量 + temp_bat = f.name + + # 运行批处理文件并捕获输出 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + shell=True + ) + + # 解析环境变量 + for line in result.stdout.split('\n'): + line = line.strip() + if '=' in line: + key, value = line.split('=', 1) + self.env_vars[key.strip()] = value.strip() + + # 清理临时文件 + os.unlink(temp_bat) + + # 更新当前进程的环境变量 + os.environ.update(self.env_vars) + + return True + + except Exception as e: + print(f"设置Intel环境失败: {e}") + return False + + def get_compiler_info(self): + """获取编译器信息""" + info = {} + + # 检查ifx编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + env={**os.environ, **self.env_vars} if self.env_vars else os.environ + ) + + if result.returncode == 0: + for line in result.stdout.split('\n'): + if 'Version' in line or '版本' in line: + info['ifx_version'] = line.strip() + break + except: + pass + + # 检查环境变量 + info['ifx_root'] = self.env_vars.get('IFX_ROOT', '') + info['compiler_root'] = self.env_vars.get('ONEAPI_ROOT', '') + + return info + +class BuildSystem: + """构建系统主类""" + + def __init__(self): + self.project_root = Path(__file__).parent.parent + self.build_dir = self.project_root / "build" + self.intel_env = IntelEnvironment() + + # 设置控制台编码 + if sys.platform == "win32": + try: + import ctypes + # 设置控制台输出为UTF-8 + ctypes.windll.kernel32.SetConsoleOutputCP(65001) + except: + pass + + def print_header(self, text): + """打印标题""" + print(f"\n{'='*70}") + print(f" {text}") + print(f"{'='*70}\n") + + def print_step(self, step, total, message): + """打印步骤""" + print(f"[{step}/{total}] {message}...") + + def print_success(self, message): + """打印成功""" + print(f"\033[92m✓ {message}\033[0m") + + def print_error(self, message): + """打印错误""" + print(f"\033[91m✗ {message}\033[0m") + + def print_warning(self, message): + """打印警告""" + print(f"\033[93m! {message}\033[0m") + + def print_info(self, message): + """打印信息""" + print(f"\033[94mℹ {message}\033[0m") + + def check_prerequisites(self): + """检查前提条件""" + self.print_step(1, 6, "检查前提条件") + + # 检查Python版本 + python_version = sys.version.split()[0] + self.print_info(f"Python版本: {python_version}") + + # 检查平台 + self.print_info(f"平台: {platform.system()} {platform.release()}") + self.print_info(f"处理器核心数: {os.cpu_count()}") + + # 检查CMake + try: + result = subprocess.run( + ["cmake", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + version_line = result.stdout.split('\n')[0] + self.print_success(f"CMake: {version_line}") + else: + self.print_error("CMake未找到") + return False + except FileNotFoundError: + self.print_error("CMake未安装") + return False + + return True + + def setup_intel_environment(self, args): + """设置Intel环境""" + self.print_step(2, 6, "配置Intel oneAPI环境") + + if not self.intel_env.find_setvars(): + self.print_warning("未找到Intel oneAPI setvars.bat") + self.print_info("将尝试使用系统环境中的编译器") + + # 检查是否能直接访问编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + self.print_success("Intel编译器在系统PATH中找到") + return True + else: + self.print_warning("Intel编译器未在PATH中找到") + except: + self.print_warning("无法访问Intel编译器") + + return True # 继续,让CMake自己找编译器 + + # 设置环境 + if self.intel_env.setup_environment(): + compiler_info = self.intel_env.get_compiler_info() + + if compiler_info.get('ifx_version'): + self.print_success(f"Intel Fortran编译器: {compiler_info['ifx_version']}") + elif compiler_info.get('ifx_root'): + self.print_success(f"Intel编译器路径: {compiler_info['ifx_root']}") + else: + self.print_success("Intel oneAPI环境配置完成") + + return True + else: + self.print_warning("Intel环境配置失败,将继续使用系统环境") + return True + + def clean_build_directory(self, args): + """清理构建目录""" + if args.clean and self.build_dir.exists(): + self.print_info("清理构建目录...") + try: + shutil.rmtree(self.build_dir) + self.print_success("构建目录已清理") + except Exception as e: + self.print_error(f"清理失败: {e}") + if not args.force: + return False + return True + + def run_command(self, cmd, cwd=None, check=True, env=None): + """运行命令""" + if isinstance(cmd, list): + cmd_str = ' '.join(str(c) for c in cmd if c) + else: + cmd_str = str(cmd) + + print(f" \033[96m$\033[0m {cmd_str}") + + try: + # 合并环境变量 + exec_env = os.environ.copy() + if env: + exec_env.update(env) + if self.intel_env.env_vars: + exec_env.update(self.intel_env.env_vars) + + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=False, + env=exec_env + ) + + # 处理输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or '完成' in line or '生成' in line: + print(f" \033[92m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + if check and result.returncode != 0: + self.print_error(f"命令执行失败,退出码: {result.returncode}") + return False + + return True + + except Exception as e: + self.print_error(f"命令执行异常: {e}") + return False + + def configure_cmake(self, args): + """配置CMake""" + self.print_step(3, 6, "配置CMake项目") + + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + f"-DCMAKE_BUILD_TYPE={args.build_type}", + ] + + if args.compiler == "ifx": + cmake_cmd.extend(["-T", "fortran=ifx"]) + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + success = self.run_command(cmake_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("CMake配置完成") + else: + self.print_error("CMake配置失败") + + return success + + def build_project(self, args): + """构建项目""" + self.print_step(4, 6, "构建项目") + + build_cmd = [ + "cmake", + "--build", ".", + "--config", args.build_type, + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + success = self.run_command(build_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("项目构建完成") + else: + self.print_error("构建失败") + + return success + + def run_tests_with_environment(self, test_exe): + """运行单个测试,确保有Intel环境""" + try: + # 创建临时的批处理文件来运行测试 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'"{test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'"{test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + return result + + except Exception as e: + print(f"运行测试失败: {e}") + return None + + def run_tests(self, args): + """运行测试""" + self.print_step(5, 6, "运行测试") + + # 查找测试可执行文件 + test_dir = self.build_dir / "bin" / args.build_type + if not test_dir.exists(): + test_dir = self.build_dir / "bin" + if not test_dir.exists(): + test_dir = self.build_dir + + test_files = list(test_dir.glob("test_*.exe")) + + if not test_files: + self.print_warning("未找到测试程序") + return True + + all_passed = True + + for test_exe in sorted(test_files): + test_name = test_exe.stem + self.print_info(f"运行测试: {test_name}") + print(f" {'-'*50}") + + # 运行测试 + result = self.run_tests_with_environment(str(test_exe)) + + if result is None: + self.print_error(f" {test_name} 运行失败") + all_passed = False + continue + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + print(f" {line}") + + if result.returncode == 0: + self.print_success(f" {test_name} 通过") + else: + self.print_error(f" {test_name} 失败 (退出码: {result.returncode})") + all_passed = False + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + print() # 空行 + + return all_passed + + def create_test_runner(self, args): + """创建独立的测试运行器""" + self.print_step(6, 6, "创建测试运行器") + + runner_path = self.build_dir / "run_tests.bat" + + content = f'''@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Test Runner +echo ======================================== +echo. + +REM Setup Intel oneAPI environment +set "SETVARS_PATH={self.intel_env.setvars_path or ''}" +if exist "%SETVARS_PATH%" ( + call "%SETVARS_PATH%" >nul + echo [INFO] Intel environment configured +) else ( + echo [WARNING] Intel environment not found + echo [WARNING] Tests may fail without runtime libraries +) + +echo. + +REM Run all test executables +set "TEST_COUNT=0" +set "PASS_COUNT=0" + +for %%f in ("bin\\{args.build_type}\\test_*.exe") do ( + set /a TEST_COUNT+=1 + echo [TEST %%f] + echo {'-'*50} + + %%f + if errorlevel 1 ( + echo [FAILED] %%f + ) else ( + echo [PASSED] %%f + set /a PASS_COUNT+=1 + ) + echo. +) + +echo ======================================== +echo Tests: %PASS_COUNT%/%TEST_COUNT% passed +if %PASS_COUNT% equ %TEST_COUNT% ( + echo [SUCCESS] All tests passed! +) else ( + echo [FAILURE] Some tests failed +) +echo ======================================== + +pause +''' + + with open(runner_path, 'w', encoding='utf-8') as f: + f.write(content) + + self.print_success(f"测试运行器已创建: {runner_path}") + self.print_info(f"使用方法: cd build && run_tests.bat") + + return runner_path + + def generate_report(self, args, build_time, tests_passed): + """生成构建报告""" + self.print_header("构建完成") + + print(f"项目: {self.project_root.name}") + print(f"构建类型: {args.build_type}") + print(f"编译器: {args.compiler}") + print(f"并行作业: {args.jobs}") + print(f"总耗时: {build_time:.1f}秒") + print(f"测试结果: {'全部通过' if tests_passed else '有失败'}") + + # 显示生成的可执行文件 + bin_dir = self.build_dir / "bin" / args.build_type + if bin_dir.exists(): + print(f"\n生成的可执行文件:") + for exe in sorted(bin_dir.glob("*.exe")): + size_mb = exe.stat().st_size / (1024 * 1024) + print(f" • {exe.name} ({size_mb:.2f} MB)") + + # 显示测试运行器信息 + runner_path = self.build_dir / "run_tests.bat" + if runner_path.exists(): + print(f"\n独立测试运行器:") + print(f" • {runner_path.name}") + print(f" 在Intel oneAPI环境中运行所有测试") + + def run(self): + """运行构建系统""" + parser = argparse.ArgumentParser( + description="Fortran CFD项目构建工具", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认构建 + %(prog)s --clean # 清理后构建 + %(prog)s --build-type Release # Release构建 + %(prog)s --no-tests # 只构建,不运行测试 + %(prog)s -j8 --verbose # 8线程并行构建,详细输出 + """ + ) + + parser.add_argument("--build-type", choices=["Debug", "Release"], + default="Debug", help="构建类型") + parser.add_argument("--compiler", choices=["ifx", "ifort"], + default="ifx", help="Fortran编译器") + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-tests", action="store_true", + help="跳过测试") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + + args = parser.parse_args() + + # 开始构建 + start_time = time.time() + + self.print_header("Fortran CFD 项目构建系统") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 检查前提条件 + if not self.check_prerequisites(): + if not args.force: + return 1 + + # 2. 设置Intel环境 + if not self.setup_intel_environment(args): + if not args.force: + return 1 + + # 3. 清理目录 + if not self.clean_build_directory(args): + if not args.force: + return 1 + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 4. 配置CMake + if not self.configure_cmake(args): + if not args.force: + return 1 + + # 5. 构建项目 + if not self.build_project(args): + if not args.force: + return 1 + + # 6. 运行测试和创建测试运行器 + tests_passed = True + if not args.no_tests: + tests_passed = self.run_tests(args) + + # 创建测试运行器 + self.create_test_runner(args) # 传递 args 参数 + + # 7. 生成报告 + build_time = time.time() - start_time + self.generate_report(args, build_time, tests_passed) + + return 0 if tests_passed else 1 + + except KeyboardInterrupt: + self.print_error("\n构建被用户中断") + return 1 + except Exception as e: + self.print_error(f"构建过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + builder = BuildSystem() + return builder.run() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/scripts/requirements.txt b/example/1d-linear-convection/weno3/fortran/registry/03d/scripts/requirements.txt new file mode 100644 index 000000000..d21a12b45 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/scripts/requirements.txt @@ -0,0 +1,2 @@ +# 构建脚本的Python依赖(可选) +# 目前没有特殊依赖,保持空文件或添加未来可能需要的包 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/scripts/run_all_steps.bat b/example/1d-linear-convection/weno3/fortran/registry/03d/scripts/run_all_steps.bat new file mode 100644 index 000000000..d506149b2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/scripts/run_all_steps.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo CFD Project: All Steps +echo ======================================== +echo. + +echo [INFO] Starting Step 1: Physics Modules Test... +call run_step1.bat + +if errorlevel 1 ( + echo [ERROR] Step 1 failed + pause + exit /b 1 +) + +echo. +echo [INFO] Starting Step 2: Configuration Physics Update... +call run_step2.bat + +if errorlevel 1 ( + echo [ERROR] Step 2 failed + pause + exit /b 1 +) + +echo. +echo ======================================== +echo All Steps Completed Successfully! +echo ======================================== +echo. +echo [INFO] Next: Update component manager for physics support +echo [INFO] Run: run_step3.bat (to be created) +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/scripts/run_example.py b/example/1d-linear-convection/weno3/fortran/registry/03d/scripts/run_example.py new file mode 100644 index 000000000..d7c199178 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/scripts/run_example.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# scripts/run_example.py +""" +运行ENO/WENO示例程序 +""" + +import os +import sys +import subprocess +from pathlib import Path + +# 添加当前目录到路径 +sys.path.insert(0, str(Path(__file__).parent)) + +try: + from build import BuildSystem +except ImportError: + print("错误: 找不到build.py,请确保在scripts目录中运行") + sys.exit(1) + +def run_example(): + """运行示例程序""" + builder = BuildSystem() + + print("\n" + "="*70) + print(" 运行ENO/WENO对比示例程序") + print("="*70 + "\n") + + # 检查是否已构建 + exe_path = builder.build_dir / "bin" / "Debug" / "example_eno_weno_comparison.exe" + + if not exe_path.exists(): + print("示例程序未构建,先构建项目...") + print("-"*50) + + # 使用简化的构建 + result = subprocess.run( + ["python", "build.py", "--no-tests", "--clean"], + cwd=builder.project_root / "scripts", + capture_output=True, + text=True + ) + + if result.returncode != 0: + print("构建失败:") + print(result.stderr) + return False + + # 运行示例程序 + print("运行示例程序...") + print("-"*50) + + try: + result = subprocess.run( + [str(exe_path)], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace' + ) + + print(result.stdout) + + if result.stderr: + print("标准错误输出:") + print(result.stderr) + + return result.returncode == 0 + + except Exception as e: + print(f"运行示例程序失败: {e}") + return False + +def main(): + """主函数""" + success = run_example() + + if success: + print("\n" + "="*70) + print(" ✓ 示例程序运行成功") + print("="*70) + return 0 + else: + print("\n" + "="*70) + print(" ✗ 示例程序运行失败") + print("="*70) + return 1 + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/scripts/run_step1.bat b/example/1d-linear-convection/weno3/fortran/registry/03d/scripts/run_step1.bat new file mode 100644 index 000000000..0b6b1f17e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/scripts/run_step1.bat @@ -0,0 +1,39 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Step 1: Physics Modules Test +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python step1 script with full Intel environment support... +echo. + +python run_step1.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Step 1 failed + pause + exit /b 1 +) + +echo. +echo [INFO] Step 1 completed successfully! +echo. +echo [INFO] Next step: Update config to include physics settings +echo [INFO] Run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/scripts/run_step1.py b/example/1d-linear-convection/weno3/fortran/registry/03d/scripts/run_step1.py new file mode 100644 index 000000000..5e087a690 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/scripts/run_step1.py @@ -0,0 +1,319 @@ +#!/usr/bin/env python3 +""" +Step 1: Physics Modules Test +扩展build.py,专门用于测试物理模块 +""" + +import os +import sys +import subprocess +import time +from pathlib import Path + +# 添加当前目录到路径,以便导入build.py的类 +sys.path.insert(0, str(Path(__file__).parent)) + +try: + from build import IntelEnvironment, BuildSystem +except ImportError: + print("Error: Cannot import build.py. Make sure build.py is in the same directory.") + sys.exit(1) + +class Step1System(BuildSystem): + """Step 1 测试系统,继承自BuildSystem""" + + def __init__(self): + super().__init__() + self.test_name = "test_physics_minimal" + self.test_exe = None + + def find_test_executable(self): + """查找测试可执行文件""" + possible_paths = [ + self.build_dir / "bin" / "Debug" / f"{self.test_name}.exe", + self.build_dir / "Debug" / f"{self.test_name}.exe", + self.build_dir / f"{self.test_name}.exe", + self.build_dir / "bin" / f"{self.test_name}.exe", + ] + + for path in possible_paths: + if path.exists(): + self.test_exe = path + self.print_success(f"Found test executable: {path}") + return True + + # 如果没有找到,尝试搜索 + self.print_warning(f"Could not find {self.test_name}.exe") + self.print_info("Searching for test executables...") + + try: + result = subprocess.run( + ["dir", str(self.build_dir), "/s", "/b", "*.exe"], + capture_output=True, + text=True, + encoding='utf-8', + shell=True + ) + + if result.returncode == 0: + test_files = [line.strip() for line in result.stdout.split('\n') + if line and 'test_' in line.lower()] + + if test_files: + self.print_info("Found test files:") + for test_file in test_files: + self.print_info(f" {test_file}") + return False + except: + pass + + return False + + def run_test_with_intel_env(self): + """在Intel环境下运行测试""" + if not self.test_exe: + self.print_error("No test executable found") + return False + + self.print_step(1, 2, f"Running test: {self.test_exe.name}") + + try: + # 创建临时的批处理文件来运行测试(包含Intel环境) + import tempfile + + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + # 设置Intel环境 + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'echo [INFO] Intel environment configured\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'echo [WARNING] Intel environment not found\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + self.print_info(f"Command: {temp_bat}") + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower() or 'fail' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or 'pass' in line.lower() or '✓' in line: + print(f" \033[92m{line}\033[0m") + elif '=' in line or '---' in line: + print(f" \033[96m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + self.print_warning("Test stderr output:") + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + self.print_step(2, 2, "Test execution completed") + + if result.returncode == 0: + self.print_success("Test passed") + return True + else: + self.print_error(f"Test failed (exit code: {result.returncode})") + return False + + except Exception as e: + self.print_error(f"Failed to run test: {e}") + return False + + def build_project_if_needed(self, args): + """如果需要,构建项目""" + if args.no_build: + self.print_info("Skipping build (--no-build flag)") + return True + + self.print_step(1, 3, "Building project") + + # 调用父类的构建方法 + build_args = argparse.Namespace() + build_args.clean = args.clean + build_args.build_type = "Debug" + build_args.compiler = "ifx" + build_args.no_tests = True # 不运行所有测试 + build_args.jobs = os.cpu_count() + build_args.verbose = args.verbose + build_args.force = args.force + + # 清理构建目录 + if args.clean and self.build_dir.exists(): + self.print_info("Cleaning build directory...") + import shutil + try: + shutil.rmtree(self.build_dir) + self.print_success("Build directory cleaned") + except Exception as e: + self.print_error(f"Clean failed: {e}") + if not args.force: + return False + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 配置CMake + self.print_step(2, 3, "Configuring CMake") + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + "-DCMAKE_BUILD_TYPE=Debug", + "-T", "fortran=ifx", + ] + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + if not self.run_command(cmake_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + # 构建项目 + self.print_step(3, 3, "Building project") + build_cmd = [ + "cmake", + "--build", ".", + "--config", "Debug", + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + if not self.run_command(build_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + self.print_success("Build completed") + return True + + def run(self): + """运行Step 1测试""" + parser = argparse.ArgumentParser( + description="Step 1: Physics Modules Test", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认运行 + %(prog)s --clean # 清理后构建并测试 + %(prog)s --no-build # 只运行测试,不重新构建 + %(prog)s --verbose # 详细输出 + %(prog)s -j4 # 使用4个并行作业构建 + """ + ) + + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-build", action="store_true", + help="不重新构建,直接运行测试") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + + args = parser.parse_args() + + # 开始测试 + start_time = time.time() + + self.print_header("Step 1: Physics Modules Implementation Test") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 设置Intel环境 + self.print_step(1, 4, "Setting up Intel oneAPI environment") + if not self.setup_intel_environment(args): + if not args.force: + self.print_error("Intel environment setup failed") + return 1 + self.print_warning("Intel environment setup failed, continuing...") + + # 2. 构建项目(如果需要) + self.print_step(2, 4, "Building project if needed") + if not self.build_project_if_needed(args): + if not args.force: + return 1 + + # 3. 查找测试可执行文件 + self.print_step(3, 4, "Finding test executable") + if not self.find_test_executable(): + if not args.force: + return 1 + self.print_warning("Test executable not found, but continuing due to --force") + return 0 + + # 4. 运行测试 + self.print_step(4, 4, "Running physics module test") + test_passed = self.run_test_with_intel_env() + + # 生成报告 + test_time = time.time() - start_time + self.print_header("Step 1 Complete") + + print(f"测试: {'通过 ✓' if test_passed else '失败 ✗'}") + print(f"测试程序: {self.test_exe.name if self.test_exe else '未找到'}") + print(f"总耗时: {test_time:.1f}秒") + + if test_passed: + print(f"\n下一步: 更新配置以包含物理设置") + print(f"建议: 修改config.f90,添加physics相关字段") + return 0 + else: + if args.force: + self.print_warning("测试失败,但由于--force标志继续执行") + return 0 + return 1 + + except KeyboardInterrupt: + self.print_error("\n测试被用户中断") + return 1 + except Exception as e: + self.print_error(f"测试过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + system = Step1System() + return system.run() + +if __name__ == "__main__": + # 需要导入argparse + import argparse + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/scripts/run_step2.bat b/example/1d-linear-convection/weno3/fortran/registry/03d/scripts/run_step2.bat new file mode 100644 index 000000000..9c1f62de4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/scripts/run_step2.bat @@ -0,0 +1,39 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Step 2: Configuration Physics Update +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python step2 script with full Intel environment support... +echo. + +python run_step2.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Step 2 failed + pause + exit /b 1 +) + +echo. +echo [INFO] Step 2 completed successfully! +echo. +echo [INFO] Next step: Update component manager to support physics +echo [INFO] Run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/scripts/run_step2.py b/example/1d-linear-convection/weno3/fortran/registry/03d/scripts/run_step2.py new file mode 100644 index 000000000..c16b76088 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/scripts/run_step2.py @@ -0,0 +1,284 @@ +#!/usr/bin/env python3 +""" +Step 2: Configuration Physics Update +测试配置模块的物理功能更新 +""" + +import os +import sys +import subprocess +import time +from pathlib import Path + +# 添加当前目录到路径,以便导入build.py的类 +sys.path.insert(0, str(Path(__file__).parent)) + +try: + from build import IntelEnvironment, BuildSystem +except ImportError: + print("Error: Cannot import build.py. Make sure build.py is in the same directory.") + sys.exit(1) + +class Step2System(BuildSystem): + """Step 2 测试系统,继承自BuildSystem""" + + def __init__(self): + super().__init__() + self.test_name = "test_config_physics" + self.test_exe = None + + def find_test_executable(self): + """查找测试可执行文件""" + possible_paths = [ + self.build_dir / "bin" / "Debug" / f"{self.test_name}.exe", + self.build_dir / "Debug" / f"{self.test_name}.exe", + self.build_dir / f"{self.test_name}.exe", + self.build_dir / "bin" / f"{self.test_name}.exe", + ] + + for path in possible_paths: + if path.exists(): + self.test_exe = path + self.print_success(f"Found test executable: {path}") + return True + + return False + + def run_test_with_intel_env(self): + """在Intel环境下运行测试""" + if not self.test_exe: + self.print_error("No test executable found") + return False + + self.print_step(1, 2, f"Running test: {self.test_exe.name}") + + try: + # 创建临时的批处理文件来运行测试(包含Intel环境) + import tempfile + + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + # 设置Intel环境 + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'echo [INFO] Intel environment configured\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'echo [WARNING] Intel environment not found\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + self.print_info(f"Command: {temp_bat}") + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower() or 'fail' in line.lower() or '✗' in line: + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or 'pass' in line.lower() or '✓' in line: + print(f" \033[92m{line}\033[0m") + elif '=' in line or '---' in line or '===' in line: + print(f" \033[96m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + self.print_warning("Test stderr output:") + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + self.print_step(2, 2, "Test execution completed") + + if result.returncode == 0: + self.print_success("Test passed") + return True + else: + self.print_error(f"Test failed (exit code: {result.returncode})") + return False + + except Exception as e: + self.print_error(f"Failed to run test: {e}") + return False + + def build_project(self, args): + """构建项目""" + if args.no_build: + self.print_info("Skipping build (--no-build flag)") + return True + + self.print_step(1, 3, "Building project") + + # 清理构建目录 + if args.clean and self.build_dir.exists(): + self.print_info("Cleaning build directory...") + import shutil + try: + shutil.rmtree(self.build_dir) + self.print_success("Build directory cleaned") + except Exception as e: + self.print_error(f"Clean failed: {e}") + if not args.force: + return False + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 配置CMake + self.print_step(2, 3, "Configuring CMake") + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + "-DCMAKE_BUILD_TYPE=Debug", + "-T", "fortran=ifx", + ] + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + if not self.run_command(cmake_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + # 构建项目 + self.print_step(3, 3, "Building project") + build_cmd = [ + "cmake", + "--build", ".", + "--config", "Debug", + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + if not self.run_command(build_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + self.print_success("Build completed") + return True + + def run(self): + """运行Step 2测试""" + import argparse + + parser = argparse.ArgumentParser( + description="Step 2: Configuration Physics Update", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认运行 + %(prog)s --clean # 清理后构建并测试 + %(prog)s --no-build # 只运行测试,不重新构建 + %(prog)s --verbose # 详细输出 + """ + ) + + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-build", action="store_true", + help="不重新构建,直接运行测试") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + + args = parser.parse_args() + + # 开始测试 + start_time = time.time() + + self.print_header("Step 2: Configuration Physics Update") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 设置Intel环境 + self.print_step(1, 4, "Setting up Intel oneAPI environment") + if not self.setup_intel_environment(args): + if not args.force: + self.print_error("Intel environment setup failed") + return 1 + self.print_warning("Intel environment setup failed, continuing...") + + # 2. 构建项目(如果需要) + self.print_step(2, 4, "Building project if needed") + if not self.build_project(args): + if not args.force: + return 1 + + # 3. 查找测试可执行文件 + self.print_step(3, 4, "Finding test executable") + if not self.find_test_executable(): + self.print_error(f"Test executable {self.test_name}.exe not found") + if not args.force: + return 1 + self.print_warning("Test executable not found, but continuing due to --force") + return 0 + + # 4. 运行测试 + self.print_step(4, 4, "Running configuration physics test") + test_passed = self.run_test_with_intel_env() + + # 生成报告 + test_time = time.time() - start_time + self.print_header("Step 2 Complete") + + print(f"测试: {'通过 ✓' if test_passed else '失败 ✗'}") + print(f"测试程序: {self.test_exe.name if self.test_exe else '未找到'}") + print(f"总耗时: {test_time:.1f}秒") + + if test_passed: + print(f"\n下一步: 更新组件管理器以支持物理模块") + print(f"建议: 修改component_manager.f90,添加physics组件创建") + return 0 + else: + if args.force: + self.print_warning("测试失败,但由于--flag继续执行") + return 0 + return 1 + + except KeyboardInterrupt: + self.print_error("\n测试被用户中断") + return 1 + except Exception as e: + self.print_error(f"测试过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + system = Step2System() + return system.run() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03d/src/CMakeLists.txt new file mode 100644 index 000000000..2aaee245a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/CMakeLists.txt @@ -0,0 +1,18 @@ +# ==================== src\CMakeLists.txt ==================== + +# src/CMakeLists.txt +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(base) +add_subdirectory(core) +add_subdirectory(infrastructure) +add_subdirectory(numerics) +add_subdirectory(physics) # ← 新增物理模块目录 +add_subdirectory(manager) +add_subdirectory(solver) + +message(STATUS "源代码目录配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/base/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03d/src/base/CMakeLists.txt new file mode 100644 index 000000000..74f4aa65f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/base/CMakeLists.txt @@ -0,0 +1,16 @@ +# src/base/CMakeLists.txt +message(STATUS "Configuring base module...") + +add_library(base STATIC + modules.f90 + precision.f90 # 新增 +) + +set_target_properties(base PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Base module configured") + +# src/core/CMakeLists.txt +message(STATUS "Configuring core module...") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/base/modules.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/src/base/modules.f90 new file mode 100644 index 000000000..43aaee241 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/base/modules.f90 @@ -0,0 +1,36 @@ +! src/base/modules.f90 +module base_modules + use, intrinsic :: iso_fortran_env, only: real64, int32 + implicit none + + public :: wp, ip, max_name_len, string_len, cfd_config_base, component_info + + integer, parameter :: wp = real64 + integer, parameter :: ip = int32 + integer, parameter :: string_len = 100 + integer, parameter :: max_name_len = 32 + + ! 基础配置类型 + type :: cfd_config_base + character(len=max_name_len) :: ic_type = "step" + character(len=max_name_len) :: recon_scheme = "eno" + character(len=max_name_len) :: flux_type = "rusanov" + integer(ip) :: rk_order = 1 + real(wp) :: wave_speed = 1.0_wp + real(wp) :: final_time = 0.625_wp + real(wp) :: dt = 0.025_wp + character(len=max_name_len) :: boundary_type = "periodic" + integer(ip) :: spatial_order = 2 + character(len=max_name_len) :: equation_type = "linear_advection" + character(len=max_name_len) :: problem_type = "linear_advection" + logical :: verbose = .true. + end type cfd_config_base + + ! 组件信息类型 + type :: component_info + character(len=max_name_len) :: category = "" + character(len=max_name_len) :: name = "" + integer(ip) :: order = 0 + end type component_info + +end module base_modules \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/base/precision.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/src/base/precision.f90 new file mode 100644 index 000000000..4ac5fd7ef --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/base/precision.f90 @@ -0,0 +1,9 @@ +! src/base/precision.f90(简单版本) +module precision_module + use base_modules, only: wp, ip + implicit none + + ! 重新导出,确保兼容 + public :: wp, ip + +end module precision_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03d/src/core/CMakeLists.txt new file mode 100644 index 000000000..d8b8df064 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/core/CMakeLists.txt @@ -0,0 +1,14 @@ +# src/core/CMakeLists.txt +message(STATUS "Configuring core module...") + +add_library(core STATIC + registry.f90 +) + +target_link_libraries(core PRIVATE base) + +set_target_properties(core PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Core module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/core/factory_base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/src/core/factory_base.f90 new file mode 100644 index 000000000..302418a15 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/core/factory_base.f90 @@ -0,0 +1,57 @@ +! src/core/factory_base.f90 +module factory_base_module + use base_modules, only: wp, ip + use registry_module, only: create_component, has_component + + implicit none + private + public :: wp, ip, factory_base, factory_create + + ! 工厂基类 + type :: factory_base + character(len=max_name_length) :: category = "" + contains + procedure :: create => factory_base_create + procedure :: get_available => factory_base_get_available + end type factory_base + + ! 便捷函数类型 + abstract interface + function factory_function_interface(category, name) result(instance) + import :: wp + character(len=*), intent(in) :: category, name + class(*), allocatable :: instance + end function factory_function_interface + end interface + +contains + + ! 创建工厂实例 + function factory_create(category) result(factory) + character(len=*), intent(in) :: category + type(factory_base) :: factory + factory%category = trim(category) + end function factory_create + + ! 工厂创建方法 + function factory_base_create(this, name) result(instance) + class(factory_base), intent(in) :: this + character(len=*), intent(in) :: name + class(*), allocatable :: instance + + instance = create_component(this%category, name) + end function factory_base_create + + ! 获取可用组件列表(简化版) + subroutine factory_base_get_available(this, names, count) + class(factory_base), intent(in) :: this + character(len=*), allocatable, intent(out) :: names(:) + integer(ip), intent(out) :: count + + ! 这里需要实现从注册表获取列表的逻辑 + ! 暂时返回空列表 + count = 0 + allocate(character(len=max_name_length) :: names(0)) + end subroutine factory_base_get_available + +end module factory_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..a960167c3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/core/factory_interfaces.f90 @@ -0,0 +1,17 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, factory_procedure + + ! Factory procedure interface + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/src/core/registry.f90 new file mode 100644 index 000000000..d155aa19b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/core/registry.f90 @@ -0,0 +1,257 @@ +! src/core/registry.f90 (更新版) +module registry_module + use base_modules, only: wp, ip, max_name_len, component_info + + implicit none + private + + ! 明确公开所有需要的接口 + public :: wp, ip ! 类型参数 + public :: component_info ! 类型 + public :: registry_init, registry_cleanup ! 初始化/清理 + public :: register_component_simple ! 注册组件 + public :: has_component_simple ! 检查组件 + public :: list_components ! 列出组件 + public :: registry_is_initialized ! 检查初始化状态 + public :: registry_get_size ! 获取大小 + public :: initialize_default_components ! 新增:初始化默认组件 + + ! 全局注册表 + type :: component_registry + type(component_info), allocatable :: components(:) + integer(ip) :: count = 0 + integer(ip) :: capacity = 100 + logical :: initialized = .false. + logical :: verbose = .true. + logical :: default_components_added = .false. ! 新增:标记是否已添加默认组件 + end type component_registry + + type(component_registry) :: registry + +contains + + ! ==================== 公共API ==================== + + subroutine registry_init(verbose) + logical, optional, intent(in) :: verbose + + if (registry%initialized) then + if (registry%verbose) then + print *, "[REGISTRY] Already initialized" + end if + return + end if + + if (present(verbose)) then + registry%verbose = verbose + end if + + allocate(registry%components(registry%capacity)) + registry%initialized = .true. + + if (registry%verbose) then + print *, "[REGISTRY] Initialized with capacity:", registry%capacity + end if + end subroutine registry_init + + subroutine registry_cleanup() + if (allocated(registry%components)) then + deallocate(registry%components) + end if + registry%initialized = .false. + registry%count = 0 + registry%default_components_added = .false. ! 重置标记 + + if (registry%verbose) then + print *, "[REGISTRY] Cleaned up" + end if + end subroutine registry_cleanup + + ! 新增:初始化默认组件 + subroutine initialize_default_components() + if (.not. registry%initialized) then + call registry_init() + end if + + if (registry%default_components_added) then + if (registry%verbose) then + print *, "[REGISTRY] Default components already added" + end if + return + end if + + ! 注册重构器 + call register_component_simple("reconstructor", "eno", order=3) + call register_component_simple("reconstructor", "weno3", order=3) + call register_component_simple("reconstructor", "weno5", order=5) + + ! 注册通量计算器 + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + + ! 注册边界条件 + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + + ! 注册时间积分器 + call register_component_simple("integrator", "rk1", order=1) + call register_component_simple("integrator", "rk2", order=2) + call register_component_simple("integrator", "rk3", order=3) + + ! 注册方程 + call register_component_simple("equation", "linear_advection") + + ! 注册问题 + call register_component_simple("problem", "linear_advection") + + ! 注册初始条件 + call register_component_simple("initial_condition", "step") + call register_component_simple("initial_condition", "sin") + call register_component_simple("initial_condition", "gaussian") + + registry%default_components_added = .true. + + if (registry%verbose) then + print *, "[REGISTRY] Default components registered" + print *, "[REGISTRY] Total components:", registry%count + end if + end subroutine initialize_default_components + + subroutine register_component_simple(category, name, order) + character(len=*), intent(in) :: category, name + integer(ip), optional, intent(in) :: order + + integer(ip) :: i + type(component_info) :: info + + if (.not. registry%initialized) then + call registry_init() + end if + + ! 检查是否已存在 + do i = 1, registry%count + if (trim(registry%components(i)%category) == trim(category) .and. & + trim(registry%components(i)%name) == trim(name)) then + if (registry%verbose) then + print *, "[WARN] Overwriting component: ", trim(category), ".", trim(name) + end if + + ! 更新 + if (present(order)) then + registry%components(i)%order = order + else + registry%components(i)%order = 0 + end if + return + end if + end do + + ! 扩展数组 + if (registry%count >= registry%capacity) then + call expand_registry() + end if + + ! 添加新组件 + registry%count = registry%count + 1 + + info%category = trim(category) + info%name = trim(name) + info%order = 0 + if (present(order)) then + info%order = order + end if + + registry%components(registry%count) = info + + if (registry%verbose) then + print *, "[OK] Registered simple: ", trim(category), ".", trim(name) + end if + end subroutine register_component_simple + + logical function has_component_simple(category, name) + character(len=*), intent(in) :: category, name + + integer(ip) :: i + + has_component_simple = .false. + + if (.not. registry%initialized) return + + do i = 1, registry%count + if (trim(registry%components(i)%category) == trim(category) .and. & + trim(registry%components(i)%name) == trim(name)) then + has_component_simple = .true. + return + end if + end do + end function has_component_simple + + subroutine list_components(category) + character(len=*), optional, intent(in) :: category + + integer(ip) :: i, count + + if (.not. registry%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + if (registry%count == 0) then + print *, "[INFO] No components registered" + return + end if + + count = 0 + print *, "=== Registry Contents ===" + do i = 1, registry%count + if (.not. present(category) .or. & + trim(registry%components(i)%category) == trim(category)) then + call print_component_info(registry%components(i)) + count = count + 1 + end if + end do + + print *, "Total:", count, "components" + print *, "==========================" + end subroutine list_components + + ! ==================== 新增函数 ==================== + + logical function registry_is_initialized() + ! 检查注册表是否已初始化 + registry_is_initialized = registry%initialized + end function registry_is_initialized + + integer(ip) function registry_get_size() + ! 获取注册表中的组件数量 + registry_get_size = registry%count + end function registry_get_size + + ! ==================== 内部辅助函数 ==================== + + subroutine expand_registry() + type(component_info), allocatable :: temp(:) + + registry%capacity = registry%capacity * 2 + allocate(temp(registry%capacity)) + temp(1:registry%count) = registry%components(1:registry%count) + call move_alloc(temp, registry%components) + + if (registry%verbose) then + print *, "[INFO] Registry expanded to capacity:", registry%capacity + end if + end subroutine expand_registry + + subroutine print_component_info(info) + type(component_info), intent(in) :: info + + if (info%order > 0) then + print *, " [", trim(info%category), ".", trim(info%name), & + " (order:", info%order, ")]" + else + print *, " [", trim(info%category), ".", trim(info%name), "]" + end if + end subroutine print_component_info + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/core/registry_initializer.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/src/core/registry_initializer.f90 new file mode 100644 index 000000000..44023d1dd --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/core/registry_initializer.f90 @@ -0,0 +1,39 @@ +! src/core/registry_initializer.f90 (新增文件) +module registry_initializer_module + use registry_module, only: register_component_simple + implicit none + private + public :: initialize_default_registry + +contains + + subroutine initialize_default_registry() + ! 注册重构器 + call register_component_simple("reconstructor", "eno", order=3) + call register_component_simple("reconstructor", "weno3", order=3) + call register_component_simple("reconstructor", "weno5", order=5) + + ! 注册通量计算器 + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + + ! 注册边界条件 + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + + ! 注册时间积分器 + call register_component_simple("integrator", "rk1", order=1) + call register_component_simple("integrator", "rk2", order=2) + call register_component_simple("integrator", "rk3", order=3) + + ! 注册方程 + call register_component_simple("equation", "linear_advection") + + ! 注册问题 + call register_component_simple("problem", "linear_advection") + + print *, "[REGISTRY] Default components registered" + end subroutine initialize_default_registry + +end module registry_initializer_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03d/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..70cbbd2f3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/infrastructure/CMakeLists.txt @@ -0,0 +1,17 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "Configuring infrastructure module...") + +add_library(infrastructure STATIC + config.f90 + mesh.f90 + domain.f90 # 新增 + solution.f90 # 新增 +) + +target_link_libraries(infrastructure PRIVATE base) + +set_target_properties(infrastructure PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Infrastructure module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/src/infrastructure/config.f90 new file mode 100644 index 000000000..7586a1a50 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/infrastructure/config.f90 @@ -0,0 +1,144 @@ +! src/infrastructure/config.f90 (修复版) +module config_module + use base_modules, only: wp, ip, max_name_len, cfd_config_base + + implicit none + public :: wp, ip, cfd_config, config_print, config_with_reconstruction + + ! 扩展配置类型 - 添加物理相关字段 + type, extends(cfd_config_base) :: cfd_config + ! 物理参数 + real(wp) :: left_boundary_value = 1.0_wp + real(wp) :: right_boundary_value = 2.0_wp + real(wp) :: domain_length = 2.0_wp + + ! 新增:物理模块相关配置 + real(wp) :: pulse_center = 0.5_wp ! 高斯脉冲中心 + real(wp) :: pulse_width = 0.1_wp ! 高斯脉冲宽度 + logical :: enable_physics = .true. ! 是否启用物理模块 + contains + ! 新增:物理相关配置方法 + procedure :: set_physics_parameters + procedure :: get_physics_info + end type cfd_config + +contains + + subroutine config_print(cfg) + type(cfd_config), intent(in) :: cfg + + print *, "=== CFD Configuration ===" + print *, "Initial condition: ", trim(cfg%ic_type) + print *, "Reconstruction: ", trim(cfg%recon_scheme), " (order:", cfg%spatial_order, ")" + print *, "Flux type: ", trim(cfg%flux_type) + print *, "Time integration: RK", cfg%rk_order + print *, "Wave speed: ", cfg%wave_speed + print *, "Final time: ", cfg%final_time + print *, "Time step: ", cfg%dt + print *, "Boundary: ", trim(cfg%boundary_type) + + ! 新增:物理配置信息 + print *, "--- Physics Configuration ---" + print *, "Equation type: ", trim(cfg%equation_type) + print *, "Problem type: ", trim(cfg%problem_type) + print *, "Domain length: ", cfg%domain_length + print *, "Physics enabled: ", cfg%enable_physics + + if (cfg%ic_type == "gaussian") then + print *, "Pulse center: ", cfg%pulse_center + print *, "Pulse width: ", cfg%pulse_width + end if + + print *, "===============================" + end subroutine config_print + + subroutine config_with_reconstruction(cfg, scheme, order) + type(cfd_config), intent(inout) :: cfg + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + integer :: i + + ! 转换为小写 + cfg%recon_scheme = scheme + do i = 1, len_trim(cfg%recon_scheme) + if (cfg%recon_scheme(i:i) >= 'A' .and. cfg%recon_scheme(i:i) <= 'Z') then + cfg%recon_scheme(i:i) = char(ichar(cfg%recon_scheme(i:i)) + 32) + end if + end do + + ! 设置阶数 + if (present(order)) then + cfg%spatial_order = order + else + if (index(cfg%recon_scheme, 'weno') > 0) then + cfg%spatial_order = 5 + else if (trim(cfg%recon_scheme) == 'eno') then + cfg%spatial_order = 3 + end if + end if + + if (cfg%verbose) then + print *, "[CONFIG] Reconstruction: ", trim(cfg%recon_scheme), & + " Order: ", cfg%spatial_order + end if + end subroutine config_with_reconstruction + + ! ========== 新增:物理参数设置方法 ========== + + subroutine set_physics_parameters(this, equation_type, problem_type, & + domain_length, enable_physics) + class(cfd_config), intent(inout) :: this + character(len=*), intent(in), optional :: equation_type, problem_type + real(wp), intent(in), optional :: domain_length + logical, intent(in), optional :: enable_physics + + if (present(equation_type)) then + this%equation_type = trim(equation_type) + if (this%verbose) then + print *, "[CONFIG] Set equation type: ", trim(this%equation_type) + end if + end if + + if (present(problem_type)) then + this%problem_type = trim(problem_type) + if (this%verbose) then + print *, "[CONFIG] Set problem type: ", trim(this%problem_type) + end if + end if + + if (present(domain_length)) then + this%domain_length = domain_length + if (this%verbose) then + print *, "[CONFIG] Set domain length: ", this%domain_length + end if + end if + + if (present(enable_physics)) then + this%enable_physics = enable_physics + if (this%verbose) then + print *, "[CONFIG] Physics module enabled: ", this%enable_physics + end if + end if + end subroutine set_physics_parameters + + subroutine get_physics_info(this) + class(cfd_config), intent(in) :: this + + print *, "=== Physics Configuration Info ===" + print *, "Equation type: ", trim(this%equation_type) + print *, "Problem type: ", trim(this%problem_type) + print *, "Domain length: ", this%domain_length + print *, "Wave speed: ", this%wave_speed + print *, "Physics enabled: ", this%enable_physics + + if (this%ic_type == "gaussian") then + print *, "Pulse parameters:" + print *, " Center: ", this%pulse_center + print *, " Width: ", this%pulse_width + end if + + print *, "==================================" + end subroutine get_physics_info + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/infrastructure/domain.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/src/infrastructure/domain.f90 new file mode 100644 index 000000000..c3662f039 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/infrastructure/domain.f90 @@ -0,0 +1,102 @@ +! src/infrastructure/domain.f90 +module domain_module + use base_modules, only: wp, ip, max_name_len + use config_module, only: cfd_config + use mesh_module, only: mesh_type + + implicit none + private + public :: wp, ip, domain_type, domain_create, is_physical_cell + + type :: domain_type + type(cfd_config), pointer :: config => null() + type(mesh_type), pointer :: mesh => null() + integer(ip) :: nghosts = 0 + integer(ip) :: ist = 1 ! 物理区域起始索引(1-based) + integer(ip) :: ied = 1 ! 物理区域结束索引(exclusive) + integer(ip) :: ntcells = 0 ! 总单元数(含ghost) + contains + procedure :: print_info => domain_print_info + procedure :: get_physical_indices => domain_get_physical_indices + end type domain_type + +contains + + function domain_create(config, mesh) result(domain) + type(cfd_config), target, intent(in) :: config + type(mesh_type), target, intent(in) :: mesh + type(domain_type) :: domain + + domain%config => config + domain%mesh => mesh + + ! 计算ghost层数(参考Julia的_calc_nghosts) + domain%nghosts = calc_nghosts(config) + domain%ist = domain%nghosts + 1 + domain%ied = domain%ist + mesh%ncells + domain%ntcells = mesh%ncells + 2 * domain%nghosts + + if (config%verbose) then + print *, "[DOMAIN] Created:" + print *, " Ghost layers: ", domain%nghosts + print *, " Physical cells: ", domain%ist, " to ", domain%ied - 1 + print *, " Total cells: ", domain%ntcells + end if + end function domain_create + + function calc_nghosts(config) result(nghosts) + type(cfd_config), intent(in) :: config + integer(ip) :: nghosts + + character(len=max_name_len) :: scheme + + scheme = config%recon_scheme + + if (scheme == "eno") then + nghosts = config%spatial_order + else if (index(scheme, "weno") > 0) then + nghosts = config%spatial_order / 2 + 1 + else + print *, "[WARNING] Unknown scheme, using default nghosts=2" + nghosts = 2 + end if + + if (nghosts <= 0) then + print *, "[ERROR] Invalid nghosts: ", nghosts + nghosts = 2 + end if + end function calc_nghosts + + logical function is_physical_cell(this, idx) + class(domain_type), intent(in) :: this + integer(ip), intent(in) :: idx + is_physical_cell = (idx >= this%ist .and. idx < this%ied) + end function is_physical_cell + + function domain_get_physical_indices(this) result(indices) + class(domain_type), intent(in) :: this + integer(ip), allocatable :: indices(:) + integer(ip) :: i, count + + count = this%ied - this%ist + allocate(indices(count)) + + do i = 1, count + indices(i) = this%ist + i - 1 + end do + end function domain_get_physical_indices + + subroutine domain_print_info(this) + class(domain_type), intent(in) :: this + + print *, "=== Domain Information ===" + print *, "Configuration: ", trim(this%config%recon_scheme), & + " order ", this%config%spatial_order + print *, "Ghost layers: ", this%nghosts + print *, "Physical cells: ", this%ist, " to ", this%ied - 1 + print *, "Total cells: ", this%ntcells + print *, "Mesh cells: ", this%mesh%ncells + print *, "==========================" + end subroutine domain_print_info + +end module domain_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..f810f3a1b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/infrastructure/mesh.f90 @@ -0,0 +1,73 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use base_modules, only: wp, ip + + implicit none + public :: wp, ip, mesh_type, mesh_init, mesh_print_info + + ! 网格类型 + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer(ip) :: ncells = 40 + integer(ip) :: nnodes + integer(ip) :: nx + real(wp), allocatable :: x(:) ! 节点坐标 + real(wp), allocatable :: xcc(:) ! 单元中心坐标 + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer(ip), optional, intent(in) :: ncells + + integer(ip) :: i + + ! 设置参数 + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! 计算 + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! 分配内存 + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! 生成节点坐标 + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! 生成单元中心坐标 + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== Mesh Information ===" + print *, "Domain: [", this%xmin, ", ", this%xmax, "]" + print *, "Cells: ", this%ncells + print *, "Nodes: ", this%nnodes + print *, "dx: ", this%dx + print *, "L: ", this%L + print *, "========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/infrastructure/solution.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/src/infrastructure/solution.f90 new file mode 100644 index 000000000..ce88fd8a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/infrastructure/solution.f90 @@ -0,0 +1,131 @@ +! src/infrastructure/solution.f90 +module solution_module + use base_modules, only: wp, ip + use domain_module, only: domain_type + + implicit none + private + public :: wp, ip, solution_type, solution_create, solution_reset + + type :: solution_type + type(domain_type), pointer :: domain => null() + real(wp), allocatable :: u(:) ! 当前解(含ghost) + real(wp), allocatable :: un(:) ! 旧解 + real(wp), allocatable :: q_face_left(:) ! 左界面值 + real(wp), allocatable :: q_face_right(:)! 右界面值 + real(wp), allocatable :: flux(:) ! 通量 + real(wp), allocatable :: res(:) ! 残差 + contains + procedure :: initialize => solution_initialize + procedure :: update_old_field => solution_update_old_field + procedure :: print_info => solution_print_info + procedure :: reset => solution_reset_instance + end type solution_type + +contains + + function solution_create(domain) result(solution) + type(domain_type), target, intent(in) :: domain + type(solution_type) :: solution + + integer(ip) :: ncells, nnodes, ntcells + + solution%domain => domain + + ncells = domain%mesh%ncells + nnodes = domain%mesh%nnodes + ntcells = domain%ntcells + + ! 分配数组(与Julia solution.jl一致) + allocate(solution%u(ntcells), source=0.0_wp) + allocate(solution%un(ntcells), source=0.0_wp) + allocate(solution%q_face_left(nnodes), source=0.0_wp) + allocate(solution%q_face_right(nnodes), source=0.0_wp) + allocate(solution%flux(nnodes), source=0.0_wp) + allocate(solution%res(ncells), source=0.0_wp) + + if (domain%config%verbose) then + print *, "[SOLUTION] Created:" + print *, " u size: ", size(solution%u), " (with ghosts)" + print *, " flux size: ", size(solution%flux) + print *, " res size: ", size(solution%res) + end if + end function solution_create + + subroutine solution_initialize(this, initial_values) + class(solution_type), intent(inout) :: this + real(wp), intent(in), optional :: initial_values(:) + + integer(ip) :: i, idx + type(domain_type), pointer :: domain + + domain => this%domain + + if (present(initial_values)) then + ! 应用初始值到物理区域 + do i = domain%ist, domain%ied - 1 + idx = i - domain%ist + 1 + if (idx <= size(initial_values)) then + this%u(i) = initial_values(idx) + end if + end do + else + ! 默认为0 + this%u = 0.0_wp + end if + + ! 同步旧场(与Julia的update_old_field一致) + call this%update_old_field() + + if (domain%config%verbose) then + print *, "[SOLUTION] Initialized" + print *, " u range: ", minval(this%u), " to ", maxval(this%u) + end if + end subroutine solution_initialize + + subroutine solution_update_old_field(this) + class(solution_type), intent(inout) :: this + this%un = this%u ! 与Julia的 un .= u 一致 + end subroutine solution_update_old_field + + subroutine solution_reset_instance(this) + class(solution_type), intent(inout) :: this + call solution_reset(this) + end subroutine solution_reset_instance + + subroutine solution_reset(solution) + type(solution_type), intent(inout) :: solution + + if (allocated(solution%u)) solution%u = 0.0_wp + if (allocated(solution%un)) solution%un = 0.0_wp + if (allocated(solution%q_face_left)) solution%q_face_left = 0.0_wp + if (allocated(solution%q_face_right)) solution%q_face_right = 0.0_wp + if (allocated(solution%flux)) solution%flux = 0.0_wp + if (allocated(solution%res)) solution%res = 0.0_wp + + if (associated(solution%domain) .and. solution%domain%config%verbose) then + print *, "[SOLUTION] Reset" + end if + end subroutine solution_reset + + subroutine solution_print_info(this) + class(solution_type), intent(in) :: this + + print *, "=== Solution Information ===" + print *, "Arrays:" + print *, " u: ", size(this%u), " elements" + print *, " un: ", size(this%un), " elements" + print *, " q_face_left: ", size(this%q_face_left), " elements" + print *, " q_face_right: ", size(this%q_face_right), " elements" + print *, " flux: ", size(this%flux), " elements" + print *, " res: ", size(this%res), " elements" + + if (allocated(this%u)) then + print *, "Values:" + print *, " u min/max: ", minval(this%u), maxval(this%u) + print *, " un min/max: ", minval(this%un), maxval(this%un) + end if + print *, "============================" + end subroutine solution_print_info + +end module solution_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/manager/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03d/src/manager/CMakeLists.txt new file mode 100644 index 000000000..00c8bf49b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/manager/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/manager/CMakeLists.txt +message(STATUS "配置管理器模块...") + +# 创建管理器库 +add_library(manager STATIC + component_manager.f90 + component_factory.f90 +) + +# 明确依赖关系:管理器依赖所有其他模块 +target_link_libraries(manager + PRIVATE + core + infrastructure + reconstructor + flux +) + +# 设置模块输出目录 +set_target_properties(manager PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "管理器模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/manager/component_factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/src/manager/component_factory.f90 new file mode 100644 index 000000000..de8cbf1a8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/manager/component_factory.f90 @@ -0,0 +1,142 @@ +! src/manager/component_factory.f90 (完整文件) +module component_factory_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use config_module, only: cfd_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + + use eno_reconstructor_module, only: eno_reconstructor, create_eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor, create_weno3_reconstructor + use weno5_reconstructor_module, only: weno5_reconstructor, create_weno5_reconstructor + use rusanov_flux_module, only: rusanov_flux, create_rusanov_flux + + implicit none + private + public :: wp, create_reconstructor, create_flux_calculator + + ! 错误代码 + integer, parameter :: CM_SUCCESS = 0 + integer, parameter :: CM_ERROR_UNKNOWN_SCHEME = 1 + integer, parameter :: CM_ERROR_UNKNOWN_FLUX = 2 + integer, parameter :: CM_ERROR_INVALID_ORDER = 3 + +contains + + ! ==================== 重构器创建 ==================== + + function create_reconstructor(config, status) result(recon) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(reconstructor_base), allocatable :: recon + + character(len=20) :: scheme + integer :: order, error_code + + scheme = trim(adjustl(config%recon_scheme)) + order = config%spatial_order + + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating reconstructor: ", scheme, " order=", order + end if + + ! 处理"weno"作为WENO5的别名(与Julia一致) + if (scheme == "weno" .and. order == 5) then + scheme = "weno5" + end if + + select case(scheme) + case('eno') + allocate(eno_reconstructor :: recon) + select type(recon) + type is(eno_reconstructor) + recon = create_eno_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case('weno3') + allocate(weno3_reconstructor :: recon) + select type(recon) + type is(weno3_reconstructor) + recon = create_weno3_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case('weno5') + allocate(weno5_reconstructor :: recon) + select type(recon) + type is(weno5_reconstructor) + recon = create_weno5_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case default + error_code = CM_ERROR_UNKNOWN_SCHEME + if (config%verbose) then + print *, "[ERROR] Unknown reconstructor scheme: ", scheme + print *, " Available: eno, weno3, weno5" + end if + end select + + ! 检查阶数有效性 + if (error_code == CM_SUCCESS) then + if (order < 1) then + error_code = CM_ERROR_INVALID_ORDER + if (config%verbose) then + print *, "[ERROR] Invalid spatial order: ", order + end if + end if + end if + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Reconstructor creation failed" + end if + end function create_reconstructor + + ! ==================== 通量计算器创建 ==================== + + function create_flux_calculator(config, status) result(flux) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(flux_calculator_base), allocatable :: flux + + character(len=20) :: flux_type + integer :: error_code + + flux_type = trim(adjustl(config%flux_type)) + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating flux calculator: ", flux_type + end if + + select case(flux_type) + case('rusanov') + allocate(rusanov_flux :: flux) + select type(flux) + type is(rusanov_flux) + flux = create_rusanov_flux() + flux%wave_speed_default = config%wave_speed + end select + + case default + error_code = CM_ERROR_UNKNOWN_FLUX + if (config%verbose) then + print *, "[ERROR] Unknown flux type: ", flux_type + print *, " Available: rusanov" + end if + end select + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Flux calculator creation failed" + end if + end function create_flux_calculator + +end module component_factory_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/manager/component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/src/manager/component_manager.f90 new file mode 100644 index 000000000..25eac29be --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/manager/component_manager.f90 @@ -0,0 +1,76 @@ +! src/manager/component_manager.f90 (完整文件) +module component_manager_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use config_module, only: cfd_config + use component_factory_module, only: create_reconstructor, create_flux_calculator + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + + implicit none + private + public :: wp, component_manager_info, validate_config + public :: create_reconstructor, create_flux_calculator + +contains + + ! ==================== 配置验证 ==================== + + function validate_config(config) result(is_valid) + type(cfd_config), intent(in) :: config + logical :: is_valid + + integer :: status + class(reconstructor_base), allocatable :: test_recon + class(flux_calculator_base), allocatable :: test_flux + + is_valid = .false. + + ! 测试创建重构器 + test_recon = create_reconstructor(config, status) + if (status /= 0) then + if (config%verbose) then + print *, "[CONFIG VALIDATION] Invalid reconstructor configuration" + end if + return + end if + + ! 测试创建通量计算器 + test_flux = create_flux_calculator(config, status) + if (status /= 0) then + if (config%verbose) then + print *, "[CONFIG VALIDATION] Invalid flux configuration" + end if + return + end if + + ! 清理测试组件 + if (allocated(test_recon)) deallocate(test_recon) + if (allocated(test_flux)) deallocate(test_flux) + + is_valid = .true. + + if (config%verbose) then + print *, "[CONFIG VALIDATION] Configuration is valid" + end if + end function validate_config + + ! ==================== 信息显示 ==================== + + subroutine component_manager_info() + print *, "=== Component Manager ===" + print *, "Available reconstructors:" + print *, " - eno (orders: 1-7)" + print *, " - weno3 (order: 3)" + print *, " - weno5 (order: 5)" + print *, "" + print *, "Available flux calculators:" + print *, " - rusanov" + print *, "" + print *, "Features:" + print *, " - Configuration validation" + print *, " - Component creation from config" + print *, " - Error handling with status codes" + print *, "=========================" + end subroutine component_manager_info + +end module component_manager_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..66874a7ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/CMakeLists.txt @@ -0,0 +1,7 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..3fde7746b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,28 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +# Start with just the base module +add_library(flux STATIC + base.f90 + rusanov.f90 +) + +#target_link_libraries(flux PRIVATE core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 明确设置不使用 /Qm64 选项 +if(CMAKE_Fortran_COMPILER_ID MATCHES "IntelLLVM|Intel") + set_target_properties(flux PROPERTIES + Fortran_FLAGS "${CMAKE_Fortran_FLAGS} /Qm64" # 明确设置,避免继承问题 + ) +endif() + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/flux/base.f90 new file mode 100644 index 000000000..7080a7ae4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/flux/base.f90 @@ -0,0 +1,30 @@ +!src/numerics/flux/base.f90 +module flux_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, flux_calculator_base + + ! Base flux calculator type + type :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure :: info => flux_info + procedure :: print_basic_info => flux_print_basic ! 添加辅助方法 + end type flux_calculator_base + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "Flux calculator information:" + print *, " Name: ", trim(this%name) + end subroutine flux_info + + subroutine flux_print_basic(this) + class(flux_calculator_base), intent(in) :: this + print *, " Name: ", trim(this%name) + end subroutine flux_print_basic + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/flux/rusanov.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/flux/rusanov.f90 new file mode 100644 index 000000000..daa9e3bb5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/flux/rusanov.f90 @@ -0,0 +1,41 @@ +! src/numerics/flux/rusanov.f90 +module rusanov_flux_module + use, intrinsic :: iso_fortran_env, only: real64 + use flux_base_module, only: flux_calculator_base + implicit none + + private + public :: real64, rusanov_flux, create_rusanov_flux + + type, extends(flux_calculator_base) :: rusanov_flux + real(real64) :: wave_speed_default = 1.0_real64 + contains + procedure :: info => rusanov_info + end type rusanov_flux + + ! 构造函数接口 + interface rusanov_flux + module procedure create_rusanov_flux + end interface + +contains + + ! 构造函数 + type(rusanov_flux) function create_rusanov_flux() result(this) + this%name = "Rusanov" + this%wave_speed_default = 1.0_real64 + end function create_rusanov_flux + + subroutine rusanov_info(this) + class(rusanov_flux), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Flux calculator information:" + call this%print_basic_info() + + ! 添加Rusanov特有信息 + print *, " Type: Rusanov flux" + print *, " Default wave speed: ", this%wave_speed_default + end subroutine rusanov_info + +end module rusanov_flux_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..c88ea647b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,23 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "Configuring reconstructor module...") + +# Start with just the base module +add_library(reconstructor STATIC + base.f90 + eno.f90 + weno3.f90 + weno5.f90 +) + +target_link_libraries(reconstructor PRIVATE core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Reconstructor module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..53798d021 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/reconstructor/base.f90 @@ -0,0 +1,33 @@ +!src/numerics/reconstructor/base.f90 +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, reconstructor_base + + ! Base reconstructor type + type :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + contains + procedure :: info => reconstructor_info + procedure :: print_basic_info => reconstructor_print_basic ! 添加一个辅助方法 + end type reconstructor_base + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + print *, "Reconstructor information:" + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_info + + subroutine reconstructor_print_basic(this) + class(reconstructor_base), intent(in) :: this + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_print_basic + +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/reconstructor/eno.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/reconstructor/eno.f90 new file mode 100644 index 000000000..f973e8b32 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/reconstructor/eno.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/eno.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, eno_reconstructor, create_eno_reconstructor ! ← 添加这个 + + type, extends(reconstructor_base) :: eno_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => eno_info + end type eno_reconstructor + + ! 构造函数接口 + interface eno_reconstructor + module procedure create_eno_reconstructor + end interface + +contains + + ! 构造函数 + type(eno_reconstructor) function create_eno_reconstructor() result(this) + this%name = "ENO" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_eno_reconstructor + + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加ENO特有信息 + print *, " Type: ENO reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/reconstructor/weno3.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/reconstructor/weno3.f90 new file mode 100644 index 000000000..d5b7a7477 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/reconstructor/weno3.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/weno3.f90 +module weno3_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno3_reconstructor, create_weno3_reconstructor + + type, extends(reconstructor_base) :: weno3_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno3_info + end type weno3_reconstructor + + ! 构造函数接口 + interface weno3_reconstructor + module procedure create_weno3_reconstructor + end interface + +contains + + ! 构造函数 + type(weno3_reconstructor) function create_weno3_reconstructor() result(this) + this%name = "WENO3" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_weno3_reconstructor + + subroutine weno3_info(this) + class(weno3_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加WENO3特有信息 + print *, " Type: WENO-3 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno3_info + +end module weno3_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/reconstructor/weno5.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/reconstructor/weno5.f90 new file mode 100644 index 000000000..a869c67d0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/numerics/reconstructor/weno5.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/weno5.f90(新增) +module weno5_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno5_reconstructor, create_weno5_reconstructor + + type, extends(reconstructor_base) :: weno5_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno5_info + end type weno5_reconstructor + + ! 构造函数接口 + interface weno5_reconstructor + module procedure create_weno5_reconstructor + end interface + +contains + + ! 构造函数 + type(weno5_reconstructor) function create_weno5_reconstructor() result(this) + this%name = "WENO5" + this%order = 5 + this%epsilon = 1.0e-6_real64 + end function create_weno5_reconstructor + + subroutine weno5_info(this) + class(weno5_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加WENO5特有信息 + print *, " Type: WENO-5 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno5_info + +end module weno5_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/physics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03d/src/physics/CMakeLists.txt new file mode 100644 index 000000000..cc4e233ab --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/physics/CMakeLists.txt @@ -0,0 +1,19 @@ +# src/physics/CMakeLists.txt +message(STATUS "配置物理模块...") + +# 创建物理模块库 +add_library(physics STATIC + physics_interface.f90 + equations/linear_convection.f90 + problems/linear_convection_problem.f90 +) + +# 链接依赖 +target_link_libraries(physics PRIVATE base) + +# 设置模块输出目录 +set_target_properties(physics PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "物理模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/physics/equations/linear_convection.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/src/physics/equations/linear_convection.f90 new file mode 100644 index 000000000..fff7be55d --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/physics/equations/linear_convection.f90 @@ -0,0 +1,49 @@ +! src/physics/equations/linear_convection.f90 +module linear_convection_equation + use precision_module, only: wp, ip + use physics_interface, only: physics_equation + implicit none + private + + ! 具体方程类型 - 先声明 + type, extends(physics_equation) :: linear_convection_eq + real(wp) :: wave_speed = 1.0_wp + contains + procedure :: flux => lc_flux + procedure :: speed => lc_speed + end type linear_convection_eq + + ! 公开接口 + public :: wp, ip + public :: linear_convection_eq, create_linear_convection_eq + +contains + + ! 构造函数 + function create_linear_convection_eq(wave_speed) result(eq) + real(wp), intent(in), optional :: wave_speed + type(linear_convection_eq) :: eq + + eq%name = "Linear Convection" + if (present(wave_speed)) then + eq%wave_speed = wave_speed + else + eq%wave_speed = 1.0_wp + end if + end function create_linear_convection_eq + + ! 方法实现 + pure function lc_flux(this, u) result(f) + class(linear_convection_eq), intent(in) :: this + real(wp), intent(in) :: u + real(wp) :: f + f = this%wave_speed * u + end function lc_flux + + pure function lc_speed(this) result(a) + class(linear_convection_eq), intent(in) :: this + real(wp) :: a + a = this%wave_speed + end function lc_speed + +end module linear_convection_equation \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/physics/physics_interface.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/src/physics/physics_interface.f90 new file mode 100644 index 000000000..45002da6c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/physics/physics_interface.f90 @@ -0,0 +1,64 @@ +! src/physics/physics_interface.f90 +module physics_interface + use precision_module, only: wp, ip + implicit none + private + + ! 定义抽象基类型 - 先声明为私有,然后在公开部分导出 + type, abstract :: physics_equation + character(len=:), allocatable :: name + contains + procedure(eq_flux_abs), deferred :: flux + procedure(eq_speed_abs), deferred :: speed + end type physics_equation + + type, abstract :: physics_problem + character(len=:), allocatable :: name + contains + procedure(prob_ic_abs), deferred :: initial_condition + procedure(prob_bc_abs), deferred :: boundary_condition + procedure(prob_exact_abs), deferred :: exact_solution + end type physics_problem + + ! 抽象接口定义 + abstract interface + pure function eq_flux_abs(this, u) result(f) + import :: physics_equation, wp + class(physics_equation), intent(in) :: this + real(wp), intent(in) :: u + real(wp) :: f + end function eq_flux_abs + + pure function eq_speed_abs(this) result(a) + import :: physics_equation, wp + class(physics_equation), intent(in) :: this + real(wp) :: a + end function eq_speed_abs + + subroutine prob_ic_abs(this, x, u) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(in) :: x(:) + real(wp), intent(out) :: u(:) + end subroutine prob_ic_abs + + subroutine prob_bc_abs(this, u, t) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(inout) :: u(:) + real(wp), intent(in), optional :: t + end subroutine prob_bc_abs + + function prob_exact_abs(this, x, t) result(u) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(in) :: x(:), t + real(wp), dimension(size(x)) :: u + end function prob_exact_abs + end interface + + ! 公开接口 - 使用独立的public语句 + public :: wp, ip + public :: physics_equation, physics_problem + +end module physics_interface \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/physics/problems/linear_convection_problem.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/src/physics/problems/linear_convection_problem.f90 new file mode 100644 index 000000000..06226ed13 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/physics/problems/linear_convection_problem.f90 @@ -0,0 +1,118 @@ +! src/physics/problems/linear_convection_problem.f90 +module linear_convection_problem + use precision_module, only: wp, ip + use physics_interface, only: physics_problem + implicit none + private + + ! 具体问题类型 - 先声明 + type, extends(physics_problem) :: linear_convection_prob + real(wp) :: wave_speed = 1.0_wp + real(wp) :: domain_length = 2.0_wp + character(len=20) :: ic_type = "step" + character(len=20) :: boundary_type = "periodic" + contains + procedure :: initial_condition => lc_initial_condition + procedure :: boundary_condition => lc_boundary_condition + procedure :: exact_solution => lc_exact_solution + end type linear_convection_prob + + ! 公开接口 + public :: wp, ip + public :: linear_convection_prob, create_linear_convection_prob + +contains + + ! 构造函数 + function create_linear_convection_prob(wave_speed, domain_length, & + ic_type, boundary_type) result(prob) + real(wp), intent(in), optional :: wave_speed, domain_length + character(len=*), intent(in), optional :: ic_type, boundary_type + type(linear_convection_prob) :: prob + + prob%name = "Linear Convection Problem" + + if (present(wave_speed)) prob%wave_speed = wave_speed + if (present(domain_length)) prob%domain_length = domain_length + if (present(ic_type)) prob%ic_type = ic_type + if (present(boundary_type)) prob%boundary_type = boundary_type + end function create_linear_convection_prob + + ! 初始条件 + subroutine lc_initial_condition(this, x, u) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(in) :: x(:) + real(wp), intent(out) :: u(:) + + integer :: i + + select case (trim(this%ic_type)) + case ("step") + do i = 1, size(x) + if (x(i) >= 0.5_wp .and. x(i) <= 1.0_wp) then + u(i) = 2.0_wp + else + u(i) = 1.0_wp + end if + end do + + case ("sin", "sine") + do i = 1, size(x) + u(i) = sin(2.0_wp * 3.141592653589793_wp * x(i) / this%domain_length) + end do + + case ("gaussian") + do i = 1, size(x) + u(i) = exp(-((x(i) - 0.5_wp) / 0.1_wp)**2) + end do + + case default + ! 默认阶跃函数 + do i = 1, size(x) + if (x(i) >= 0.5_wp .and. x(i) <= 1.0_wp) then + u(i) = 2.0_wp + else + u(i) = 1.0_wp + end if + end do + end select + end subroutine lc_initial_condition + + ! 边界条件(虚拟实现,实际在boundary模块) + subroutine lc_boundary_condition(this, u, t) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(inout) :: u(:) + real(wp), intent(in), optional :: t + + ! 边界条件将在独立模块实现 + print *, "[PROBLEM] Boundary condition placeholder" + if (present(t)) then + print *, " Time = ", t + end if + end subroutine lc_boundary_condition + + ! 精确解(周期性平移) + function lc_exact_solution(this, x, t) result(u) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(in) :: x(:), t + real(wp), dimension(size(x)) :: u + real(wp), dimension(size(x)) :: x_shifted + integer :: i + + ! 周期性平移 + do i = 1, size(x) + x_shifted(i) = x(i) - this%wave_speed * t + ! 确保在 [0, domain_length) 范围内 + do while (x_shifted(i) < 0.0_wp) + x_shifted(i) = x_shifted(i) + this%domain_length + end do + do while (x_shifted(i) >= this%domain_length) + x_shifted(i) = x_shifted(i) - this%domain_length + end do + end do + + ! 重用初始条件函数 + call this%initial_condition(x_shifted, u) + end function lc_exact_solution + +end module linear_convection_problem \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/solver/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03d/src/solver/CMakeLists.txt new file mode 100644 index 000000000..f8499eecd --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/solver/CMakeLists.txt @@ -0,0 +1,21 @@ +# src/solver/CMakeLists.txt +message(STATUS "配置求解器模块...") + +add_library(solver STATIC + base.f90 + physics_solver.f90 +) + +target_link_libraries(solver + PRIVATE + infrastructure + core + physics + manager +) + +set_target_properties(solver PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "求解器模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/solver/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/src/solver/base.f90 new file mode 100644 index 000000000..cfd78c475 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/solver/base.f90 @@ -0,0 +1,264 @@ +! src/solver/base.f90 +module solver_base_module + use base_modules, only: wp => wp, ip => ip ! 重命名以避免冲突 + + use config_module, only: cfd_config + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create + + implicit none + private + + ! 明确导出列表 + public :: wp, ip ! 类型参数 + public :: solver_base, create_solver_base ! 类型和构造函数 + public :: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, SOLVER_RUNNING + public :: SOLVER_COMPLETED, SOLVER_ERROR ! 状态常量 + + ! 求解器状态枚举 + integer, parameter :: SOLVER_UNINITIALIZED = 0 + integer, parameter :: SOLVER_INITIALIZED = 1 + integer, parameter :: SOLVER_RUNNING = 2 + integer, parameter :: SOLVER_COMPLETED = 3 + integer, parameter :: SOLVER_ERROR = 4 + + ! 求解器基类 + type :: solver_base + ! 基本组件 + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + + ! 状态管理 + integer :: state = SOLVER_UNINITIALIZED + character(len=100) :: error_message = "" + real(wp) :: current_time = 0.0_wp + integer(ip) :: current_step = 0 + + ! 时间控制 + real(wp) :: dt_original = 0.0_wp + contains + procedure :: initialize => solver_base_initialize + procedure :: step => solver_base_step + procedure :: run_to_time => solver_base_run_to_time + procedure :: cleanup => solver_base_cleanup + procedure :: get_state => solver_base_get_state + procedure :: get_error => solver_base_get_error + procedure :: print_info => solver_base_print_info + end type solver_base + + ! 构造函数接口 + interface solver_base + module procedure create_solver_base + end interface + +contains + + ! ==================== 构造函数 ==================== + + function create_solver_base(config, mesh) result(solver) + type(cfd_config), intent(in) :: config + type(mesh_type), intent(in) :: mesh + type(solver_base) :: solver + + solver%config = config + solver%mesh = mesh + + ! 创建域 + solver%domain = domain_create(config, mesh) + + ! 创建解 + solver%solution = solution_create(solver%domain) + + ! 保存原始时间步长 + solver%dt_original = config%dt + + if (config%verbose) then + print *, "[SOLVER] Base solver created" + print *, " Mesh cells: ", mesh%ncells + print *, " Domain total cells: ", solver%domain%ntcells + end if + end function create_solver_base + + ! ==================== 初始化 ==================== + + subroutine solver_base_initialize(this) + class(solver_base), intent(inout) :: this + + if (this%state == SOLVER_INITIALIZED) then + if (this%config%verbose) then + print *, "[SOLVER] Already initialized" + end if + return + end if + + ! 初始化解(通过配置) + ! 这里暂时简化,实际需要调用初始条件工厂 + print *, "[INFO] Base solver initialized (simplified)" + + ! 更新状态 + this%state = SOLVER_INITIALIZED + this%current_time = 0.0_wp + this%current_step = 0 + + if (this%config%verbose) then + print *, "[SOLVER] Initialized at t = ", this%current_time + end if + end subroutine solver_base_initialize + + ! ==================== 单步计算(虚方法) ==================== + + subroutine solver_base_step(this, dt) + class(solver_base), intent(inout) :: this + real(wp), intent(in) :: dt + + ! 基类中这只是虚方法,需要在子类中实现 + print *, "[INFO] Base solver step (virtual method)" + print *, " dt = ", dt + print *, " t = ", this%current_time + + ! 更新时间 + this%current_time = this%current_time + dt + this%current_step = this%current_step + 1 + + ! 简单模拟:只是更新状态 + if (this%config%verbose) then + print *, "[SOLVER] Step completed: t = ", this%current_time, & + ", step = ", this%current_step + end if + end subroutine solver_base_step + + ! ==================== 运行到指定时间 ==================== + + subroutine solver_base_run_to_time(this, final_time) + class(solver_base), intent(inout) :: this + real(wp), intent(in) :: final_time + + real(wp) :: dt, t_remaining + integer :: step_count + + if (this%state /= SOLVER_INITIALIZED) then + this%error_message = "Solver not initialized" + this%state = SOLVER_ERROR + if (this%config%verbose) then + print *, "[SOLVER BASE ERROR] Not initialized: ", trim(this%error_message) + end if + return + end if + + this%state = SOLVER_RUNNING + step_count = 0 + + if (this%config%verbose) then + print *, "[SOLVER BASE] Running from t = ", this%current_time, & + " to t = ", final_time + print *, " Time step: ", this%config%dt + end if + + do while (this%current_time < final_time - 1e-12_wp) + ! 计算时间步长 + t_remaining = final_time - this%current_time + dt = min(this%config%dt, t_remaining) + + ! 执行时间步 + call this%step(dt) + + step_count = step_count + 1 + + ! 每50步输出一次进度 + if (mod(step_count, 50) == 0 .and. this%config%verbose) then + print *, "[SOLVER BASE] Progress: t = ", this%current_time, & + " / ", final_time, " (step ", step_count, ")" + end if + end do + + ! 恢复原始时间步长 + this%config%dt = this%dt_original + + ! 更新状态 + this%state = SOLVER_COMPLETED + + if (this%config%verbose) then + print *, "[SOLVER BASE] Run completed:" + print *, " Final time: ", this%current_time + print *, " Total steps: ", this%current_step + print *, " State: ", this%state + end if + end subroutine solver_base_run_to_time + + ! ==================== 清理 ==================== + + subroutine solver_base_cleanup(this) + class(solver_base), intent(inout) :: this + + ! 重置状态 + this%state = SOLVER_UNINITIALIZED + this%current_time = 0.0_wp + this%current_step = 0 + this%error_message = "" + + if (this%config%verbose) then + print *, "[SOLVER] Cleaned up" + end if + end subroutine solver_base_cleanup + + ! ==================== 状态查询 ==================== + + function solver_base_get_state(this) result(state) + class(solver_base), intent(in) :: this + integer :: state + state = this%state + end function solver_base_get_state + + function solver_base_get_error(this) result(error_msg) + class(solver_base), intent(in) :: this + character(len=100) :: error_msg + error_msg = trim(this%error_message) + end function solver_base_get_error + + ! ==================== 信息打印 ==================== + + subroutine solver_base_print_info(this) + class(solver_base), intent(in) :: this + + character(len=20) :: state_str + + ! 状态字符串 + select case (this%state) + case (SOLVER_UNINITIALIZED) + state_str = "Uninitialized" + case (SOLVER_INITIALIZED) + state_str = "Initialized" + case (SOLVER_RUNNING) + state_str = "Running" + case (SOLVER_COMPLETED) + state_str = "Completed" + case (SOLVER_ERROR) + state_str = "Error" + case default + state_str = "Unknown" + end select + + print *, "=== Solver Information ===" + print *, "State: ", trim(state_str) + print *, "Current time: ", this%current_time + print *, "Current step: ", this%current_step + print *, "Error message: '", trim(this%error_message), "'" + + ! 配置信息 + print *, "Configuration:" + print *, " Scheme: ", trim(this%config%recon_scheme) + print *, " Order: ", this%config%spatial_order + print *, " dt: ", this%config%dt + + ! 域信息 + print *, "Domain:" + print *, " Ghost layers: ", this%domain%nghosts + print *, " Physical cells: ", this%domain%ist, " to ", this%domain%ied - 1 + + print *, "=========================" + end subroutine solver_base_print_info + +end module solver_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/src/solver/physics_solver.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/src/solver/physics_solver.f90 new file mode 100644 index 000000000..f41ac89fd --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/src/solver/physics_solver.f90 @@ -0,0 +1,503 @@ +! src/solver/physics_solver.f90 (简化版) +module physics_solver_module + use base_modules, only: wp => wp, ip => ip + use config_module, only: cfd_config + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create + + use physics_interface, only: physics_equation, physics_problem + use linear_convection_equation, only: linear_convection_eq, create_linear_convection_eq + use linear_convection_problem, only: linear_convection_prob, create_linear_convection_prob + + use component_manager_module, only: create_reconstructor, create_flux_calculator + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + + implicit none + private + + ! 明确导出列表 + public :: wp, ip, physics_solver, create_physics_solver + public :: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, SOLVER_RUNNING + public :: SOLVER_COMPLETED, SOLVER_ERROR + + ! 求解器状态枚举 + integer, parameter :: SOLVER_UNINITIALIZED = 0 + integer, parameter :: SOLVER_INITIALIZED = 1 + integer, parameter :: SOLVER_RUNNING = 2 + integer, parameter :: SOLVER_COMPLETED = 3 + integer, parameter :: SOLVER_ERROR = 4 + + ! 物理求解器类型(不继承,独立实现) + type :: physics_solver + ! 基本组件 + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + + ! 物理组件 + class(physics_equation), allocatable :: equation + class(physics_problem), allocatable :: problem + + ! 数值组件 + class(reconstructor_base), allocatable :: reconstructor + class(flux_calculator_base), allocatable :: flux_calculator + + ! 状态管理 + integer :: state = SOLVER_UNINITIALIZED + character(len=100) :: error_message = "" + real(wp) :: current_time = 0.0_wp + integer(ip) :: current_step = 0 + + ! 时间控制 + real(wp) :: dt_original = 0.0_wp + logical :: physics_initialized = .false. + contains + procedure :: initialize => physics_solver_initialize + procedure :: step => physics_solver_step + procedure :: run_to_time => physics_solver_run_to_time + procedure :: cleanup => physics_solver_cleanup + procedure :: get_state => physics_solver_get_state + procedure :: get_error => physics_solver_get_error + procedure :: print_info => physics_solver_print_info + procedure, private :: create_physics_components + procedure, private :: create_numerical_components + end type physics_solver + + ! 构造函数接口 + interface physics_solver + module procedure create_physics_solver + end interface + +contains + + ! ==================== 构造函数 ==================== + + function create_physics_solver(config, mesh) result(solver) + type(cfd_config), intent(in) :: config + type(mesh_type), intent(in) :: mesh + type(physics_solver) :: solver + + solver%config = config + solver%mesh = mesh + + ! 创建域 + solver%domain = domain_create(config, mesh) + + ! 创建解 + solver%solution = solution_create(solver%domain) + + ! 保存原始时间步长 + solver%dt_original = config%dt + + ! 创建组件 + call solver%create_physics_components() + call solver%create_numerical_components() + + if (config%verbose) then + print *, "[PHYSICS SOLVER] Created:" + print *, " Mesh cells: ", mesh%ncells + print *, " Domain total cells: ", solver%domain%ntcells + end if + end function create_physics_solver + + ! ==================== 创建物理组件 ==================== + + subroutine create_physics_components(this) + class(physics_solver), intent(inout) :: this + + ! 检查是否启用物理模块 + if (.not. this%config%enable_physics) then + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Physics module disabled" + end if + return + end if + + ! 创建物理方程 + select case (trim(this%config%equation_type)) + case ("linear_advection") + allocate(linear_convection_eq :: this%equation) + select type(eq => this%equation) + type is(linear_convection_eq) + eq = create_linear_convection_eq(wave_speed=this%config%wave_speed) + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Created linear convection equation" + print *, " Wave speed: ", eq%wave_speed + end if + end select + + case default + if (this%config%verbose) then + print *, "[WARNING] Unknown equation type: ", trim(this%config%equation_type) + print *, " Using linear convection as default" + end if + + allocate(linear_convection_eq :: this%equation) + select type(eq => this%equation) + type is(linear_convection_eq) + eq = create_linear_convection_eq(wave_speed=this%config%wave_speed) + end select + end select + + ! 创建物理问题 + select case (trim(this%config%problem_type)) + case ("linear_advection") + allocate(linear_convection_prob :: this%problem) + select type(prob => this%problem) + type is(linear_convection_prob) + prob = create_linear_convection_prob( & + wave_speed=this%config%wave_speed, & + domain_length=this%config%domain_length, & + ic_type=this%config%ic_type, & + boundary_type=this%config%boundary_type) + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Created linear convection problem" + print *, " IC type: ", trim(prob%ic_type) + end if + end select + + case default + if (this%config%verbose) then + print *, "[WARNING] Unknown problem type: ", trim(this%config%problem_type) + print *, " Using linear convection as default" + end if + + allocate(linear_convection_prob :: this%problem) + select type(prob => this%problem) + type is(linear_convection_prob) + prob = create_linear_convection_prob( & + wave_speed=this%config%wave_speed, & + domain_length=this%config%domain_length, & + ic_type=this%config%ic_type, & + boundary_type=this%config%boundary_type) + end select + end select + + this%physics_initialized = .true. + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Physics components created" + end if + end subroutine create_physics_components + + ! ==================== 创建数值组件 ==================== + + subroutine create_numerical_components(this) + class(physics_solver), intent(inout) :: this + integer :: status + + ! 创建重构器 + this%reconstructor = create_reconstructor(this%config, status) + if (status /= 0) then + print *, "[ERROR] Failed to create reconstructor" + this%state = SOLVER_ERROR + this%error_message = "Failed to create reconstructor" + return + end if + + ! 创建通量计算器 + this%flux_calculator = create_flux_calculator(this%config, status) + if (status /= 0) then + print *, "[ERROR] Failed to create flux calculator" + this%state = SOLVER_ERROR + this%error_message = "Failed to create flux calculator" + return + end if + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Numerical components created" + end if + end subroutine create_numerical_components + + ! ==================== 初始化 ==================== + + subroutine physics_solver_initialize(this) + class(physics_solver), intent(inout) :: this + + if (this%state == SOLVER_INITIALIZED) then + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Already initialized" + end if + return + end if + + ! 如果启用了物理模块,应用初始条件 + if (this%physics_initialized .and. allocated(this%problem)) then + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Applying initial condition" + end if + + select type(prob => this%problem) + type is(linear_convection_prob) + ! 获取网格单元中心坐标 + call prob%initial_condition(this%mesh%xcc, & + this%solution%u(this%domain%ist:this%domain%ied-1)) + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Initial condition applied" + end if + end select + else + ! 简化的初始化:阶跃函数 + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Using simplified initialization" + end if + + ! 在 [0.5, 1.0] 区域内设为 2.0,其他区域为 1.0 + where (this%mesh%xcc >= 0.5_wp .and. this%mesh%xcc <= 1.0_wp) + this%solution%u(this%domain%ist:this%domain%ied-1) = 2.0_wp + elsewhere + this%solution%u(this%domain%ist:this%domain%ied-1) = 1.0_wp + end where + end if + + ! 同步旧场 + call this%solution%update_old_field() + + ! 更新状态 + this%state = SOLVER_INITIALIZED + this%current_time = 0.0_wp + this%current_step = 0 + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Initialized at t = ", this%current_time + end if + end subroutine physics_solver_initialize + + ! ==================== 时间步进 ==================== + + subroutine physics_solver_step(this, dt) + class(physics_solver), intent(inout) :: this + real(wp), intent(in) :: dt + + integer :: i + real(wp) :: u_val, f_val + + if (this%config%verbose .and. mod(this%current_step, 100) == 0) then + print *, "[PHYSICS SOLVER] Step ", this%current_step + 1, & + " dt = ", dt, " t = ", this%current_time + end if + + ! 更新旧场 + call this%solution%update_old_field() + + ! 简化的数值方法 + do i = this%domain%ist, this%domain%ied - 1 + u_val = this%solution%un(i) ! 使用旧值 + + ! 简单的线性对流:u_t + a*u_x = 0 + ! 使用一阶迎风格式 + this%solution%u(i) = u_val - dt * this%config%wave_speed * & + (u_val - this%solution%un(i-1)) / this%mesh%dx + end do + + ! 更新时间 + this%current_time = this%current_time + dt + this%current_step = this%current_step + 1 + + ! 每100步输出一次进度 + if (this%config%verbose .and. mod(this%current_step, 100) == 0) then + print *, "[PHYSICS SOLVER] Step ", this%current_step, & + " completed, t = ", this%current_time + end if + end subroutine physics_solver_step + + ! ==================== 运行到指定时间 ==================== + + subroutine physics_solver_run_to_time(this, final_time) + class(physics_solver), intent(inout) :: this + real(wp), intent(in) :: final_time + + real(wp) :: dt, t_remaining + integer :: step_count + + if (this%state /= SOLVER_INITIALIZED) then + this%error_message = "Solver not initialized" + this%state = SOLVER_ERROR + if (this%config%verbose) then + print *, "[PHYSICS SOLVER ERROR] Not initialized" + end if + return + end if + + this%state = SOLVER_RUNNING + step_count = 0 + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Running from t = ", this%current_time, & + " to t = ", final_time + end if + + do while (this%current_time < final_time - 1e-12_wp) + ! 计算时间步长 + t_remaining = final_time - this%current_time + dt = min(this%config%dt, t_remaining) + + ! 执行时间步 + call this%step(dt) + + step_count = step_count + 1 + + ! 每100步输出一次进度 + if (mod(step_count, 100) == 0 .and. this%config%verbose) then + print *, "[PHYSICS SOLVER] Progress: t = ", this%current_time, & + " / ", final_time + end if + end do + + ! 恢复原始时间步长 + this%config%dt = this%dt_original + + ! 更新状态 + this%state = SOLVER_COMPLETED + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Run completed:" + print *, " Final time: ", this%current_time + print *, " Total steps: ", this%current_step + print *, " Final u range: ", minval(this%solution%u), " to ", maxval(this%solution%u) + end if + end subroutine physics_solver_run_to_time + + ! ==================== 清理 ==================== + + subroutine physics_solver_cleanup(this) + class(physics_solver), intent(inout) :: this + + integer :: old_state + real(wp) :: old_time + integer(ip) :: old_step + + ! 保存清理前的状态用于调试 + old_state = this%state + old_time = this%current_time + old_step = this%current_step + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Cleaning up..." + print *, " Before cleanup - State: ", old_state + print *, " Before cleanup - Time: ", old_time + print *, " Before cleanup - Steps: ", old_step + end if + + ! 清理物理组件 + if (allocated(this%equation)) then + deallocate(this%equation) + if (this%config%verbose) then + print *, " Deallocated equation" + end if + end if + + if (allocated(this%problem)) then + deallocate(this%problem) + if (this%config%verbose) then + print *, " Deallocated problem" + end if + end if + + ! 清理数值组件 + if (allocated(this%reconstructor)) then + deallocate(this%reconstructor) + if (this%config%verbose) then + print *, " Deallocated reconstructor" + end if + end if + + if (allocated(this%flux_calculator)) then + deallocate(this%flux_calculator) + if (this%config%verbose) then + print *, " Deallocated flux calculator" + end if + end if + + ! 重置状态 - 但不重置解数组(保持分配) + this%state = SOLVER_UNINITIALIZED + this%current_time = 0.0_wp + this%current_step = 0 + this%error_message = "" + this%physics_initialized = .false. + + if (this%config%verbose) then + print *, " After cleanup - State: ", this%state + print *, " After cleanup - Time: ", this%current_time + print *, " After cleanup - Steps: ", this%current_step + print *, "[PHYSICS SOLVER] Cleanup completed" + end if + + end subroutine physics_solver_cleanup + + ! ==================== 状态查询 ==================== + + function physics_solver_get_state(this) result(state) + class(physics_solver), intent(in) :: this + integer :: state + state = this%state + end function physics_solver_get_state + + function physics_solver_get_error(this) result(error_msg) + class(physics_solver), intent(in) :: this + character(len=100) :: error_msg + error_msg = trim(this%error_message) + end function physics_solver_get_error + + ! ==================== 信息打印 ==================== + + subroutine physics_solver_print_info(this) + class(physics_solver), intent(in) :: this + + character(len=20) :: state_str + + ! 状态字符串 + select case (this%state) + case (SOLVER_UNINITIALIZED) + state_str = "Uninitialized" + case (SOLVER_INITIALIZED) + state_str = "Initialized" + case (SOLVER_RUNNING) + state_str = "Running" + case (SOLVER_COMPLETED) + state_str = "Completed" + case (SOLVER_ERROR) + state_str = "Error" + case default + write(state_str, '(A, I3)') "Unknown ", this%state + end select + + print *, "=== Physics Solver Information ===" + print *, "State: ", trim(state_str), " (", this%state, ")" + print *, "Current time: ", this%current_time + print *, "Current step: ", this%current_step + print *, "Error message: '", trim(this%error_message), "'" + + ! 配置信息 + print *, "Configuration:" + print *, " Scheme: ", trim(this%config%recon_scheme) + print *, " Order: ", this%config%spatial_order + print *, " dt: ", this%config%dt + print *, " Final time: ", this%config%final_time + + ! 域信息 + print *, "Domain:" + print *, " Ghost layers: ", this%domain%nghosts + print *, " Physical cells: ", this%domain%ist, " to ", this%domain%ied - 1 + + ! 物理信息 + print *, "Physics:" + print *, " Initialized: ", this%physics_initialized + print *, " Equation type: ", trim(this%config%equation_type) + print *, " Problem type: ", trim(this%config%problem_type) + + ! 解信息 + if (allocated(this%solution%u)) then + print *, "Solution:" + print *, " u size: ", size(this%solution%u) + print *, " u physical size: ", this%domain%ied - this%domain%ist + end if + + print *, "===================================" + end subroutine physics_solver_print_info + +end module physics_solver_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/CMakeLists.txt new file mode 100644 index 000000000..b609e28d9 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/CMakeLists.txt @@ -0,0 +1,106 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +# 基础设施测试 +add_executable(test_infrastructure test_infrastructure.f90) +target_link_libraries(test_infrastructure + PRIVATE + infrastructure + core +) + +# 注册系统测试 +add_executable(test_registry test_registry.f90) +target_link_libraries(test_registry + PRIVATE + core + infrastructure +) + +# 物理模块测试 +add_executable(test_physics test_physics.f90) +target_link_libraries(test_physics + PRIVATE + physics + base +) + +# 组件管理器测试 +add_executable(test_component_manager test_component_manager.f90) +target_link_libraries(test_component_manager + PRIVATE + manager + infrastructure +) + +# 配置物理测试 +add_executable(test_config_physics test_config_physics.f90) +target_link_libraries(test_config_physics + PRIVATE + infrastructure + core +) + +# 求解器基础测试 +add_executable(test_solver_base test_solver_base.f90) +target_link_libraries(test_solver_base + PRIVATE + solver + infrastructure + core +) + +# 物理求解器测试 +add_executable(test_physics_solver test_physics_solver.f90) +target_link_libraries(test_physics_solver + PRIVATE + solver + infrastructure + core + physics + manager +) + +# 新增:简单物理求解器测试 +add_executable(test_physics_solver_simple test_physics_solver_simple.f90) +target_link_libraries(test_physics_solver_simple + PRIVATE + solver + infrastructure + core + physics + manager +) + +add_executable(test_simple_link test_simple_link.f90) +target_link_libraries(test_simple_link + PRIVATE + reconstructor + flux +) + + +add_executable(test_factory_simple test_factory_simple.f90) +target_link_libraries(test_factory_simple + PRIVATE + core + infrastructure + reconstructor + flux +) + +add_executable(test_domain_solution test_domain_solution.f90) +target_link_libraries(test_domain_solution + PRIVATE + infrastructure + core +) + +add_executable(test_component_manager_physics test_component_manager_physics.f90) +target_link_libraries(test_component_manager_physics + PRIVATE + manager + infrastructure + physics + core +) diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_architecture.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_architecture.f90 new file mode 100644 index 000000000..1c40d4677 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_architecture.f90 @@ -0,0 +1,117 @@ +! tests/test_architecture.f90 +program test_architecture + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module + use config_module + use mesh_module + use component_manager_module + + implicit none + + print *, "=== CFD架构测试 ===" + print *, "" + + ! 测试1: 基本系统 + call test_basic_systems() + + ! 测试2: 组件注册 + call test_registry_integration() + + ! 测试3: 配置验证 + call test_config_validation() + + ! 测试4: 完整流程 + call test_full_workflow() + + print *, "" + print *, "=== 架构测试完成 ===" + +contains + + subroutine test_basic_systems() + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "1. 测试基本系统..." + print *, "-------------------" + + ! 测试配置 + call config_print(config) + print *, "✓ 配置创建成功" + + ! 测试网格 + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "✓ 网格创建成功" + print *, "" + end subroutine test_basic_systems + + subroutine test_registry_integration() + print *, "2. 测试注册系统集成..." + print *, "------------------------" + + call initialize_registry(verbose=.true.) + + ! 注册核心组件 + print *, "注册核心组件:" + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + ! 显示注册内容 + call component_registry%list_simple() + print *, "注册表大小: ", registry_get_size() + + call cleanup_registry() + print *, "✓ 注册系统测试完成" + print *, "" + end subroutine test_registry_integration + + subroutine test_config_validation() + type(cfd_config) :: config + logical :: is_valid + + print *, "3. 测试配置验证..." + print *, "-------------------" + + ! 测试有效配置 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + print *, "测试配置:" + print *, " - 重构器: ", trim(config%recon_scheme), " 阶数: ", config%spatial_order + print *, " - 通量: ", trim(config%flux_type) + print *, " - 波速: ", config%wave_speed + + ! 这里调用验证函数(需要先实现) + ! is_valid = validate_config(config) + print *, "✓ 配置验证接口准备就绪" + print *, "" + end subroutine test_config_validation + + subroutine test_full_workflow() + print *, "4. 测试完整流程..." + print *, "-------------------" + + print *, "步骤1: 初始化系统 ✓" + print *, "步骤2: 创建网格 ✓" + print *, "步骤3: 设置配置 ✓" + print *, "步骤4: 创建组件 (待实现)" + print *, "步骤5: 初始化解 (待实现)" + print *, "步骤6: 时间推进 (待实现)" + print *, "步骤7: 输出结果 (待实现)" + print *, "" + + print *, "✓ 流程框架定义完成" + end subroutine test_full_workflow + +end program test_architecture \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_cfd_architecture.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_cfd_architecture.f90 new file mode 100644 index 000000000..1c40d4677 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_cfd_architecture.f90 @@ -0,0 +1,117 @@ +! tests/test_architecture.f90 +program test_architecture + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module + use config_module + use mesh_module + use component_manager_module + + implicit none + + print *, "=== CFD架构测试 ===" + print *, "" + + ! 测试1: 基本系统 + call test_basic_systems() + + ! 测试2: 组件注册 + call test_registry_integration() + + ! 测试3: 配置验证 + call test_config_validation() + + ! 测试4: 完整流程 + call test_full_workflow() + + print *, "" + print *, "=== 架构测试完成 ===" + +contains + + subroutine test_basic_systems() + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "1. 测试基本系统..." + print *, "-------------------" + + ! 测试配置 + call config_print(config) + print *, "✓ 配置创建成功" + + ! 测试网格 + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "✓ 网格创建成功" + print *, "" + end subroutine test_basic_systems + + subroutine test_registry_integration() + print *, "2. 测试注册系统集成..." + print *, "------------------------" + + call initialize_registry(verbose=.true.) + + ! 注册核心组件 + print *, "注册核心组件:" + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + ! 显示注册内容 + call component_registry%list_simple() + print *, "注册表大小: ", registry_get_size() + + call cleanup_registry() + print *, "✓ 注册系统测试完成" + print *, "" + end subroutine test_registry_integration + + subroutine test_config_validation() + type(cfd_config) :: config + logical :: is_valid + + print *, "3. 测试配置验证..." + print *, "-------------------" + + ! 测试有效配置 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + print *, "测试配置:" + print *, " - 重构器: ", trim(config%recon_scheme), " 阶数: ", config%spatial_order + print *, " - 通量: ", trim(config%flux_type) + print *, " - 波速: ", config%wave_speed + + ! 这里调用验证函数(需要先实现) + ! is_valid = validate_config(config) + print *, "✓ 配置验证接口准备就绪" + print *, "" + end subroutine test_config_validation + + subroutine test_full_workflow() + print *, "4. 测试完整流程..." + print *, "-------------------" + + print *, "步骤1: 初始化系统 ✓" + print *, "步骤2: 创建网格 ✓" + print *, "步骤3: 设置配置 ✓" + print *, "步骤4: 创建组件 (待实现)" + print *, "步骤5: 初始化解 (待实现)" + print *, "步骤6: 时间推进 (待实现)" + print *, "步骤7: 输出结果 (待实现)" + print *, "" + + print *, "✓ 流程框架定义完成" + end subroutine test_full_workflow + +end program test_architecture \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_component_manager.f90 new file mode 100644 index 000000000..f60c35056 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_component_manager.f90 @@ -0,0 +1,111 @@ +! tests/test_component_manager.f90 +program test_component_manager + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use component_manager_module, only: create_reconstructor, create_flux_calculator + use component_manager_module, only: component_manager_info, validate_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + implicit none + + type(cfd_config) :: config + class(reconstructor_base), allocatable :: recon + class(flux_calculator_base), allocatable :: flux + integer :: status + logical :: is_valid + + print *, "=== Component Manager Test ===" + print *, "" + + ! 显示组件管理器信息 + call component_manager_info() + print *, "" + + ! 测试1: 基本配置 + print *, "1. Testing basic ENO3 + Rusanov configuration..." + print *, "-----------------------------------------------" + + config%verbose = .true. + call config_print(config) + + ! 配置ENO3重构 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + call config_print(config) + print *, "" + + ! 验证配置 + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Configuration is valid" + else + print *, "[ERROR] Configuration is invalid" + end if + print *, "" + + ! 测试2: 创建组件 + print *, "2. Testing component creation..." + print *, "--------------------------------" + + ! 创建重构器(带状态检查) + recon = create_reconstructor(config, status) + if (status == 0) then + print *, "[OK] Reconstructor created successfully" + call recon%info() + else + print *, "[ERROR] Failed to create reconstructor, code:", status + end if + print *, "" + + ! 创建通量计算器 + flux = create_flux_calculator(config, status) + if (status == 0) then + print *, "[OK] Flux calculator created successfully" + call flux%info() + else + print *, "[ERROR] Failed to create flux calculator, code:", status + end if + print *, "" + + ! 测试3: WENO3重构测试 + print *, "3. Testing WENO3 configuration..." + print *, "---------------------------------" + + call config_with_reconstruction(config, "weno3", 3) + + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] WENO3 configuration is valid" + + ! 创建WENO3重构器 + recon = create_reconstructor(config) + call recon%info() + else + print *, "[ERROR] WENO3 configuration is invalid" + end if + print *, "" + + ! 测试4: 错误配置测试 + print *, "4. Testing invalid configuration..." + print *, "-----------------------------------" + + config%recon_scheme = "unknown_scheme" + config%flux_type = "unknown_flux" + + is_valid = validate_config(config) + if (.not. is_valid) then + print *, "[OK] Invalid configuration correctly rejected" + else + print *, "[ERROR] Invalid configuration should have been rejected" + end if + + ! 清理 + if (allocated(recon)) deallocate(recon) + if (allocated(flux)) deallocate(flux) + + print *, "" + print *, "=== Component manager test completed successfully ===" + +end program test_component_manager \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_component_manager_physics.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_component_manager_physics.f90 new file mode 100644 index 000000000..f2becca95 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_component_manager_physics.f90 @@ -0,0 +1,120 @@ +! tests/test_component_manager_physics.f90 (简化版) +program test_component_manager_physics + use base_modules, only: wp + use config_module, only: cfd_config, config_print, config_with_reconstruction + use component_manager_module, only: component_manager_info, validate_config + + implicit none + + type(cfd_config) :: config + logical :: is_valid + + print *, "=== Component Manager Physics Test (Simplified) ===" + print *, "" + + ! 测试1: 显示组件管理器信息 + print *, "1. Testing component manager info..." + print *, "-------------------------------------" + call component_manager_info() + print *, "" + + ! 测试2: 物理模块测试(默认) + print *, "2. Testing physics module with default configuration..." + print *, "------------------------------------------------------" + + config%verbose = .true. + call config_print(config) + + ! 验证配置 + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Default configuration is valid" + else + print *, "[ERROR] Default configuration is invalid" + end if + print *, "" + + ! 测试3: 测试物理配置 + print *, "3. Testing physics configuration..." + print *, "------------------------------------" + + ! 修改物理参数 + config%equation_type = "linear_advection" + config%problem_type = "linear_advection" + config%wave_speed = 2.5_wp + config%domain_length = 3.0_wp + + print *, "Modified physics configuration:" + print *, " Equation type: ", trim(config%equation_type) + print *, " Problem type: ", trim(config%problem_type) + print *, " Wave speed: ", config%wave_speed + print *, " Domain length: ", config%domain_length + print *, " Physics enabled: ", config%enable_physics + + ! 验证修改后的配置 + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Modified physics configuration is valid" + else + print *, "[ERROR] Modified physics configuration is invalid" + end if + print *, "" + + ! 测试4: 数值组件测试 + print *, "4. Testing numerical components with physics..." + print *, "-----------------------------------------------" + + call config_with_reconstruction(config, "weno3", 3) + config%flux_type = "rusanov" + + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Combined physics+numerics configuration is valid" + else + print *, "[ERROR] Combined configuration is invalid" + end if + print *, "" + + ! 测试5: 物理模块禁用测试 + print *, "5. Testing physics module disabled..." + print *, "---------------------------------------" + + config%enable_physics = .false. + config%verbose = .false. + + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Configuration valid even with physics disabled" + else + print *, "[ERROR] Configuration should be valid with physics disabled" + end if + print *, "" + + ! 测试6: 错误配置测试 + print *, "6. Testing error handling..." + print *, "-----------------------------" + + config%verbose = .true. + config%enable_physics = .true. + config%recon_scheme = "unknown_scheme" + config%flux_type = "unknown_flux" + config%equation_type = "unknown_equation" + config%problem_type = "unknown_problem" + + is_valid = validate_config(config) + if (.not. is_valid) then + print *, "[OK] Invalid configuration correctly rejected" + else + print *, "[ERROR] Invalid configuration should have been rejected" + end if + print *, "" + + print *, "=== Component Manager Physics Test Summary ===" + print *, "✓ Component manager info works" + print *, "✓ Configuration validation works with physics" + print *, "✓ Error handling works correctly" + print *, "✓ Combined physics+numerics validation works" + print *, "" + print *, "下一步: 集成物理模块到求解器框架" + +end program test_component_manager_physics \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_config_physics.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_config_physics.f90 new file mode 100644 index 000000000..c6fef5c0b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_config_physics.f90 @@ -0,0 +1,141 @@ +! tests/test_config_physics.f90 (修复版) +program test_config_physics + use base_modules, only: wp + use config_module, only: cfd_config, config_print, config_with_reconstruction + + implicit none + + type(cfd_config) :: config + + print *, "=== Configuration Physics Test (Simplified) ===" + print *, "" + + ! 测试1: 默认配置 + print *, "1. Testing default configuration..." + print *, "-----------------------------------" + call config_print(config) + print *, "" + + ! 测试2: 验证基础物理字段 + print *, "2. Testing basic physics fields..." + print *, "----------------------------------" + + print *, "Verifying default physics fields:" + + if (trim(config%equation_type) == "linear_advection") then + print *, " ✓ Default equation type: linear_advection" + else + print *, " ✗ Unexpected equation type: ", trim(config%equation_type) + end if + + if (trim(config%problem_type) == "linear_advection") then + print *, " ✓ Default problem type: linear_advection" + else + print *, " ✗ Unexpected problem type: ", trim(config%problem_type) + end if + + if (abs(config%domain_length - 2.0_wp) < 1e-10_wp) then + print *, " ✓ Default domain length: 2.0" + else + print *, " ✗ Unexpected domain length: ", config%domain_length + end if + + if (config%enable_physics) then + print *, " ✓ Physics enabled by default" + else + print *, " ✗ Physics not enabled by default" + end if + + print *, "" + + ! 测试3: 使用类型绑定的方法(正确的方法名) + print *, "3. Testing type-bound procedures..." + print *, "--------------------------------------" + + call config%set_physics_parameters( & + equation_type="burgers_equation", & + problem_type="sod_shock_tube", & + domain_length=3.0_wp, & + enable_physics=.false.) + + print *, "After set_physics_parameters:" + print *, " Equation type: ", trim(config%equation_type) + print *, " Problem type: ", trim(config%problem_type) + print *, " Domain length: ", config%domain_length + print *, " Physics enabled: ", config%enable_physics + + if (trim(config%equation_type) == "burgers_equation") then + print *, " ✓ Equation type modified successfully via set_physics_parameters" + end if + + if (trim(config%problem_type) == "sod_shock_tube") then + print *, " ✓ Problem type modified successfully via set_physics_parameters" + end if + + if (abs(config%domain_length - 3.0_wp) < 1e-10_wp) then + print *, " ✓ Domain length modified successfully via set_physics_parameters" + end if + + if (.not. config%enable_physics) then + print *, " ✓ Physics disabled successfully via set_physics_parameters" + end if + + print *, "" + + ! 测试4: 调用get_physics_info方法 + print *, "4. Testing get_physics_info method..." + print *, "--------------------------------------" + call config%get_physics_info() + print *, "" + + ! 测试5: 高斯脉冲配置 + print *, "5. Testing Gaussian pulse configuration..." + print *, "-----------------------------------------" + + config%ic_type = "gaussian" + config%pulse_center = 0.6_wp + config%pulse_width = 0.15_wp + + print *, "Gaussian pulse parameters:" + print *, " IC type: ", trim(config%ic_type) + print *, " Center: ", config%pulse_center + print *, " Width: ", config%pulse_width + + if (trim(config%ic_type) == "gaussian") then + print *, " ✓ Gaussian IC type set" + end if + + if (abs(config%pulse_center - 0.6_wp) < 1e-10_wp) then + print *, " ✓ Pulse center set" + end if + + if (abs(config%pulse_width - 0.15_wp) < 1e-10_wp) then + print *, " ✓ Pulse width set" + end if + + print *, "" + + ! 测试6: 重构配置 + print *, "6. Testing reconstruction configuration..." + print *, "------------------------------------------" + + call config_with_reconstruction(config, "weno", 5) + + print *, "Reconstruction configuration:" + print *, " Scheme: ", trim(config%recon_scheme) + print *, " Order: ", config%spatial_order + + if (trim(config%recon_scheme) == "weno" .and. config%spatial_order == 5) then + print *, " ✓ WENO5 configuration successful" + else + print *, " ✗ Reconstruction configuration failed" + end if + + print *, "" + + print *, "=== Configuration Physics Test Complete ===" + print *, "✓ Config module updated with physics support" + print *, "✓ Fields can be directly accessed and modified" + print *, "✓ Type-bound procedures work correctly" + +end program test_config_physics \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_domain_solution.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_domain_solution.f90 new file mode 100644 index 000000000..ff659bac2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_domain_solution.f90 @@ -0,0 +1,102 @@ +! tests/test_domain_solution.f90 +program test_domain_solution + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create, solution_reset + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + real(wp), allocatable :: initial_values(:) + integer :: i + + print *, "=== Domain and Solution Test ===" + print *, "" + + ! 测试1: 不同重构方案的ghost层计算 + print *, "1. Testing ghost layer calculation..." + print *, "--------------------------------------" + + ! ENO3 + call config_with_reconstruction(config, "eno", 3) + config%verbose = .false. + call mesh%init(ncells=10) + domain = domain_create(config, mesh) + print *, "ENO3: nghosts = ", domain%nghosts, " (expected: 3)" + + ! WENO3 + call config_with_reconstruction(config, "weno3", 3) + domain = domain_create(config, mesh) + print *, "WENO3: nghosts = ", domain%nghosts, " (expected: 2)" + + ! WENO5 + call config_with_reconstruction(config, "weno", 5) + domain = domain_create(config, mesh) + print *, "WENO5: nghosts = ", domain%nghosts, " (expected: 3)" + print *, "" + + ! 测试2: Solution数组 + print *, "2. Testing solution arrays..." + print *, "------------------------------" + + call config_with_reconstruction(config, "eno", 3) + config%verbose = .true. + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=10) + + domain = domain_create(config, mesh) + call domain%print_info() + print *, "" + + solution = solution_create(domain) + call solution%print_info() + print *, "" + + ! 测试3: 初始化和更新 + print *, "3. Testing initialization and update..." + print *, "----------------------------------------" + + allocate(initial_values(mesh%ncells)) + do i = 1, mesh%ncells + initial_values(i) = sin(2.0_wp * 3.14159265358979_wp * mesh%xcc(i) / mesh%L) + end do + + call solution%initialize(initial_values) + print *, "After initialization:" + print *, " u range: ", minval(solution%u), " to ", maxval(solution%u) + print *, " un range: ", minval(solution%un), " to ", maxval(solution%un) + + ! 修改当前解,测试更新 + solution%u = solution%u * 2.0_wp + call solution%update_old_field() + print *, "After update: max|u - un| = ", maxval(abs(solution%u - solution%un)) + print *, "" + + ! 测试4: 重置 + print *, "4. Testing reset..." + print *, "-------------------" + + call solution_reset(solution) + print *, "After reset:" + print *, " u max: ", maxval(abs(solution%u)) + print *, " un max: ", maxval(abs(solution%un)) + print *, " flux max: ", maxval(abs(solution%flux)) + print *, "" + + deallocate(initial_values) + + print *, "=== Test Summary ===" + print *, "✓ Ghost layer calculation works" + print *, "✓ Domain creation works" + print *, "✓ Solution arrays work" + print *, "✓ Initialization works" + print *, "✓ Field update works" + print *, "✓ Reset works" + print *, "" + print *, "Ready for next step: Implementing Physics modules" + +end program test_domain_solution \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_factory_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_factory_simple.f90 new file mode 100644 index 000000000..db65da7cf --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_factory_simple.f90 @@ -0,0 +1,58 @@ +! tests/test_factory_simple.f90 (修复版) +program test_factory_simple + use base_modules, only: wp ! ← 添加这行 + use config_module, only: cfd_config, config_print + use mesh_module, only: mesh_type + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(eno_reconstructor) :: eno + type(weno3_reconstructor) :: weno3 + type(rusanov_flux) :: rusanov + + print *, "=== Factory Pattern Simple Test ===" + print *, "" + + ! Test 1: Basic systems + print *, "1. Testing basic systems..." + print *, "-----------------------------" + call config_print(config) + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 2: Creating reconstructors + print *, "2. Testing reconstructors..." + print *, "------------------------------" + + ! 创建并测试ENO重构器 + print *, "Creating ENO reconstructor..." + eno = eno_reconstructor() ! 使用构造函数 + call eno%info() ! 必须调用info方法 + + print *, "" + print *, "Creating WENO3 reconstructor..." + weno3 = weno3_reconstructor() ! 使用构造函数 + call weno3%info() ! 必须调用info方法 + print *, "" + + ! Test 3: Creating flux calculator + print *, "3. Testing flux calculator..." + print *, "-------------------------------" + + print *, "Creating Rusanov flux calculator..." + rusanov = rusanov_flux() ! 使用构造函数 + call rusanov%info() ! 必须调用info方法 + print *, "" + + print *, "=== Factory pattern simple test completed successfully ===" + +end program test_factory_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_infrastructure.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_infrastructure.f90 new file mode 100644 index 000000000..22fa92d1a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_infrastructure.f90 @@ -0,0 +1,56 @@ +! tests/test_infrastructure.f90 (原test_basic_only.f90) +program test_infrastructure + use base_modules, only: wp + use config_module, only: cfd_config, config_print + use mesh_module, only: mesh_type + use registry_module, only: registry_init, registry_cleanup, & + register_component_simple, list_components + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "=== 基础设施测试 ===" + print *, "" + + ! 测试1: 配置 + print *, "1. 测试配置模块..." + print *, "-------------------" + call config_print(config) + print *, "" + + ! 测试2: 网格 + print *, "2. 测试网格模块..." + print *, "------------------" + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=5) + print *, "网格初始化:" + print *, " 单元数: ", mesh%ncells + print *, " 节点数: ", mesh%nnodes + print *, " 网格间距: ", mesh%dx + print *, "" + + ! 测试3: 注册系统 + print *, "3. 测试注册系统..." + print *, "------------------" + + call registry_init() + + ! 注册组件(使用简化版本) + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("flux", "rusanov") + + ! 列出组件 + call list_components() + print *, "" + + ! 清理 + call registry_cleanup() + + print *, "=== 基础设施测试通过 ===" + print *, "✓ 配置模块工作正常" + print *, "✓ 网格模块工作正常" + print *, "✓ 注册系统工作正常" + +end program test_infrastructure \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_physics.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_physics.f90 new file mode 100644 index 000000000..3dababb6a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_physics.f90 @@ -0,0 +1,91 @@ +! tests/test_physics.f90 (原test_physics_minimal.f90) +program test_physics + use base_modules, only: wp, ip + use linear_convection_equation, only: linear_convection_eq, create_linear_convection_eq + use linear_convection_problem, only: linear_convection_prob, create_linear_convection_prob + + implicit none + + type(linear_convection_eq) :: eq + type(linear_convection_prob) :: prob + real(wp) :: u, f, a + real(wp), allocatable :: x(:), u_ic(:), u_exact(:) + integer :: i, nx = 10 + + print *, "=== 物理模块基础测试 ===" + print *, "" + + ! 测试1: 方程功能 + print *, "1. 测试方程功能..." + print *, "-------------------" + + eq = create_linear_convection_eq(wave_speed=2.0_wp) + print *, "方程: ", eq%name + print *, "波速: ", eq%wave_speed + + u = 1.5_wp + f = eq%flux(u) + a = eq%speed() + + print *, "u = ", u + print *, "F(u) = ", f, " (期望: 3.0)" + print *, "波速 a = ", a, " (期望: 2.0)" + + if (abs(f - 3.0_wp) < 1e-10_wp .and. abs(a - 2.0_wp) < 1e-10_wp) then + print *, "✓ 方程功能正常" + else + print *, "✗ 方程功能异常" + end if + print *, "" + + ! 测试2: 问题功能 + print *, "2. 测试问题功能..." + print *, "-------------------" + + prob = create_linear_convection_prob(ic_type="step", domain_length=2.0_wp) + print *, "问题: ", prob%name + print *, "初始条件类型: ", trim(prob%ic_type) + print *, "域长度: ", prob%domain_length + + allocate(x(nx), u_ic(nx), u_exact(nx)) + do i = 1, nx + x(i) = 0.0_wp + (i-1) * 0.2_wp + end do + + ! 测试初始条件 + call prob%initial_condition(x, u_ic) + print *, "初始条件范围: ", minval(u_ic), " 到 ", maxval(u_ic) + + ! 测试精确解 + u_exact = prob%exact_solution(x, 0.0_wp) + print *, "t=0时精确解范围: ", minval(u_exact), " 到 ", maxval(u_exact) + + ! 检查阶跃函数 + if (abs(u_ic(1) - 1.0_wp) < 1e-10_wp .and. & + abs(u_ic(6) - 2.0_wp) < 1e-10_wp) then + print *, "✓ 阶跃初始条件正确" + else + print *, "✗ 阶跃初始条件错误" + end if + + ! 检查精确解与初始条件一致 + if (maxval(abs(u_ic - u_exact)) < 1e-10_wp) then + print *, "✓ t=0时精确解与初始条件一致" + else + print *, "✗ 精确解计算错误" + end if + print *, "" + + ! 测试3: 边界条件接口 + print *, "3. 测试边界条件接口..." + print *, "----------------------" + call prob%boundary_condition(u_ic, 0.0_wp) + print *, "✓ 边界条件接口正常" + print *, "" + + deallocate(x, u_ic, u_exact) + + print *, "=== 物理模块测试完成 ===" + print *, "下一步: 将物理模块集成到现有系统中" + +end program test_physics \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_physics_solver.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_physics_solver.f90 new file mode 100644 index 000000000..ba49ffbac --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_physics_solver.f90 @@ -0,0 +1,133 @@ +! tests/test_physics_solver.f90 (修复版) +program test_physics_solver + use base_modules, only: wp ! 使用一致的wp定义 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use mesh_module, only: mesh_type + use solver_base_module, only: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, & + SOLVER_COMPLETED, SOLVER_ERROR + use physics_solver_module, only: physics_solver + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(physics_solver) :: psolver + character(len=100) :: error_msg + integer :: state + + print *, "=== Physics Solver Test ===" + print *, "" + + ! 测试1: 创建物理求解器(默认物理配置) + print *, "1. Creating physics solver (default physics)..." + print *, "------------------------------------------------" + + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%dt = 0.01_wp + config%enable_physics = .true. + + call config_print(config) + + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=10) + + psolver = physics_solver(config, mesh) + call psolver%print_info() + print *, "" + + ! 测试2: 初始化 + print *, "2. Initializing physics solver..." + print *, "----------------------------------" + + call psolver%initialize() + state = psolver%get_state() + error_msg = psolver%get_error() + print *, "State after initialization: ", state + print *, "Expected: ", SOLVER_INITIALIZED + print *, "Match? ", state == SOLVER_INITIALIZED + print *, "Error message: '", trim(error_msg), "'" + print *, "" + + ! 测试3: 运行一小段时间 + print *, "3. Running physics solver (short time)..." + print *, "------------------------------------------" + + call psolver%run_to_time(0.02_wp) + state = psolver%get_state() + error_msg = psolver%get_error() + print *, "State after short run: ", state + print *, "Expected: ", SOLVER_COMPLETED + print *, "Match? ", state == SOLVER_COMPLETED + print *, "Current time: ", psolver%current_time + print *, "Current step: ", psolver%current_step + print *, "" + + ! 测试4: 禁用物理模块 + print *, "4. Testing physics solver with physics disabled..." + print *, "--------------------------------------------------" + + config%enable_physics = .false. + config%verbose = .false. + + psolver = physics_solver(config, mesh) + call psolver%initialize() + call psolver%run_to_time(0.01_wp) + + state = psolver%get_state() + error_msg = psolver%get_error() + print *, "State with physics disabled: ", state + print *, "Expected: ", SOLVER_COMPLETED + print *, "Match? ", state == SOLVER_COMPLETED + print *, "" + + ! 测试5: 不同物理配置 + print *, "5. Testing different physics configurations..." + print *, "----------------------------------------------" + + config%verbose = .true. + config%enable_physics = .true. + config%equation_type = "linear_advection" + config%problem_type = "linear_advection" + config%wave_speed = 2.5_wp + config%domain_length = 3.0_wp + config%ic_type = "gaussian" + + psolver = physics_solver(config, mesh) + call psolver%initialize() + + print *, "Physics configuration test completed" + print *, "" + + ! 测试6: 清理和错误处理 + print *, "6. Testing cleanup and error handling..." + print *, "----------------------------------------" + + call psolver%cleanup() + state = psolver%get_state() + error_msg = psolver%get_error() + print *, "State after cleanup: ", state + print *, "Expected: ", SOLVER_UNINITIALIZED + print *, "Match? ", state == SOLVER_UNINITIALIZED + + ! 尝试运行已清理的求解器 + call psolver%run_to_time(0.01_wp) + state = psolver%get_state() + error_msg = psolver%get_error() + print *, "State after error attempt: ", state + print *, "Expected: ", SOLVER_ERROR + print *, "Match? ", state == SOLVER_ERROR + print *, "Error message: '", trim(error_msg), "'" + print *, "" + + ! 最终信息 + print *, "=== Physics Solver Test Complete ===" + print *, "✓ Physics solver creation works" + print *, "✓ Physics component initialization works" + print *, "✓ Physics-enabled time stepping works" + print *, "✓ Physics disabled mode works" + print *, "✓ Different physics configurations work" + print *, "✓ Cleanup and error handling work" + print *, "" + print *, "下一步: 实现完整的数值方法(重构、通量、时间积分)" + +end program test_physics_solver \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_physics_solver_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_physics_solver_simple.f90 new file mode 100644 index 000000000..13312efde --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_physics_solver_simple.f90 @@ -0,0 +1,161 @@ +! tests/test_physics_solver_simple.f90 +program test_physics_solver_simple + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction + use mesh_module, only: mesh_type + use physics_solver_module, only: physics_solver, SOLVER_COMPLETED + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(physics_solver) :: solver + real(wp) :: final_time, final_step + integer :: state + + print *, "=========================================" + print *, " 简单物理求解器测试" + print *, "=========================================" + print *, "" + + ! 步骤1: 配置 + print *, "[步骤1] 配置求解器..." + print *, "---------------------" + + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%dt = 0.01_wp + config%final_time = 0.1_wp + config%wave_speed = 1.0_wp + config%ic_type = "step" + config%boundary_type = "periodic" + config%equation_type = "linear_advection" + config%problem_type = "linear_advection" + config%enable_physics = .true. + config%domain_length = 1.0_wp + + print *, "配置参数:" + print *, " 重构格式: ", trim(config%recon_scheme) + print *, " 时间步长: ", config%dt + print *, " 最终时间: ", config%final_time + print *, " 波速: ", config%wave_speed + print *, "" + + ! 步骤2: 创建网格 + print *, "[步骤2] 创建网格..." + print *, "-------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + + print *, "网格信息:" + print *, " 单元数: ", mesh%ncells + print *, " 节点数: ", mesh%nnodes + print *, " 网格间距: ", mesh%dx + print *, "" + + ! 步骤3: 创建求解器 + print *, "[步骤3] 创建求解器..." + print *, "---------------------" + + solver = physics_solver(config, mesh) + + print *, "求解器创建成功" + print *, " 初始状态: ", solver%get_state() + print *, "" + + ! 步骤4: 初始化 + print *, "[步骤4] 初始化求解器..." + print *, "-----------------------" + + call solver%initialize() + + state = solver%get_state() + print *, "初始化完成" + print *, " 状态: ", state + print *, " 当前时间: ", solver%current_time + print *, " 当前步数: ", solver%current_step + print *, "" + + ! 步骤5: 运行求解器 + print *, "[步骤5] 运行求解器..." + print *, "---------------------" + + call solver%run_to_time(config%final_time) + + state = solver%get_state() + print *, "运行完成" + print *, " 状态: ", state + print *, " 最终时间: ", solver%current_time + print *, " 总步数: ", solver%current_step + print *, "" + + ! 步骤6: 保存结果 + print *, "[步骤6] 保存结果..." + print *, "-------------------" + + final_time = solver%current_time + final_step = real(solver%current_step, wp) + state = solver%get_state() + + print *, "保存的结果:" + print *, " 状态: ", state + print *, " 时间: ", final_time + print *, " 步数: ", final_step + print *, "" + + ! 步骤7: 清理求解器 + print *, "[步骤7] 清理求解器..." + print *, "---------------------" + + call solver%cleanup() + + print *, "清理后状态:" + print *, " 状态: ", solver%get_state() + print *, " 时间: ", solver%current_time + print *, " 步数: ", solver%current_step + print *, "" + + ! 步骤8: 验证结果 + print *, "[步骤8] 验证结果..." + print *, "-------------------" + + print *, "验证标准:" + print *, " 1. 运行后状态应为 COMPLETED (", SOLVER_COMPLETED, ")" + print *, " 2. 最终时间应接近 ", config%final_time + print *, " 3. 步数应大于 0" + print *, "" + + if (state == SOLVER_COMPLETED) then + print *, "✓ 状态验证通过: COMPLETED" + else + print *, "✗ 状态验证失败: 期望 ", SOLVER_COMPLETED, ", 实际 ", state + end if + + if (abs(final_time - config%final_time) < 1e-5_wp) then + print *, "✓ 时间验证通过: ", final_time, " ≈ ", config%final_time + else + print *, "✗ 时间验证失败: ", final_time, " ≠ ", config%final_time + end if + + if (final_step > 0) then + print *, "✓ 步数验证通过: ", final_step, " > 0" + else + print *, "✗ 步数验证失败: ", final_step, " ≤ 0" + end if + + print *, "" + + ! 最终判断 + if (state == SOLVER_COMPLETED .and. & + abs(final_time - config%final_time) < 1e-5_wp .and. & + final_step > 0) then + print *, "=========================================" + print *, " 所有测试通过! ✓" + print *, "=========================================" + else + print *, "=========================================" + print *, " 测试失败 ✗" + print *, "=========================================" + end if + +end program test_physics_solver_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_registry.f90 new file mode 100644 index 000000000..e82651ffb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_registry.f90 @@ -0,0 +1,87 @@ +! tests/test_registry.f90 (原test_minimal_simple.f90) +program test_registry + use base_modules, only: wp + use registry_module + use config_module + use mesh_module + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== 注册系统功能测试 ===" + print *, "" + + ! 测试1: 配置系统 + print *, "1. 测试配置系统" + print *, "--------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! 测试2: 网格系统 + print *, "2. 测试网格系统" + print *, "--------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! 测试3: 注册系统 + print *, "3. 测试注册系统" + print *, "--------------" + + call registry_init() + + ! 注册组件 + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call list_components() + print *, "注册表大小: ", registry_get_size() + print *, "" + + ! 测试组件查找 + print *, "4. 测试组件查找" + print *, "--------------" + + if (has_component_simple("reconstructor", "eno")) then + print *, "找到: reconstructor.eno" + else + print *, "未找到: reconstructor.eno" + end if + + if (has_component_simple("reconstructor", "unknown")) then + print *, "找到: reconstructor.unknown" + else + print *, "未找到: reconstructor.unknown" + end if + print *, "" + + ! 测试获取可用组件 + print *, "5. 测试注册系统功能" + print *, "------------------" + print *, "注册表已初始化: ", registry_is_initialized() + print *, "组件数量: ", registry_get_size() + print *, "" + + ! 清理 + call registry_cleanup() + + print *, "=== 注册系统测试完成 ===" + +end program test_registry \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_simple_link.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_simple_link.f90 new file mode 100644 index 000000000..71cc614e8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_simple_link.f90 @@ -0,0 +1,78 @@ +! tests/test_simple_link.f90 +program test_simple_link + use base_modules, only: wp ! ← 添加这行 + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call registry_init() + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call list_components() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component_simple("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component_simple("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Cleanup + call registry_cleanup() + + print *, "=== Minimal test completed successfully ===" + +end program test_simple_link \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_solver_base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_solver_base.f90 new file mode 100644 index 000000000..6cfe47e41 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_solver_base.f90 @@ -0,0 +1,99 @@ +! tests/test_solver_base.f90 (修复版) +program test_solver_base + ! 所有 USE 语句必须在程序开始处 + use base_modules, only: wp + use config_module, only: cfd_config, config_print, config_with_reconstruction + use mesh_module, only: mesh_type + use solver_base_module, only: solver_base, SOLVER_UNINITIALIZED, & + SOLVER_INITIALIZED, SOLVER_COMPLETED, SOLVER_ERROR + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(solver_base) :: solver + integer :: state + + print *, "=== Solver Base Test ===" + print *, "" + + ! 测试1: 创建求解器 + print *, "1. Creating solver..." + print *, "----------------------" + + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%dt = 0.01_wp + + call config_print(config) + + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=10) + + solver = solver_base(config, mesh) + call solver%print_info() + print *, "" + + ! 测试2: 初始化 + print *, "2. Initializing solver..." + print *, "-------------------------" + + call solver%initialize() + state = solver%get_state() + print *, "State after initialization: ", state + print *, "Expected: ", SOLVER_INITIALIZED + print *, "Match? ", state == SOLVER_INITIALIZED + print *, "Error message: '", trim(solver%get_error()), "'" + print *, "" + + ! 测试3: 运行求解器 + print *, "3. Running solver..." + print *, "--------------------" + + call solver%run_to_time(0.05_wp) + state = solver%get_state() + print *, "State after run: ", state + print *, "Expected: ", SOLVER_COMPLETED + print *, "Match? ", state == SOLVER_COMPLETED + print *, "Current time: ", solver%current_time + print *, "Current step: ", solver%current_step + print *, "" + + ! 测试4: 再次运行(从已完成状态) + print *, "4. Running again from completed state..." + print *, "----------------------------------------" + + ! 需要先清理才能重新运行 + call solver%cleanup() + call solver%initialize() + call solver%run_to_time(0.1_wp) + + call solver%print_info() + print *, "" + + ! 测试5: 错误处理 + print *, "5. Testing error states..." + print *, "--------------------------" + + ! 创建一个未初始化的求解器 + call solver%cleanup() + state = solver%get_state() + print *, "Uninitialized state: ", state + print *, "Expected: ", SOLVER_UNINITIALIZED + print *, "Match? ", state == SOLVER_UNINITIALIZED + + ! 尝试运行未初始化的求解器 + call solver%run_to_time(0.01_wp) + state = solver%get_state() + print *, "State after error: ", state + print *, "Expected: ", SOLVER_ERROR + print *, "Match? ", state == SOLVER_ERROR + print *, "Error message: '", trim(solver%get_error()), "'" + print *, "" + + print *, "=== Solver Base Test Complete ===" + print *, "✓ Solver base class works" + print *, "✓ State management works" + print *, "✓ Time stepping framework works" + print *, "✓ Error handling works" + +end program test_solver_base \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_solver_framework.f90 b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_solver_framework.f90 new file mode 100644 index 000000000..6754323d0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03d/tests/test_solver_framework.f90 @@ -0,0 +1,91 @@ +! tests/test_solver_framework.f90 +program test_solver_framework + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use mesh_module, only: mesh_type + use solver_module, only: cfd_solver, solver_create, solver_run, solver_cleanup + use solver_module, only: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, & + SOLVER_COMPLETED, SOLVER_ERROR + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(cfd_solver) :: solver + + print *, "=== 求解器框架测试 ===" + print *, "" + + ! 测试1: 基本创建 + print *, "1. 测试求解器创建..." + print *, "----------------------" + + ! 创建配置 + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.0_real64 + config%dt = 0.01_real64 + + call config_print(config) + print *, "" + + ! 创建网格 + call mesh%init(xmin=0.0_real64, xmax=2.0_real64, ncells=20) + call mesh%print_info() + print *, "" + + ! 创建求解器 + solver = solver_create(config, mesh) + print *, "✓ 求解器创建成功" + print *, " 状态: ", solver%get_state() + print *, "" + + ! 测试2: 求解器初始化 + print *, "2. 测试求解器初始化..." + print *, "------------------------" + + call solver%initialize() + print *, "✓ 求解器初始化完成" + print *, " 状态: ", solver%get_state() + print *, " 错误信息: '", trim(solver%get_error()), "'" + print *, "" + + ! 测试3: 简单运行 + print *, "3. 测试求解器运行..." + print *, "----------------------" + + call solver_run(solver, 0.05_real64) ! 运行到0.05秒 + print *, "✓ 求解器运行完成" + print *, " 最终状态: ", solver%get_state() + print *, "" + + ! 测试4: 清理 + print *, "4. 测试求解器清理..." + print *, "----------------------" + + call solver_cleanup(solver) + print *, "✓ 求解器清理完成" + print *, " 状态: ", solver%get_state() + print *, "" + + ! 测试5: 错误处理 + print *, "5. 测试错误处理..." + print *, "-------------------" + + ! 尝试重复初始化 + call solver%initialize() + print *, " 重复初始化状态: ", solver%get_state() + print *, " 错误信息: '", trim(solver%get_error()), "'" + + call solver_cleanup(solver) + print *, "" + + print *, "=== 框架测试总结 ===" + print *, "✓ 求解器创建/初始化/运行/清理流程验证完成" + print *, "✓ 状态管理正常工作" + print *, "✓ 错误处理机制就绪" + print *, "" + print *, "下一步: 添加实际数值计算功能" + +end program test_solver_framework \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03e/CMakeLists.txt new file mode 100644 index 000000000..55859dc2a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) +add_subdirectory(examples) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/README.md b/example/1d-linear-convection/weno3/fortran/registry/03e/README.md new file mode 100644 index 000000000..c4889757b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/README.md @@ -0,0 +1,221 @@ +# Fortran CFD Project + +基于工厂模式和注册系统的CFD求解器框架。 + +## 项目结构 + +``` +fortran_cfd/ +├── CMakeLists.txt # 根CMake配置 +├── scripts/ # 构建脚本 +│ ├── build.py # Python构建脚本(推荐) +│ ├── build.bat # Batch包装脚本 +│ ├── build.ps1 # PowerShell包装脚本 +│ └── requirements.txt # Python依赖 +├── src/ # 源代码 +│ ├── CMakeLists.txt +│ ├── core/ # 核心模块(注册系统、工厂接口) +│ ├── infrastructure/ # 基础设施(配置、网格) +│ └── numerics/ # 数值方法 +├── tests/ # 测试 +│ ├── CMakeLists.txt +│ ├── test_minimal_simple.f90 # 简单测试 +│ └── test_factory.f90 # 工厂模式测试 +└── README.md # 项目说明(本文件) +``` + +## 快速开始 + +### 使用Python脚本(推荐) + +```bash +# 进入脚本目录 +cd scripts + +# 基本构建 +python build.py + +# 清理并构建 +python build.py --clean + +# 发布构建 +python build.py --build-type Release --clean + +# 并行构建(使用所有核心) +python build.py -j + +# 不运行测试 +python build.py --no-tests + +# 查看帮助 +python build.py --help +``` + +### 使用批处理脚本 + +```bash +cd scripts +.\build.bat +``` + +### 使用PowerShell脚本 + +```powershell +cd scripts +.\build.ps1 +``` + +## 手动构建 + +```bash +# 设置Intel环境 +cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && powershell' + +# 配置项目 +mkdir build +cd build +cmake -G "Visual Studio 17 2022" -A x64 -T fortran=ifx .. + +# 构建项目 +cmake --build . --config Debug + +# 运行测试 +Debug\test_simple.exe +Debug\test_factory.exe +``` + +## 功能特性 + +✅ 组件注册系统 +✅ 工厂模式 +✅ ENO/WENO重构器 +✅ Rusanov通量计算器 +✅ 可扩展架构 +✅ 自动化测试 + +## 依赖 + +- Intel oneAPI(包含ifx编译器) +- CMake 3.12+ +- Python 3.6+(仅用于构建脚本) + +## 源代码文件 + +### 核心模块 +- `src/core/registry.f90` - 组件注册系统 +- `src/core/factory_interfaces.f90` - 工厂接口 + +### 基础设施 +- `src/infrastructure/config.f90` - 配置管理 +- `src/infrastructure/mesh.f90` - 网格生成 + +### 数值方法 +- `src/numerics/reconstructor/base.f90` - 重构器基类 +- `src/numerics/reconstructor/eno.f90` - ENO重构器 +- `src/numerics/reconstructor/weno3.f90` - WENO-3重构器 +- `src/numerics/flux/base.f90` - 通量计算器基类 +- `src/numerics/flux/rusanov.f90` - Rusanov通量 + +### 测试 +- `tests/test_minimal_simple.f90` - 简单功能测试 +- `tests/test_factory.f90` - 工厂模式测试 + +## 构建脚本 + +### Python脚本 (build.py) +主构建脚本,支持: +- 自动设置Intel oneAPI环境 +- 参数化构建选项 +- 并行编译 +- 自动化测试 +- 彩色输出 + +### Batch脚本 (build.bat) +Windows批处理包装器,调用Python脚本。 + +### PowerShell脚本 (build.ps1) +PowerShell包装器,调用Python脚本。 + +## 示例输出 + +成功构建后,会看到类似输出: + +``` +============================================================ + Fortran CFD Project Builder +============================================================ + +[1/4] Setting up Intel Fortran compiler... +✓ Intel oneAPI environment configured + +[2/4] Preparing build directory... +✓ Cleaned build directory + +[3/4] Configuring project... + $ cmake -G Visual Studio 17 2022 -A x64 -T fortran=ifx -DCMAKE_BUILD_TYPE=Debug .. +✓ CMake configuration successful + +[4/4] Building project... + $ cmake --build . --config Debug +✓ Build completed in 12.3 seconds + +============================================================ + Running Tests +============================================================ + +[TEST] Simple functionality test... +✓ Simple test passed + +[TEST] Factory pattern test... +✓ Factory test passed + +============================================================ + Build Summary +============================================================ +✓ Build directory: D:\path\to\build +✓ Build type: Debug +✓ Total time: 15.2s +``` + +## 开发指南 + +### 添加新组件 + +1. 在相应模块中创建Fortran源文件 +2. 实现工厂函数 +3. 使用`register_component_with_factory`注册组件 +4. 添加测试 + +### 扩展架构 + +项目采用模块化设计: +1. **注册系统** - 管理所有可用的组件 +2. **工厂模式** - 动态创建组件实例 +3. **抽象接口** - 确保组件兼容性 +4. **配置驱动** - 通过配置文件控制行为 + +## 故障排除 + +### 常见问题 + +1. **Intel环境未找到** + - 检查Intel oneAPI安装路径 + - 手动运行`setvars.bat` + +2. **CMake配置失败** + - 确保使用正确的生成器:`Visual Studio 17 2022` + - 检查Fortran编译器是否可用 + +3. **构建失败** + - 检查依赖项是否完整 + - 查看详细的错误信息 + +### 调试建议 + +- 使用`--verbose`参数获取详细输出 +- 检查`build/CMakeCache.txt`了解配置详情 +- 查看编译器错误日志 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/examples/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03e/examples/CMakeLists.txt new file mode 100644 index 000000000..389925bbe --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/examples/CMakeLists.txt @@ -0,0 +1,23 @@ +# examples/CMakeLists.txt +message(STATUS "配置示例程序...") + +# 主示例程序:ENO/WENO对比 +add_executable(run_eno_weno + run_eno_weno.f90 +) + +target_link_libraries(run_eno_weno + PRIVATE + solver + infrastructure + core + physics + manager + results # ← 新增链接结果模块 +) + +install(TARGETS run_eno_weno + RUNTIME DESTINATION bin/examples +) + +message(STATUS "示例程序配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/examples/run_eno_weno.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/examples/run_eno_weno.f90 new file mode 100644 index 000000000..ffa6ff9d0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/examples/run_eno_weno.f90 @@ -0,0 +1,327 @@ +! examples/run_eno_weno.f90 (修正版) +program run_eno_weno + ! Example program: ENO/WENO comparison analysis + + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction, config_print + use mesh_module, only: mesh_type + use registry_module, only: registry_init, registry_cleanup, initialize_default_components + use physics_solver_module, only: physics_solver, SOLVER_COMPLETED + use results_module, only: results_saver, save_results ! 修改:只导入需要的内容 + + implicit none + + type(cfd_config) :: config_eno3, config_weno3, config_weno5 + type(mesh_type) :: mesh + type(physics_solver) :: solver_eno3, solver_weno3, solver_weno5 + + ! 结果保存器 + type(results_saver) :: saver ! 直接声明类型 + + ! Variables to save results + real(wp) :: time_eno3, time_weno3, time_weno5 + integer :: steps_eno3, steps_weno3, steps_weno5 + integer :: state_eno3, state_weno3, state_weno5 + logical :: all_success + + ! Debug: print start marker + print *, "==========================================" + print *, "START: ENO/WENO Comparison Analysis" + print *, "==========================================" + print *, "" + + ! Step 0: Initialize system + print *, "[STEP 0] Initializing system..." + print *, "--------------------------------" + + call registry_init(verbose=.true.) + print *, "Registry initialized" + + call initialize_default_components() + print *, "Default components registered" + print *, "" + + ! Step 1: Create mesh + print *, "[STEP 1] Creating computational mesh..." + print *, "----------------------------------------" + + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=40) + call mesh%print_info() + print *, "" + + ! 创建结果保存器 - 使用构造函数而不是赋值 + saver = results_saver("results", .true.) ! 直接使用构造函数 + + ! ========== ENO3 Solver ========== + print *, "[STEP 2] Configuring and running ENO3 solver..." + print *, "-----------------------------------------------" + + ! Configure ENO3 + config_eno3%verbose = .true. + config_eno3%ic_type = "step" + config_eno3%wave_speed = 1.0_wp + config_eno3%final_time = 0.625_wp + config_eno3%dt = 0.0025_wp + config_eno3%rk_order = 2 + config_eno3%boundary_type = "periodic" + config_eno3%equation_type = "linear_advection" + config_eno3%problem_type = "linear_advection" + config_eno3%enable_physics = .true. + config_eno3%domain_length = 2.0_wp + + call config_with_reconstruction(config_eno3, "eno", 3) + + print *, "ENO3 configuration:" + call config_print(config_eno3) + print *, "" + + ! Create and run ENO3 solver + print *, "Creating ENO3 solver instance..." + ! 替换这三行: + ! call physics_solver_constructor(solver_eno3, config_eno3, mesh) + + ! 改为: + solver_eno3 = physics_solver(config_eno3, mesh) + + print *, "Initializing ENO3 solver..." + call solver_eno3%initialize() + + print *, "Running ENO3 solver..." + call solver_eno3%run_to_time(config_eno3%final_time) + + ! Immediately save ENO3 results + time_eno3 = solver_eno3%current_time + steps_eno3 = solver_eno3%current_step + state_eno3 = solver_eno3%get_state() + + print *, "ENO3 solver completed" + print *, " Final time: ", time_eno3 + print *, " Total steps: ", steps_eno3 + print *, " State: ", state_eno3 + print *, "" + + ! 保存ENO3结果到文件 + call save_results(saver, "ENO3", & + solver_eno3%config, solver_eno3%mesh, solver_eno3%domain, & + solver_eno3%solution, & + solver_eno3%current_time, solver_eno3%current_step, solver_eno3%get_state()) + + ! ========== WENO3 Solver ========== + print *, "[STEP 3] Configuring and running WENO3 solver..." + print *, "------------------------------------------------" + + ! Configure WENO3 + config_weno3%verbose = .true. + config_weno3%ic_type = "step" + config_weno3%wave_speed = 1.0_wp + config_weno3%final_time = 0.625_wp + config_weno3%dt = 0.0025_wp + config_weno3%rk_order = 2 + config_weno3%boundary_type = "periodic" + config_weno3%equation_type = "linear_advection" + config_weno3%problem_type = "linear_advection" + config_weno3%enable_physics = .true. + config_weno3%domain_length = 2.0_wp + + call config_with_reconstruction(config_weno3, "weno3", 3) + + print *, "WENO3 configuration:" + call config_print(config_weno3) + print *, "" + + ! Create and run WENO3 solver + print *, "Creating WENO3 solver instance..." + call physics_solver_constructor(solver_weno3, config_weno3, mesh) + + print *, "Initializing WENO3 solver..." + call solver_weno3%initialize() + + print *, "Running WENO3 solver..." + call solver_weno3%run_to_time(config_weno3%final_time) + + ! Immediately save WENO3 results + time_weno3 = solver_weno3%current_time + steps_weno3 = solver_weno3%current_step + state_weno3 = solver_weno3%get_state() + + print *, "WENO3 solver completed" + print *, " Final time: ", time_weno3 + print *, " Total steps: ", steps_weno3 + print *, " State: ", state_weno3 + print *, "" + + ! 保存WENO3结果到文件 + call save_results(saver, "WENO3", & + solver_weno3%config, solver_weno3%mesh, solver_weno3%domain, & + solver_weno3%solution, time_weno3, steps_weno3, state_weno3) + + ! ========== WENO5 Solver ========== + print *, "[STEP 4] Configuring and running WENO5 solver..." + print *, "------------------------------------------------" + + ! Configure WENO5 + config_weno5%verbose = .true. + config_weno5%ic_type = "step" + config_weno5%wave_speed = 1.0_wp + config_weno5%final_time = 0.625_wp + config_weno5%dt = 0.0025_wp + config_weno5%rk_order = 2 + config_weno5%boundary_type = "periodic" + config_weno5%equation_type = "linear_advection" + config_weno5%problem_type = "linear_advection" + config_weno5%enable_physics = .true. + config_weno5%domain_length = 2.0_wp + + call config_with_reconstruction(config_weno5, "weno", 5) + + print *, "WENO5 configuration:" + call config_print(config_weno5) + print *, "" + + ! Create and run WENO5 solver + print *, "Creating WENO5 solver instance..." + call physics_solver_constructor(solver_weno5, config_weno5, mesh) + + print *, "Initializing WENO5 solver..." + call solver_weno5%initialize() + + print *, "Running WENO5 solver..." + call solver_weno5%run_to_time(config_weno5%final_time) + + ! Immediately save WENO5 results + time_weno5 = solver_weno5%current_time + steps_weno5 = solver_weno5%current_step + state_weno5 = solver_weno5%get_state() + + print *, "WENO5 solver completed" + print *, " Final time: ", time_weno5 + print *, " Total steps: ", steps_weno5 + print *, " State: ", state_weno5 + print *, "" + + ! 保存WENO5结果到文件 + call save_results(saver, "WENO5", & + solver_weno5%config, solver_weno5%mesh, solver_weno5%domain, & + solver_weno5%solution, time_weno5, steps_weno5, state_weno5) + + ! ========== Results Summary ========== + print *, "==========================================" + print *, " RESULTS SUMMARY" + print *, "==========================================" + print *, "" + + print *, "Solver Performance Comparison:" + print *, "------------------------------" + + print *, "ENO3:" + print *, " Final time: ", time_eno3 + print *, " Total steps: ", steps_eno3 + print *, " State: ", state_eno3 + print *, " Results saved to: results_ENO3_40.dat" + print *, "" + + print *, "WENO3:" + print *, " Final time: ", time_weno3 + print *, " Total steps: ", steps_weno3 + print *, " State: ", state_weno3 + print *, " Results saved to: results_WENO3_40.dat" + print *, "" + + print *, "WENO5:" + print *, " Final time: ", time_weno5 + print *, " Total steps: ", steps_weno5 + print *, " State: ", state_weno5 + print *, " Results saved to: results_WENO5_40.dat" + print *, "" + + ! ========== Final Judgment ========== + print *, "==========================================" + print *, " FINAL JUDGMENT" + print *, "==========================================" + print *, "" + + all_success = (state_eno3 == SOLVER_COMPLETED) .and. & + (state_weno3 == SOLVER_COMPLETED) .and. & + (state_weno5 == SOLVER_COMPLETED) + + if (all_success) then + print *, "✓ ALL SOLVERS SUCCESSFULLY COMPLETED!" + print *, "" + print *, "Parameter Summary:" + print *, " Grid cells: ", mesh%ncells + print *, " Time step: ", config_eno3%dt + print *, " Final time: ", config_eno3%final_time + print *, " Wave speed: ", config_eno3%wave_speed + print *, " IC type: ", trim(config_eno3%ic_type) + print *, "" + print *, "Performance Comparison:" + print *, " ENO3: ", steps_eno3, " steps" + print *, " WENO3: ", steps_weno3, " steps" + print *, " WENO5: ", steps_weno5, " steps" + print *, "" + print *, "To visualize results:" + print *, " python ../python/plot_results.py --auto" + else + print *, "✗ SOME SOLVERS FAILED" + print *, "" + print *, "Failure Analysis:" + if (state_eno3 /= SOLVER_COMPLETED) then + print *, " • ENO3 failed with state: ", state_eno3 + end if + if (state_weno3 /= SOLVER_COMPLETED) then + print *, " • WENO3 failed with state: ", state_weno3 + end if + if (state_weno5 /= SOLVER_COMPLETED) then + print *, " • WENO5 failed with state: ", state_weno5 + end if + end if + + print *, "" + print *, "==========================================" + print *, " ANALYSIS COMPLETE" + print *, "==========================================" + + ! ========== Cleanup ========== + print *, "" + print *, "[STEP 5] Cleaning up system..." + print *, "--------------------------------" + + call solver_eno3%cleanup() + call solver_weno3%cleanup() + call solver_weno5%cleanup() + call registry_cleanup() + + print *, "All solvers cleaned up" + print *, "Registry cleaned up" + print *, "" + + ! ========== Wait for user input before exit ========== + print *, "==========================================" + print *, "Press ENTER to exit..." + print *, "==========================================" + + ! Wait for user input (uncomment if needed) + ! read(*,*) + + ! Alternative: add a small delay + print *, "Program will exit in 3 seconds..." + call sleep(3) + + print *, "" + print *, "==========================================" + print *, " PROGRAM END" + print *, "==========================================" + +contains + + ! 辅助函数:显式创建physics_solver(如果原始代码使用函数而不是子程序) + subroutine physics_solver_constructor(solver, config, mesh) + type(physics_solver), intent(out) :: solver + type(cfd_config), intent(in) :: config + type(mesh_type), intent(in) :: mesh + + ! 使用赋值构造函数(如果physics_solver_module中有相应的接口) + solver = physics_solver(config, mesh) + end subroutine physics_solver_constructor + +end program run_eno_weno \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/python/plot_results.py b/example/1d-linear-convection/weno3/fortran/registry/03e/python/plot_results.py new file mode 100644 index 000000000..cf8e40175 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/python/plot_results.py @@ -0,0 +1,419 @@ +#!/usr/bin/env python3 +# python/plot_results.py +""" +Fortran CFD 结果可视化脚本 +与Julia的plotter.jl功能类似,但直接读取Fortran生成的文本文件 +""" + +import numpy as np +import matplotlib.pyplot as plt +import os +import sys +import glob +from pathlib import Path +import re + +class FortranResultsPlotter: + """Fortran结果绘图器""" + + def __init__(self, style='default'): + self.style = style + self.setup_styles() + + def setup_styles(self): + """设置绘图样式""" + if self.style == 'default': + self.styles = { + 'numerical': { + 'color': 'blue', + 'linestyle': '-', + 'marker': 'o', + 'markerfacecolor': 'none', + 'markersize': 4, + 'linewidth': 1 + }, + 'analytical': { + 'color': 'red', + 'linestyle': '--', + 'marker': '', + 'linewidth': 1.5 + }, + 'comparison': [ + {'color': 'black', 'linestyle': '-', 'marker': 'o', 'markerfacecolor': 'none'}, + {'color': 'blue', 'linestyle': '--', 'marker': 's', 'markerfacecolor': 'none'}, + {'color': 'green', 'linestyle': ':', 'marker': '^', 'markerfacecolor': 'none'} + ] + } + + def read_fortran_results(self, filename): + """读取Fortran生成的结果文件""" + data = {} + + try: + with open(filename, 'r', encoding='utf-8', errors='ignore') as f: + lines = f.readlines() + + data_lines = [] + in_data_section = False # 新状态:是否在数据区域 + + for line in lines: + line = line.strip() + if not line: + continue + + # 跳过所有分隔线 + if line.startswith("===="): + continue + + # 检测数据区域开始 + if line == "DATA: x, numerical, analytical": + in_data_section = True + continue + + if not in_data_section: + # 解析头部信息 + if "Solver:" in line: + data['solver'] = line.split(":", 1)[1].strip() + elif "Scheme:" in line: + data['scheme'] = line.split(":", 1)[1].strip() + elif "Order:" in line: + if "RK Order:" in line: + data['rk_order'] = int(line.split(":", 1)[1].strip()) + else: + data['order'] = int(line.split(":", 1)[1].strip()) + elif "Current Time:" in line: + data['time'] = float(line.split(":", 1)[1].strip()) + elif "Grid Points:" in line: + data['n_points'] = int(line.split(":", 1)[1].strip()) + else: + # 解析数据行 + parts = line.split() + if len(parts) >= 3: + try: + x = float(parts[0]) + numerical = float(parts[1]) + analytical = float(parts[2]) + data_lines.append([x, numerical, analytical]) + except ValueError: + continue # 忽略无法解析的行 + + if data_lines: + import numpy as np + data_array = np.array(data_lines) + data['x'] = data_array[:, 0] + data['numerical'] = data_array[:, 1] + data['analytical'] = data_array[:, 2] + + print(f"Read {len(data['x'])} points from {filename}") + print(f" Solver: {data.get('solver', 'N/A')}") + print(f" Scheme: {data.get('scheme', 'N/A')} order {data.get('order', 'N/A')}") + print(f" Time: {data.get('time', 'N/A')}") + + return data + else: + print(f"Warning: No data found in {filename}") + return None + + except Exception as e: + print(f"Error reading {filename}: {e}") + return None + + def plot_single_result(self, filename, title=None, show=True, save_path=None): + """绘制单个结果文件""" + data = self.read_fortran_results(filename) + if data is None: + return False + + fig, ax = plt.subplots(figsize=(10, 6)) + + # 自动生成标题 + if title is None: + title = f"1D Convection (t={data['time']:.3f})\n" + title += f"{data['order']}th-order {data['scheme'].upper()} + {data['rk_order']}nd-order RK" + + # 绘制数值解 + ax.plot(data['x'], data['numerical'], + label=f"Numerical ({data['scheme'].upper()}{data['order']})", + **self.styles['numerical']) + + # 绘制解析解 + ax.plot(data['x'], data['analytical'], + label="Analytical", + **self.styles['analytical']) + + # 设置图形属性 + ax.set_title(title, fontsize=12) + ax.set_xlabel("x", fontsize=10) + ax.set_ylabel("u", fontsize=10) + ax.legend(fontsize=9) + ax.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + + # 保存或显示 + if save_path: + plt.savefig(save_path, dpi=150, bbox_inches='tight') + print(f"Saved plot to {save_path}") + + if show: + plt.show() + else: + plt.close() + + return True + + def format_scheme_label(self, scheme: str, order: int) -> str: + s = scheme.upper() + if s.startswith("WENO"): + return f"WENO{order}" + else: + return f"{s}{order}" + + def plot_comparison(self, filenames, labels=None, title=None, show=True, save_path=None): + """比较多个结果文件""" + all_data = [] + print(f"plot_comparison filenames={filenames}") + + # 读取所有文件 + for filename in filenames: + print(f"plot_comparison filename={filename}") + data = self.read_fortran_results(filename) + if data is not None: + all_data.append(data) + + if not all_data: + print("No valid data to plot") + return False + + fig, axes = plt.subplots(2, 2, figsize=(14, 10)) + + # 子图1:所有方法比较 + for i, data in enumerate(all_data): + #print(f"i,all_data={i,all_data}") + if labels and i < len(labels): + label = labels[i] + else: + label = f"{data['scheme'].upper()}{data['order']}" + + style_idx = i % len(self.styles['comparison']) + style = self.styles['comparison'][style_idx] + + axes[0, 0].plot(data['x'], data['numerical'], + label=label, **style) + + axes[0, 0].plot(all_data[0]['x'], all_data[0]['analytical'], + label="Analytical", **self.styles['analytical']) + + axes[0, 0].set_xlabel('x', fontsize=12) + axes[0, 0].set_ylabel('u(x)', fontsize=12) + axes[0, 0].set_title('Numerical Solutions Comparison', fontsize=14) + axes[0, 0].legend() + axes[0, 0].grid(True, alpha=0.3) + + # 子图2:误差分析 + for i, data in enumerate(all_data): + if labels and i < len(labels): + label = labels[i] + else: + label = f"{data['scheme'].upper()}{data['order']}" + + error = np.abs(data['numerical'] - data['analytical']) + axes[0, 1].semilogy(data['x'], error, label=label) + + axes[0, 1].set_xlabel('x', fontsize=12) + axes[0, 1].set_ylabel('Absolute Error', fontsize=12) + axes[0, 1].set_title('Error Comparison', fontsize=14) + axes[0, 1].legend() + axes[0, 1].grid(True, alpha=0.3) + + # 子图3:数值解细节(放大) + x_min, x_max = all_data[0]['x'].min(), all_data[0]['x'].max() + zoom_center = 1.0 # 阶跃函数位置附近 + zoom_width = 0.3 + + for i, data in enumerate(all_data): + if labels and i < len(labels): + label = labels[i] + else: + label = f"{data['scheme'].upper()}{data['order']}" + + style_idx = i % len(self.styles['comparison']) + style = self.styles['comparison'][style_idx] + + axes[1, 0].plot(data['x'], data['numerical'], label=label, **style) + + axes[1, 0].plot(all_data[0]['x'], all_data[0]['analytical'], + label="Analytical", **self.styles['analytical']) + + axes[1, 0].set_xlim(zoom_center - zoom_width/2, zoom_center + zoom_width/2) + axes[1, 0].set_xlabel('x', fontsize=12) + axes[1, 0].set_ylabel('u(x)', fontsize=12) + axes[1, 0].set_title('Zoomed View (x ≈ 1.0)', fontsize=14) + axes[1, 0].legend() + axes[1, 0].grid(True, alpha=0.3) + + # 子图4:性能统计 + axes[1, 1].axis('off') + + # 计算并显示L2误差 + errors = [] + schemes = [] + + for data in all_data: + error = np.sqrt(np.mean((data['numerical'] - data['analytical'])**2)) + errors.append(error) + schemes.append(f"{data['scheme'].upper()}{data['order']}") + + # 创建表格数据 + table_data = [] + for i, (scheme, error) in enumerate(zip(schemes, errors)): + table_data.append([scheme, f"{error:.2e}"]) + + # 在子图中显示表格 + table = axes[1, 1].table(cellText=table_data, + colLabels=['Scheme', 'L2 Error'], + loc='center', + cellLoc='center', + colWidths=[0.3, 0.4]) + + table.auto_set_font_size(False) + table.set_fontsize(10) + table.scale(1, 1.5) + + axes[1, 1].set_title('Performance Summary (L2 Error)', fontsize=14) + + # 设置总标题 + if title is None: + time = all_data[0]['time'] + schemes_str = ", ".join([self.format_scheme_label(d['scheme'], d['order']) for d in all_data]) + title = f"1D Convection Comparison (t={time:.3f})\n{schemes_str}" + + fig.suptitle(title, fontsize=16) + plt.tight_layout() + + # 保存或显示 + if save_path: + plt.savefig(save_path, dpi=150, bbox_inches='tight') + print(f"Saved comparison plot to {save_path}") + + if show: + plt.show() + else: + plt.close() + + return True + + def get_scheme_from_filename(self, filename): + print(f"filename={filename}") + stem = Path(filename).stem # e.g., "results_ENO3_40" + parts = stem.split('_') + if len(parts) >= 2: + return parts[1] # "ENO3", "WENO3", "WENO5" + return "" + + def plot_eno_weno_comparison(self, result_dir=".", save_path="eno_weno_comparison.png"): + """自动绘制ENO/WENO对比图(类似Julia的功能)""" + # 查找结果文件 + pattern = os.path.join(result_dir, "results_*.dat") + files = glob.glob(pattern) + + if not files: + print(f"No result files found matching {pattern}") + return False + + # 重新分类 + file_info = [] + eno_files = [] + weno3_files = [] + weno5_files = [] + for f in files: + print(f"f={f}") + print(f"type(f)={type(f)}") + scheme = self.get_scheme_from_filename(f) + print(f"scheme={scheme}") + if scheme == "ENO3": + eno_files.append(f) + elif scheme == "WENO3": + weno3_files.append(f) + elif scheme == "WENO5": + weno5_files.append(f) + + # 按求解器类型分类 + print(f"eno_files={eno_files}") + print(f"weno3_files={weno3_files}") + print(f"weno5_files={weno5_files}") + + # 选择最新的文件(如果有多组) + files_to_plot = [] + labels = [] + + if eno_files: + files_to_plot.append(sorted(eno_files)[-1]) + labels.append("ENO3") + + if weno3_files: + files_to_plot.append(sorted(weno3_files)[-1]) + labels.append("WENO3") + + if weno5_files: + files_to_plot.append(sorted(weno5_files)[-1]) + labels.append("WENO5") + + if len(files_to_plot) >= 2: + print(f"Plotting comparison of {len(files_to_plot)} solvers") + return self.plot_comparison(files_to_plot, labels=labels, + save_path=save_path, show=True) + else: + print("Not enough different solvers for comparison") + return False + +def main(): + """主函数""" + import argparse + + parser = argparse.ArgumentParser(description='Fortran CFD Results Visualizer') + parser.add_argument('--file', help='Plot single result file') + parser.add_argument('--compare', nargs='+', help='Compare multiple files') + parser.add_argument('--dir', default='.', help='Directory containing result files') + parser.add_argument('--auto', action='store_true', help='Auto plot ENO/WENO comparison') + parser.add_argument('--save', help='Save plot to file (without showing)') + parser.add_argument('--no-show', action='store_true', help='Don\'t show plot') + + args = parser.parse_args() + + plotter = FortranResultsPlotter() + + if args.file: + # 绘制单个文件 + plotter.plot_single_result(args.file, save_path=args.save, + show=not args.no_show) + + elif args.compare: + # 比较多个文件 + plotter.plot_comparison(args.compare, save_path=args.save, + show=not args.no_show) + + elif args.auto: + # 自动绘制比较图 + save_path = args.save or "eno_weno_comparison.png" + plotter.plot_eno_weno_comparison(args.dir, save_path=save_path) + + else: + # 默认:显示帮助 + parser.print_help() + + # 也显示可用的结果文件 + print("\nAvailable result files:") + pattern = os.path.join(args.dir, "results_*.dat") + files = glob.glob(pattern) + + if files: + for f in sorted(files): + print(f" {os.path.basename(f)}") + + print(f"\nTo plot ENO/WENO comparison automatically:") + print(f" python plot_results.py --auto") + else: + print(" No result files found") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/scripts/build.bat b/example/1d-linear-convection/weno3/fortran/registry/03e/scripts/build.bat new file mode 100644 index 000000000..6fd6dc031 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/scripts/build.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Project Builder +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python build script with full Intel environment support... +echo. + +python build.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Build failed + pause + exit /b 1 +) + +echo. +echo [INFO] Build completed successfully! +echo. +echo [INFO] To run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/scripts/build.py b/example/1d-linear-convection/weno3/fortran/registry/03e/scripts/build.py new file mode 100644 index 000000000..3bf6d5375 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/scripts/build.py @@ -0,0 +1,629 @@ +#!/usr/bin/env python3 +""" +Fortran CFD Project Builder - 完整Python解决方案 +在Python内部处理Intel oneAPI环境配置 +""" + +import os +import sys +import subprocess +import shutil +import argparse +import time +import platform +import tempfile +from pathlib import Path + +class IntelEnvironment: + """Intel oneAPI环境管理器""" + + def __init__(self): + self.setvars_path = None + self.env_vars = {} + + def find_setvars(self): + """查找setvars.bat文件""" + possible_paths = [ + r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat", + r"C:\Program Files\Intel\oneAPI\setvars.bat", + r"C:\Program Files (x86)\Intel\oneAPI\compiler\latest\env\vars.bat", + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\setvars.bat"), + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\compiler\latest\env\vars.bat"), + ] + + for path in possible_paths: + if os.path.exists(path): + self.setvars_path = path + return True + + return False + + def setup_environment(self): + """设置Intel环境""" + if not self.find_setvars(): + return False + + try: + # 创建临时的批处理文件来捕获环境变量 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + f.write(f'@echo off\n') + f.write(f'call "{self.setvars_path}" >nul 2>&1\n') + f.write(f'set\n') # 输出所有环境变量 + temp_bat = f.name + + # 运行批处理文件并捕获输出 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + shell=True + ) + + # 解析环境变量 + for line in result.stdout.split('\n'): + line = line.strip() + if '=' in line: + key, value = line.split('=', 1) + self.env_vars[key.strip()] = value.strip() + + # 清理临时文件 + os.unlink(temp_bat) + + # 更新当前进程的环境变量 + os.environ.update(self.env_vars) + + return True + + except Exception as e: + print(f"设置Intel环境失败: {e}") + return False + + def get_compiler_info(self): + """获取编译器信息""" + info = {} + + # 检查ifx编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + env={**os.environ, **self.env_vars} if self.env_vars else os.environ + ) + + if result.returncode == 0: + for line in result.stdout.split('\n'): + if 'Version' in line or '版本' in line: + info['ifx_version'] = line.strip() + break + except: + pass + + # 检查环境变量 + info['ifx_root'] = self.env_vars.get('IFX_ROOT', '') + info['compiler_root'] = self.env_vars.get('ONEAPI_ROOT', '') + + return info + +class BuildSystem: + """构建系统主类""" + + def __init__(self): + self.project_root = Path(__file__).parent.parent + self.build_dir = self.project_root / "build" + self.intel_env = IntelEnvironment() + + # 设置控制台编码 + if sys.platform == "win32": + try: + import ctypes + # 设置控制台输出为UTF-8 + ctypes.windll.kernel32.SetConsoleOutputCP(65001) + except: + pass + + def print_header(self, text): + """打印标题""" + print(f"\n{'='*70}") + print(f" {text}") + print(f"{'='*70}\n") + + def print_step(self, step, total, message): + """打印步骤""" + print(f"[{step}/{total}] {message}...") + + def print_success(self, message): + """打印成功""" + print(f"\033[92m✓ {message}\033[0m") + + def print_error(self, message): + """打印错误""" + print(f"\033[91m✗ {message}\033[0m") + + def print_warning(self, message): + """打印警告""" + print(f"\033[93m! {message}\033[0m") + + def print_info(self, message): + """打印信息""" + print(f"\033[94mℹ {message}\033[0m") + + def check_prerequisites(self): + """检查前提条件""" + self.print_step(1, 6, "检查前提条件") + + # 检查Python版本 + python_version = sys.version.split()[0] + self.print_info(f"Python版本: {python_version}") + + # 检查平台 + self.print_info(f"平台: {platform.system()} {platform.release()}") + self.print_info(f"处理器核心数: {os.cpu_count()}") + + # 检查CMake + try: + result = subprocess.run( + ["cmake", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + version_line = result.stdout.split('\n')[0] + self.print_success(f"CMake: {version_line}") + else: + self.print_error("CMake未找到") + return False + except FileNotFoundError: + self.print_error("CMake未安装") + return False + + return True + + def setup_intel_environment(self, args): + """设置Intel环境""" + self.print_step(2, 6, "配置Intel oneAPI环境") + + if not self.intel_env.find_setvars(): + self.print_warning("未找到Intel oneAPI setvars.bat") + self.print_info("将尝试使用系统环境中的编译器") + + # 检查是否能直接访问编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + self.print_success("Intel编译器在系统PATH中找到") + return True + else: + self.print_warning("Intel编译器未在PATH中找到") + except: + self.print_warning("无法访问Intel编译器") + + return True # 继续,让CMake自己找编译器 + + # 设置环境 + if self.intel_env.setup_environment(): + compiler_info = self.intel_env.get_compiler_info() + + if compiler_info.get('ifx_version'): + self.print_success(f"Intel Fortran编译器: {compiler_info['ifx_version']}") + elif compiler_info.get('ifx_root'): + self.print_success(f"Intel编译器路径: {compiler_info['ifx_root']}") + else: + self.print_success("Intel oneAPI环境配置完成") + + return True + else: + self.print_warning("Intel环境配置失败,将继续使用系统环境") + return True + + def clean_build_directory(self, args): + """清理构建目录""" + if args.clean and self.build_dir.exists(): + self.print_info("清理构建目录...") + try: + shutil.rmtree(self.build_dir) + self.print_success("构建目录已清理") + except Exception as e: + self.print_error(f"清理失败: {e}") + if not args.force: + return False + return True + + def run_command(self, cmd, cwd=None, check=True, env=None): + """运行命令""" + if isinstance(cmd, list): + cmd_str = ' '.join(str(c) for c in cmd if c) + else: + cmd_str = str(cmd) + + print(f" \033[96m$\033[0m {cmd_str}") + + try: + # 合并环境变量 + exec_env = os.environ.copy() + if env: + exec_env.update(env) + if self.intel_env.env_vars: + exec_env.update(self.intel_env.env_vars) + + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=False, + env=exec_env + ) + + # 处理输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or '完成' in line or '生成' in line: + print(f" \033[92m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + if check and result.returncode != 0: + self.print_error(f"命令执行失败,退出码: {result.returncode}") + return False + + return True + + except Exception as e: + self.print_error(f"命令执行异常: {e}") + return False + + def configure_cmake(self, args): + """配置CMake""" + self.print_step(3, 6, "配置CMake项目") + + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + f"-DCMAKE_BUILD_TYPE={args.build_type}", + ] + + if args.compiler == "ifx": + cmake_cmd.extend(["-T", "fortran=ifx"]) + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + success = self.run_command(cmake_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("CMake配置完成") + else: + self.print_error("CMake配置失败") + + return success + + def build_project(self, args): + """构建项目""" + self.print_step(4, 6, "构建项目") + + build_cmd = [ + "cmake", + "--build", ".", + "--config", args.build_type, + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + success = self.run_command(build_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("项目构建完成") + else: + self.print_error("构建失败") + + return success + + def run_tests_with_environment(self, test_exe): + """运行单个测试,确保有Intel环境""" + try: + # 创建临时的批处理文件来运行测试 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'"{test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'"{test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + return result + + except Exception as e: + print(f"运行测试失败: {e}") + return None + + def run_tests(self, args): + """运行测试""" + self.print_step(5, 6, "运行测试") + + # 查找测试可执行文件 + test_dir = self.build_dir / "bin" / args.build_type + if not test_dir.exists(): + test_dir = self.build_dir / "bin" + if not test_dir.exists(): + test_dir = self.build_dir + + test_files = list(test_dir.glob("test_*.exe")) + + if not test_files: + self.print_warning("未找到测试程序") + return True + + all_passed = True + + for test_exe in sorted(test_files): + test_name = test_exe.stem + self.print_info(f"运行测试: {test_name}") + print(f" {'-'*50}") + + # 运行测试 + result = self.run_tests_with_environment(str(test_exe)) + + if result is None: + self.print_error(f" {test_name} 运行失败") + all_passed = False + continue + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + print(f" {line}") + + if result.returncode == 0: + self.print_success(f" {test_name} 通过") + else: + self.print_error(f" {test_name} 失败 (退出码: {result.returncode})") + all_passed = False + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + print() # 空行 + + return all_passed + + def create_test_runner(self, args): + """创建独立的测试运行器""" + self.print_step(6, 6, "创建测试运行器") + + runner_path = self.build_dir / "run_tests.bat" + + content = f'''@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Test Runner +echo ======================================== +echo. + +REM Setup Intel oneAPI environment +set "SETVARS_PATH={self.intel_env.setvars_path or ''}" +if exist "%SETVARS_PATH%" ( + call "%SETVARS_PATH%" >nul + echo [INFO] Intel environment configured +) else ( + echo [WARNING] Intel environment not found + echo [WARNING] Tests may fail without runtime libraries +) + +echo. + +REM Run all test executables +set "TEST_COUNT=0" +set "PASS_COUNT=0" + +for %%f in ("bin\\{args.build_type}\\test_*.exe") do ( + set /a TEST_COUNT+=1 + echo [TEST %%f] + echo {'-'*50} + + %%f + if errorlevel 1 ( + echo [FAILED] %%f + ) else ( + echo [PASSED] %%f + set /a PASS_COUNT+=1 + ) + echo. +) + +echo ======================================== +echo Tests: %PASS_COUNT%/%TEST_COUNT% passed +if %PASS_COUNT% equ %TEST_COUNT% ( + echo [SUCCESS] All tests passed! +) else ( + echo [FAILURE] Some tests failed +) +echo ======================================== + +pause +''' + + with open(runner_path, 'w', encoding='utf-8') as f: + f.write(content) + + self.print_success(f"测试运行器已创建: {runner_path}") + self.print_info(f"使用方法: cd build && run_tests.bat") + + return runner_path + + def generate_report(self, args, build_time, tests_passed): + """生成构建报告""" + self.print_header("构建完成") + + print(f"项目: {self.project_root.name}") + print(f"构建类型: {args.build_type}") + print(f"编译器: {args.compiler}") + print(f"并行作业: {args.jobs}") + print(f"总耗时: {build_time:.1f}秒") + print(f"测试结果: {'全部通过' if tests_passed else '有失败'}") + + # 显示生成的可执行文件 + bin_dir = self.build_dir / "bin" / args.build_type + if bin_dir.exists(): + print(f"\n生成的可执行文件:") + for exe in sorted(bin_dir.glob("*.exe")): + size_mb = exe.stat().st_size / (1024 * 1024) + print(f" • {exe.name} ({size_mb:.2f} MB)") + + # 显示测试运行器信息 + runner_path = self.build_dir / "run_tests.bat" + if runner_path.exists(): + print(f"\n独立测试运行器:") + print(f" • {runner_path.name}") + print(f" 在Intel oneAPI环境中运行所有测试") + + def run(self): + """运行构建系统""" + parser = argparse.ArgumentParser( + description="Fortran CFD项目构建工具", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认构建 + %(prog)s --clean # 清理后构建 + %(prog)s --build-type Release # Release构建 + %(prog)s --no-tests # 只构建,不运行测试 + %(prog)s -j8 --verbose # 8线程并行构建,详细输出 + """ + ) + + parser.add_argument("--build-type", choices=["Debug", "Release"], + default="Debug", help="构建类型") + parser.add_argument("--compiler", choices=["ifx", "ifort"], + default="ifx", help="Fortran编译器") + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-tests", action="store_true", + help="跳过测试") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + + args = parser.parse_args() + + # 开始构建 + start_time = time.time() + + self.print_header("Fortran CFD 项目构建系统") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 检查前提条件 + if not self.check_prerequisites(): + if not args.force: + return 1 + + # 2. 设置Intel环境 + if not self.setup_intel_environment(args): + if not args.force: + return 1 + + # 3. 清理目录 + if not self.clean_build_directory(args): + if not args.force: + return 1 + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 4. 配置CMake + if not self.configure_cmake(args): + if not args.force: + return 1 + + # 5. 构建项目 + if not self.build_project(args): + if not args.force: + return 1 + + # 6. 运行测试和创建测试运行器 + tests_passed = True + if not args.no_tests: + tests_passed = self.run_tests(args) + + # 创建测试运行器 + self.create_test_runner(args) # 传递 args 参数 + + # 7. 生成报告 + build_time = time.time() - start_time + self.generate_report(args, build_time, tests_passed) + + return 0 if tests_passed else 1 + + except KeyboardInterrupt: + self.print_error("\n构建被用户中断") + return 1 + except Exception as e: + self.print_error(f"构建过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + builder = BuildSystem() + return builder.run() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/scripts/requirements.txt b/example/1d-linear-convection/weno3/fortran/registry/03e/scripts/requirements.txt new file mode 100644 index 000000000..d21a12b45 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/scripts/requirements.txt @@ -0,0 +1,2 @@ +# 构建脚本的Python依赖(可选) +# 目前没有特殊依赖,保持空文件或添加未来可能需要的包 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/scripts/run_all_steps.bat b/example/1d-linear-convection/weno3/fortran/registry/03e/scripts/run_all_steps.bat new file mode 100644 index 000000000..d506149b2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/scripts/run_all_steps.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo CFD Project: All Steps +echo ======================================== +echo. + +echo [INFO] Starting Step 1: Physics Modules Test... +call run_step1.bat + +if errorlevel 1 ( + echo [ERROR] Step 1 failed + pause + exit /b 1 +) + +echo. +echo [INFO] Starting Step 2: Configuration Physics Update... +call run_step2.bat + +if errorlevel 1 ( + echo [ERROR] Step 2 failed + pause + exit /b 1 +) + +echo. +echo ======================================== +echo All Steps Completed Successfully! +echo ======================================== +echo. +echo [INFO] Next: Update component manager for physics support +echo [INFO] Run: run_step3.bat (to be created) +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/scripts/run_example.py b/example/1d-linear-convection/weno3/fortran/registry/03e/scripts/run_example.py new file mode 100644 index 000000000..d7c199178 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/scripts/run_example.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# scripts/run_example.py +""" +运行ENO/WENO示例程序 +""" + +import os +import sys +import subprocess +from pathlib import Path + +# 添加当前目录到路径 +sys.path.insert(0, str(Path(__file__).parent)) + +try: + from build import BuildSystem +except ImportError: + print("错误: 找不到build.py,请确保在scripts目录中运行") + sys.exit(1) + +def run_example(): + """运行示例程序""" + builder = BuildSystem() + + print("\n" + "="*70) + print(" 运行ENO/WENO对比示例程序") + print("="*70 + "\n") + + # 检查是否已构建 + exe_path = builder.build_dir / "bin" / "Debug" / "example_eno_weno_comparison.exe" + + if not exe_path.exists(): + print("示例程序未构建,先构建项目...") + print("-"*50) + + # 使用简化的构建 + result = subprocess.run( + ["python", "build.py", "--no-tests", "--clean"], + cwd=builder.project_root / "scripts", + capture_output=True, + text=True + ) + + if result.returncode != 0: + print("构建失败:") + print(result.stderr) + return False + + # 运行示例程序 + print("运行示例程序...") + print("-"*50) + + try: + result = subprocess.run( + [str(exe_path)], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace' + ) + + print(result.stdout) + + if result.stderr: + print("标准错误输出:") + print(result.stderr) + + return result.returncode == 0 + + except Exception as e: + print(f"运行示例程序失败: {e}") + return False + +def main(): + """主函数""" + success = run_example() + + if success: + print("\n" + "="*70) + print(" ✓ 示例程序运行成功") + print("="*70) + return 0 + else: + print("\n" + "="*70) + print(" ✗ 示例程序运行失败") + print("="*70) + return 1 + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/scripts/run_step1.bat b/example/1d-linear-convection/weno3/fortran/registry/03e/scripts/run_step1.bat new file mode 100644 index 000000000..0b6b1f17e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/scripts/run_step1.bat @@ -0,0 +1,39 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Step 1: Physics Modules Test +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python step1 script with full Intel environment support... +echo. + +python run_step1.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Step 1 failed + pause + exit /b 1 +) + +echo. +echo [INFO] Step 1 completed successfully! +echo. +echo [INFO] Next step: Update config to include physics settings +echo [INFO] Run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/scripts/run_step1.py b/example/1d-linear-convection/weno3/fortran/registry/03e/scripts/run_step1.py new file mode 100644 index 000000000..5e087a690 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/scripts/run_step1.py @@ -0,0 +1,319 @@ +#!/usr/bin/env python3 +""" +Step 1: Physics Modules Test +扩展build.py,专门用于测试物理模块 +""" + +import os +import sys +import subprocess +import time +from pathlib import Path + +# 添加当前目录到路径,以便导入build.py的类 +sys.path.insert(0, str(Path(__file__).parent)) + +try: + from build import IntelEnvironment, BuildSystem +except ImportError: + print("Error: Cannot import build.py. Make sure build.py is in the same directory.") + sys.exit(1) + +class Step1System(BuildSystem): + """Step 1 测试系统,继承自BuildSystem""" + + def __init__(self): + super().__init__() + self.test_name = "test_physics_minimal" + self.test_exe = None + + def find_test_executable(self): + """查找测试可执行文件""" + possible_paths = [ + self.build_dir / "bin" / "Debug" / f"{self.test_name}.exe", + self.build_dir / "Debug" / f"{self.test_name}.exe", + self.build_dir / f"{self.test_name}.exe", + self.build_dir / "bin" / f"{self.test_name}.exe", + ] + + for path in possible_paths: + if path.exists(): + self.test_exe = path + self.print_success(f"Found test executable: {path}") + return True + + # 如果没有找到,尝试搜索 + self.print_warning(f"Could not find {self.test_name}.exe") + self.print_info("Searching for test executables...") + + try: + result = subprocess.run( + ["dir", str(self.build_dir), "/s", "/b", "*.exe"], + capture_output=True, + text=True, + encoding='utf-8', + shell=True + ) + + if result.returncode == 0: + test_files = [line.strip() for line in result.stdout.split('\n') + if line and 'test_' in line.lower()] + + if test_files: + self.print_info("Found test files:") + for test_file in test_files: + self.print_info(f" {test_file}") + return False + except: + pass + + return False + + def run_test_with_intel_env(self): + """在Intel环境下运行测试""" + if not self.test_exe: + self.print_error("No test executable found") + return False + + self.print_step(1, 2, f"Running test: {self.test_exe.name}") + + try: + # 创建临时的批处理文件来运行测试(包含Intel环境) + import tempfile + + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + # 设置Intel环境 + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'echo [INFO] Intel environment configured\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'echo [WARNING] Intel environment not found\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + self.print_info(f"Command: {temp_bat}") + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower() or 'fail' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or 'pass' in line.lower() or '✓' in line: + print(f" \033[92m{line}\033[0m") + elif '=' in line or '---' in line: + print(f" \033[96m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + self.print_warning("Test stderr output:") + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + self.print_step(2, 2, "Test execution completed") + + if result.returncode == 0: + self.print_success("Test passed") + return True + else: + self.print_error(f"Test failed (exit code: {result.returncode})") + return False + + except Exception as e: + self.print_error(f"Failed to run test: {e}") + return False + + def build_project_if_needed(self, args): + """如果需要,构建项目""" + if args.no_build: + self.print_info("Skipping build (--no-build flag)") + return True + + self.print_step(1, 3, "Building project") + + # 调用父类的构建方法 + build_args = argparse.Namespace() + build_args.clean = args.clean + build_args.build_type = "Debug" + build_args.compiler = "ifx" + build_args.no_tests = True # 不运行所有测试 + build_args.jobs = os.cpu_count() + build_args.verbose = args.verbose + build_args.force = args.force + + # 清理构建目录 + if args.clean and self.build_dir.exists(): + self.print_info("Cleaning build directory...") + import shutil + try: + shutil.rmtree(self.build_dir) + self.print_success("Build directory cleaned") + except Exception as e: + self.print_error(f"Clean failed: {e}") + if not args.force: + return False + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 配置CMake + self.print_step(2, 3, "Configuring CMake") + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + "-DCMAKE_BUILD_TYPE=Debug", + "-T", "fortran=ifx", + ] + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + if not self.run_command(cmake_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + # 构建项目 + self.print_step(3, 3, "Building project") + build_cmd = [ + "cmake", + "--build", ".", + "--config", "Debug", + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + if not self.run_command(build_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + self.print_success("Build completed") + return True + + def run(self): + """运行Step 1测试""" + parser = argparse.ArgumentParser( + description="Step 1: Physics Modules Test", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认运行 + %(prog)s --clean # 清理后构建并测试 + %(prog)s --no-build # 只运行测试,不重新构建 + %(prog)s --verbose # 详细输出 + %(prog)s -j4 # 使用4个并行作业构建 + """ + ) + + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-build", action="store_true", + help="不重新构建,直接运行测试") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + + args = parser.parse_args() + + # 开始测试 + start_time = time.time() + + self.print_header("Step 1: Physics Modules Implementation Test") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 设置Intel环境 + self.print_step(1, 4, "Setting up Intel oneAPI environment") + if not self.setup_intel_environment(args): + if not args.force: + self.print_error("Intel environment setup failed") + return 1 + self.print_warning("Intel environment setup failed, continuing...") + + # 2. 构建项目(如果需要) + self.print_step(2, 4, "Building project if needed") + if not self.build_project_if_needed(args): + if not args.force: + return 1 + + # 3. 查找测试可执行文件 + self.print_step(3, 4, "Finding test executable") + if not self.find_test_executable(): + if not args.force: + return 1 + self.print_warning("Test executable not found, but continuing due to --force") + return 0 + + # 4. 运行测试 + self.print_step(4, 4, "Running physics module test") + test_passed = self.run_test_with_intel_env() + + # 生成报告 + test_time = time.time() - start_time + self.print_header("Step 1 Complete") + + print(f"测试: {'通过 ✓' if test_passed else '失败 ✗'}") + print(f"测试程序: {self.test_exe.name if self.test_exe else '未找到'}") + print(f"总耗时: {test_time:.1f}秒") + + if test_passed: + print(f"\n下一步: 更新配置以包含物理设置") + print(f"建议: 修改config.f90,添加physics相关字段") + return 0 + else: + if args.force: + self.print_warning("测试失败,但由于--force标志继续执行") + return 0 + return 1 + + except KeyboardInterrupt: + self.print_error("\n测试被用户中断") + return 1 + except Exception as e: + self.print_error(f"测试过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + system = Step1System() + return system.run() + +if __name__ == "__main__": + # 需要导入argparse + import argparse + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/scripts/run_step2.bat b/example/1d-linear-convection/weno3/fortran/registry/03e/scripts/run_step2.bat new file mode 100644 index 000000000..9c1f62de4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/scripts/run_step2.bat @@ -0,0 +1,39 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Step 2: Configuration Physics Update +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python step2 script with full Intel environment support... +echo. + +python run_step2.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Step 2 failed + pause + exit /b 1 +) + +echo. +echo [INFO] Step 2 completed successfully! +echo. +echo [INFO] Next step: Update component manager to support physics +echo [INFO] Run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/scripts/run_step2.py b/example/1d-linear-convection/weno3/fortran/registry/03e/scripts/run_step2.py new file mode 100644 index 000000000..c16b76088 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/scripts/run_step2.py @@ -0,0 +1,284 @@ +#!/usr/bin/env python3 +""" +Step 2: Configuration Physics Update +测试配置模块的物理功能更新 +""" + +import os +import sys +import subprocess +import time +from pathlib import Path + +# 添加当前目录到路径,以便导入build.py的类 +sys.path.insert(0, str(Path(__file__).parent)) + +try: + from build import IntelEnvironment, BuildSystem +except ImportError: + print("Error: Cannot import build.py. Make sure build.py is in the same directory.") + sys.exit(1) + +class Step2System(BuildSystem): + """Step 2 测试系统,继承自BuildSystem""" + + def __init__(self): + super().__init__() + self.test_name = "test_config_physics" + self.test_exe = None + + def find_test_executable(self): + """查找测试可执行文件""" + possible_paths = [ + self.build_dir / "bin" / "Debug" / f"{self.test_name}.exe", + self.build_dir / "Debug" / f"{self.test_name}.exe", + self.build_dir / f"{self.test_name}.exe", + self.build_dir / "bin" / f"{self.test_name}.exe", + ] + + for path in possible_paths: + if path.exists(): + self.test_exe = path + self.print_success(f"Found test executable: {path}") + return True + + return False + + def run_test_with_intel_env(self): + """在Intel环境下运行测试""" + if not self.test_exe: + self.print_error("No test executable found") + return False + + self.print_step(1, 2, f"Running test: {self.test_exe.name}") + + try: + # 创建临时的批处理文件来运行测试(包含Intel环境) + import tempfile + + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + # 设置Intel环境 + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'echo [INFO] Intel environment configured\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'echo [WARNING] Intel environment not found\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + self.print_info(f"Command: {temp_bat}") + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower() or 'fail' in line.lower() or '✗' in line: + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or 'pass' in line.lower() or '✓' in line: + print(f" \033[92m{line}\033[0m") + elif '=' in line or '---' in line or '===' in line: + print(f" \033[96m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + self.print_warning("Test stderr output:") + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + self.print_step(2, 2, "Test execution completed") + + if result.returncode == 0: + self.print_success("Test passed") + return True + else: + self.print_error(f"Test failed (exit code: {result.returncode})") + return False + + except Exception as e: + self.print_error(f"Failed to run test: {e}") + return False + + def build_project(self, args): + """构建项目""" + if args.no_build: + self.print_info("Skipping build (--no-build flag)") + return True + + self.print_step(1, 3, "Building project") + + # 清理构建目录 + if args.clean and self.build_dir.exists(): + self.print_info("Cleaning build directory...") + import shutil + try: + shutil.rmtree(self.build_dir) + self.print_success("Build directory cleaned") + except Exception as e: + self.print_error(f"Clean failed: {e}") + if not args.force: + return False + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 配置CMake + self.print_step(2, 3, "Configuring CMake") + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + "-DCMAKE_BUILD_TYPE=Debug", + "-T", "fortran=ifx", + ] + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + if not self.run_command(cmake_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + # 构建项目 + self.print_step(3, 3, "Building project") + build_cmd = [ + "cmake", + "--build", ".", + "--config", "Debug", + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + if not self.run_command(build_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + self.print_success("Build completed") + return True + + def run(self): + """运行Step 2测试""" + import argparse + + parser = argparse.ArgumentParser( + description="Step 2: Configuration Physics Update", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认运行 + %(prog)s --clean # 清理后构建并测试 + %(prog)s --no-build # 只运行测试,不重新构建 + %(prog)s --verbose # 详细输出 + """ + ) + + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-build", action="store_true", + help="不重新构建,直接运行测试") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + + args = parser.parse_args() + + # 开始测试 + start_time = time.time() + + self.print_header("Step 2: Configuration Physics Update") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 设置Intel环境 + self.print_step(1, 4, "Setting up Intel oneAPI environment") + if not self.setup_intel_environment(args): + if not args.force: + self.print_error("Intel environment setup failed") + return 1 + self.print_warning("Intel environment setup failed, continuing...") + + # 2. 构建项目(如果需要) + self.print_step(2, 4, "Building project if needed") + if not self.build_project(args): + if not args.force: + return 1 + + # 3. 查找测试可执行文件 + self.print_step(3, 4, "Finding test executable") + if not self.find_test_executable(): + self.print_error(f"Test executable {self.test_name}.exe not found") + if not args.force: + return 1 + self.print_warning("Test executable not found, but continuing due to --force") + return 0 + + # 4. 运行测试 + self.print_step(4, 4, "Running configuration physics test") + test_passed = self.run_test_with_intel_env() + + # 生成报告 + test_time = time.time() - start_time + self.print_header("Step 2 Complete") + + print(f"测试: {'通过 ✓' if test_passed else '失败 ✗'}") + print(f"测试程序: {self.test_exe.name if self.test_exe else '未找到'}") + print(f"总耗时: {test_time:.1f}秒") + + if test_passed: + print(f"\n下一步: 更新组件管理器以支持物理模块") + print(f"建议: 修改component_manager.f90,添加physics组件创建") + return 0 + else: + if args.force: + self.print_warning("测试失败,但由于--flag继续执行") + return 0 + return 1 + + except KeyboardInterrupt: + self.print_error("\n测试被用户中断") + return 1 + except Exception as e: + self.print_error(f"测试过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + system = Step2System() + return system.run() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03e/src/CMakeLists.txt new file mode 100644 index 000000000..2d1466a77 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/CMakeLists.txt @@ -0,0 +1,39 @@ +# ==================== src\CMakeLists.txt ==================== + +# src/CMakeLists.txt +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 添加子目录 +add_subdirectory(base) +add_subdirectory(core) +add_subdirectory(infrastructure) +add_subdirectory(numerics) +add_subdirectory(physics) # ← 新增物理模块目录 +add_subdirectory(manager) +add_subdirectory(solver) + +# ==================== 新增:结果模块 ==================== +message(STATUS "配置结果模块...") + +add_library(results STATIC + results.f90 # 新增文件 +) + +target_link_libraries(results + PRIVATE + base + infrastructure + solver +) + +set_target_properties(results PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "结果模块配置完成") +# ==================== 结束新增 ==================== + +message(STATUS "源代码目录配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/base/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03e/src/base/CMakeLists.txt new file mode 100644 index 000000000..74f4aa65f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/base/CMakeLists.txt @@ -0,0 +1,16 @@ +# src/base/CMakeLists.txt +message(STATUS "Configuring base module...") + +add_library(base STATIC + modules.f90 + precision.f90 # 新增 +) + +set_target_properties(base PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Base module configured") + +# src/core/CMakeLists.txt +message(STATUS "Configuring core module...") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/base/modules.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/src/base/modules.f90 new file mode 100644 index 000000000..43aaee241 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/base/modules.f90 @@ -0,0 +1,36 @@ +! src/base/modules.f90 +module base_modules + use, intrinsic :: iso_fortran_env, only: real64, int32 + implicit none + + public :: wp, ip, max_name_len, string_len, cfd_config_base, component_info + + integer, parameter :: wp = real64 + integer, parameter :: ip = int32 + integer, parameter :: string_len = 100 + integer, parameter :: max_name_len = 32 + + ! 基础配置类型 + type :: cfd_config_base + character(len=max_name_len) :: ic_type = "step" + character(len=max_name_len) :: recon_scheme = "eno" + character(len=max_name_len) :: flux_type = "rusanov" + integer(ip) :: rk_order = 1 + real(wp) :: wave_speed = 1.0_wp + real(wp) :: final_time = 0.625_wp + real(wp) :: dt = 0.025_wp + character(len=max_name_len) :: boundary_type = "periodic" + integer(ip) :: spatial_order = 2 + character(len=max_name_len) :: equation_type = "linear_advection" + character(len=max_name_len) :: problem_type = "linear_advection" + logical :: verbose = .true. + end type cfd_config_base + + ! 组件信息类型 + type :: component_info + character(len=max_name_len) :: category = "" + character(len=max_name_len) :: name = "" + integer(ip) :: order = 0 + end type component_info + +end module base_modules \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/base/precision.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/src/base/precision.f90 new file mode 100644 index 000000000..4ac5fd7ef --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/base/precision.f90 @@ -0,0 +1,9 @@ +! src/base/precision.f90(简单版本) +module precision_module + use base_modules, only: wp, ip + implicit none + + ! 重新导出,确保兼容 + public :: wp, ip + +end module precision_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03e/src/core/CMakeLists.txt new file mode 100644 index 000000000..d8b8df064 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/core/CMakeLists.txt @@ -0,0 +1,14 @@ +# src/core/CMakeLists.txt +message(STATUS "Configuring core module...") + +add_library(core STATIC + registry.f90 +) + +target_link_libraries(core PRIVATE base) + +set_target_properties(core PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Core module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/core/factory_base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/src/core/factory_base.f90 new file mode 100644 index 000000000..302418a15 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/core/factory_base.f90 @@ -0,0 +1,57 @@ +! src/core/factory_base.f90 +module factory_base_module + use base_modules, only: wp, ip + use registry_module, only: create_component, has_component + + implicit none + private + public :: wp, ip, factory_base, factory_create + + ! 工厂基类 + type :: factory_base + character(len=max_name_length) :: category = "" + contains + procedure :: create => factory_base_create + procedure :: get_available => factory_base_get_available + end type factory_base + + ! 便捷函数类型 + abstract interface + function factory_function_interface(category, name) result(instance) + import :: wp + character(len=*), intent(in) :: category, name + class(*), allocatable :: instance + end function factory_function_interface + end interface + +contains + + ! 创建工厂实例 + function factory_create(category) result(factory) + character(len=*), intent(in) :: category + type(factory_base) :: factory + factory%category = trim(category) + end function factory_create + + ! 工厂创建方法 + function factory_base_create(this, name) result(instance) + class(factory_base), intent(in) :: this + character(len=*), intent(in) :: name + class(*), allocatable :: instance + + instance = create_component(this%category, name) + end function factory_base_create + + ! 获取可用组件列表(简化版) + subroutine factory_base_get_available(this, names, count) + class(factory_base), intent(in) :: this + character(len=*), allocatable, intent(out) :: names(:) + integer(ip), intent(out) :: count + + ! 这里需要实现从注册表获取列表的逻辑 + ! 暂时返回空列表 + count = 0 + allocate(character(len=max_name_length) :: names(0)) + end subroutine factory_base_get_available + +end module factory_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..a960167c3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/core/factory_interfaces.f90 @@ -0,0 +1,17 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, factory_procedure + + ! Factory procedure interface + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/src/core/registry.f90 new file mode 100644 index 000000000..d155aa19b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/core/registry.f90 @@ -0,0 +1,257 @@ +! src/core/registry.f90 (更新版) +module registry_module + use base_modules, only: wp, ip, max_name_len, component_info + + implicit none + private + + ! 明确公开所有需要的接口 + public :: wp, ip ! 类型参数 + public :: component_info ! 类型 + public :: registry_init, registry_cleanup ! 初始化/清理 + public :: register_component_simple ! 注册组件 + public :: has_component_simple ! 检查组件 + public :: list_components ! 列出组件 + public :: registry_is_initialized ! 检查初始化状态 + public :: registry_get_size ! 获取大小 + public :: initialize_default_components ! 新增:初始化默认组件 + + ! 全局注册表 + type :: component_registry + type(component_info), allocatable :: components(:) + integer(ip) :: count = 0 + integer(ip) :: capacity = 100 + logical :: initialized = .false. + logical :: verbose = .true. + logical :: default_components_added = .false. ! 新增:标记是否已添加默认组件 + end type component_registry + + type(component_registry) :: registry + +contains + + ! ==================== 公共API ==================== + + subroutine registry_init(verbose) + logical, optional, intent(in) :: verbose + + if (registry%initialized) then + if (registry%verbose) then + print *, "[REGISTRY] Already initialized" + end if + return + end if + + if (present(verbose)) then + registry%verbose = verbose + end if + + allocate(registry%components(registry%capacity)) + registry%initialized = .true. + + if (registry%verbose) then + print *, "[REGISTRY] Initialized with capacity:", registry%capacity + end if + end subroutine registry_init + + subroutine registry_cleanup() + if (allocated(registry%components)) then + deallocate(registry%components) + end if + registry%initialized = .false. + registry%count = 0 + registry%default_components_added = .false. ! 重置标记 + + if (registry%verbose) then + print *, "[REGISTRY] Cleaned up" + end if + end subroutine registry_cleanup + + ! 新增:初始化默认组件 + subroutine initialize_default_components() + if (.not. registry%initialized) then + call registry_init() + end if + + if (registry%default_components_added) then + if (registry%verbose) then + print *, "[REGISTRY] Default components already added" + end if + return + end if + + ! 注册重构器 + call register_component_simple("reconstructor", "eno", order=3) + call register_component_simple("reconstructor", "weno3", order=3) + call register_component_simple("reconstructor", "weno5", order=5) + + ! 注册通量计算器 + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + + ! 注册边界条件 + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + + ! 注册时间积分器 + call register_component_simple("integrator", "rk1", order=1) + call register_component_simple("integrator", "rk2", order=2) + call register_component_simple("integrator", "rk3", order=3) + + ! 注册方程 + call register_component_simple("equation", "linear_advection") + + ! 注册问题 + call register_component_simple("problem", "linear_advection") + + ! 注册初始条件 + call register_component_simple("initial_condition", "step") + call register_component_simple("initial_condition", "sin") + call register_component_simple("initial_condition", "gaussian") + + registry%default_components_added = .true. + + if (registry%verbose) then + print *, "[REGISTRY] Default components registered" + print *, "[REGISTRY] Total components:", registry%count + end if + end subroutine initialize_default_components + + subroutine register_component_simple(category, name, order) + character(len=*), intent(in) :: category, name + integer(ip), optional, intent(in) :: order + + integer(ip) :: i + type(component_info) :: info + + if (.not. registry%initialized) then + call registry_init() + end if + + ! 检查是否已存在 + do i = 1, registry%count + if (trim(registry%components(i)%category) == trim(category) .and. & + trim(registry%components(i)%name) == trim(name)) then + if (registry%verbose) then + print *, "[WARN] Overwriting component: ", trim(category), ".", trim(name) + end if + + ! 更新 + if (present(order)) then + registry%components(i)%order = order + else + registry%components(i)%order = 0 + end if + return + end if + end do + + ! 扩展数组 + if (registry%count >= registry%capacity) then + call expand_registry() + end if + + ! 添加新组件 + registry%count = registry%count + 1 + + info%category = trim(category) + info%name = trim(name) + info%order = 0 + if (present(order)) then + info%order = order + end if + + registry%components(registry%count) = info + + if (registry%verbose) then + print *, "[OK] Registered simple: ", trim(category), ".", trim(name) + end if + end subroutine register_component_simple + + logical function has_component_simple(category, name) + character(len=*), intent(in) :: category, name + + integer(ip) :: i + + has_component_simple = .false. + + if (.not. registry%initialized) return + + do i = 1, registry%count + if (trim(registry%components(i)%category) == trim(category) .and. & + trim(registry%components(i)%name) == trim(name)) then + has_component_simple = .true. + return + end if + end do + end function has_component_simple + + subroutine list_components(category) + character(len=*), optional, intent(in) :: category + + integer(ip) :: i, count + + if (.not. registry%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + if (registry%count == 0) then + print *, "[INFO] No components registered" + return + end if + + count = 0 + print *, "=== Registry Contents ===" + do i = 1, registry%count + if (.not. present(category) .or. & + trim(registry%components(i)%category) == trim(category)) then + call print_component_info(registry%components(i)) + count = count + 1 + end if + end do + + print *, "Total:", count, "components" + print *, "==========================" + end subroutine list_components + + ! ==================== 新增函数 ==================== + + logical function registry_is_initialized() + ! 检查注册表是否已初始化 + registry_is_initialized = registry%initialized + end function registry_is_initialized + + integer(ip) function registry_get_size() + ! 获取注册表中的组件数量 + registry_get_size = registry%count + end function registry_get_size + + ! ==================== 内部辅助函数 ==================== + + subroutine expand_registry() + type(component_info), allocatable :: temp(:) + + registry%capacity = registry%capacity * 2 + allocate(temp(registry%capacity)) + temp(1:registry%count) = registry%components(1:registry%count) + call move_alloc(temp, registry%components) + + if (registry%verbose) then + print *, "[INFO] Registry expanded to capacity:", registry%capacity + end if + end subroutine expand_registry + + subroutine print_component_info(info) + type(component_info), intent(in) :: info + + if (info%order > 0) then + print *, " [", trim(info%category), ".", trim(info%name), & + " (order:", info%order, ")]" + else + print *, " [", trim(info%category), ".", trim(info%name), "]" + end if + end subroutine print_component_info + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/core/registry_initializer.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/src/core/registry_initializer.f90 new file mode 100644 index 000000000..44023d1dd --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/core/registry_initializer.f90 @@ -0,0 +1,39 @@ +! src/core/registry_initializer.f90 (新增文件) +module registry_initializer_module + use registry_module, only: register_component_simple + implicit none + private + public :: initialize_default_registry + +contains + + subroutine initialize_default_registry() + ! 注册重构器 + call register_component_simple("reconstructor", "eno", order=3) + call register_component_simple("reconstructor", "weno3", order=3) + call register_component_simple("reconstructor", "weno5", order=5) + + ! 注册通量计算器 + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + + ! 注册边界条件 + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + + ! 注册时间积分器 + call register_component_simple("integrator", "rk1", order=1) + call register_component_simple("integrator", "rk2", order=2) + call register_component_simple("integrator", "rk3", order=3) + + ! 注册方程 + call register_component_simple("equation", "linear_advection") + + ! 注册问题 + call register_component_simple("problem", "linear_advection") + + print *, "[REGISTRY] Default components registered" + end subroutine initialize_default_registry + +end module registry_initializer_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03e/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..70cbbd2f3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/infrastructure/CMakeLists.txt @@ -0,0 +1,17 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "Configuring infrastructure module...") + +add_library(infrastructure STATIC + config.f90 + mesh.f90 + domain.f90 # 新增 + solution.f90 # 新增 +) + +target_link_libraries(infrastructure PRIVATE base) + +set_target_properties(infrastructure PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Infrastructure module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/src/infrastructure/config.f90 new file mode 100644 index 000000000..7586a1a50 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/infrastructure/config.f90 @@ -0,0 +1,144 @@ +! src/infrastructure/config.f90 (修复版) +module config_module + use base_modules, only: wp, ip, max_name_len, cfd_config_base + + implicit none + public :: wp, ip, cfd_config, config_print, config_with_reconstruction + + ! 扩展配置类型 - 添加物理相关字段 + type, extends(cfd_config_base) :: cfd_config + ! 物理参数 + real(wp) :: left_boundary_value = 1.0_wp + real(wp) :: right_boundary_value = 2.0_wp + real(wp) :: domain_length = 2.0_wp + + ! 新增:物理模块相关配置 + real(wp) :: pulse_center = 0.5_wp ! 高斯脉冲中心 + real(wp) :: pulse_width = 0.1_wp ! 高斯脉冲宽度 + logical :: enable_physics = .true. ! 是否启用物理模块 + contains + ! 新增:物理相关配置方法 + procedure :: set_physics_parameters + procedure :: get_physics_info + end type cfd_config + +contains + + subroutine config_print(cfg) + type(cfd_config), intent(in) :: cfg + + print *, "=== CFD Configuration ===" + print *, "Initial condition: ", trim(cfg%ic_type) + print *, "Reconstruction: ", trim(cfg%recon_scheme), " (order:", cfg%spatial_order, ")" + print *, "Flux type: ", trim(cfg%flux_type) + print *, "Time integration: RK", cfg%rk_order + print *, "Wave speed: ", cfg%wave_speed + print *, "Final time: ", cfg%final_time + print *, "Time step: ", cfg%dt + print *, "Boundary: ", trim(cfg%boundary_type) + + ! 新增:物理配置信息 + print *, "--- Physics Configuration ---" + print *, "Equation type: ", trim(cfg%equation_type) + print *, "Problem type: ", trim(cfg%problem_type) + print *, "Domain length: ", cfg%domain_length + print *, "Physics enabled: ", cfg%enable_physics + + if (cfg%ic_type == "gaussian") then + print *, "Pulse center: ", cfg%pulse_center + print *, "Pulse width: ", cfg%pulse_width + end if + + print *, "===============================" + end subroutine config_print + + subroutine config_with_reconstruction(cfg, scheme, order) + type(cfd_config), intent(inout) :: cfg + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + integer :: i + + ! 转换为小写 + cfg%recon_scheme = scheme + do i = 1, len_trim(cfg%recon_scheme) + if (cfg%recon_scheme(i:i) >= 'A' .and. cfg%recon_scheme(i:i) <= 'Z') then + cfg%recon_scheme(i:i) = char(ichar(cfg%recon_scheme(i:i)) + 32) + end if + end do + + ! 设置阶数 + if (present(order)) then + cfg%spatial_order = order + else + if (index(cfg%recon_scheme, 'weno') > 0) then + cfg%spatial_order = 5 + else if (trim(cfg%recon_scheme) == 'eno') then + cfg%spatial_order = 3 + end if + end if + + if (cfg%verbose) then + print *, "[CONFIG] Reconstruction: ", trim(cfg%recon_scheme), & + " Order: ", cfg%spatial_order + end if + end subroutine config_with_reconstruction + + ! ========== 新增:物理参数设置方法 ========== + + subroutine set_physics_parameters(this, equation_type, problem_type, & + domain_length, enable_physics) + class(cfd_config), intent(inout) :: this + character(len=*), intent(in), optional :: equation_type, problem_type + real(wp), intent(in), optional :: domain_length + logical, intent(in), optional :: enable_physics + + if (present(equation_type)) then + this%equation_type = trim(equation_type) + if (this%verbose) then + print *, "[CONFIG] Set equation type: ", trim(this%equation_type) + end if + end if + + if (present(problem_type)) then + this%problem_type = trim(problem_type) + if (this%verbose) then + print *, "[CONFIG] Set problem type: ", trim(this%problem_type) + end if + end if + + if (present(domain_length)) then + this%domain_length = domain_length + if (this%verbose) then + print *, "[CONFIG] Set domain length: ", this%domain_length + end if + end if + + if (present(enable_physics)) then + this%enable_physics = enable_physics + if (this%verbose) then + print *, "[CONFIG] Physics module enabled: ", this%enable_physics + end if + end if + end subroutine set_physics_parameters + + subroutine get_physics_info(this) + class(cfd_config), intent(in) :: this + + print *, "=== Physics Configuration Info ===" + print *, "Equation type: ", trim(this%equation_type) + print *, "Problem type: ", trim(this%problem_type) + print *, "Domain length: ", this%domain_length + print *, "Wave speed: ", this%wave_speed + print *, "Physics enabled: ", this%enable_physics + + if (this%ic_type == "gaussian") then + print *, "Pulse parameters:" + print *, " Center: ", this%pulse_center + print *, " Width: ", this%pulse_width + end if + + print *, "==================================" + end subroutine get_physics_info + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/infrastructure/domain.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/src/infrastructure/domain.f90 new file mode 100644 index 000000000..c3662f039 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/infrastructure/domain.f90 @@ -0,0 +1,102 @@ +! src/infrastructure/domain.f90 +module domain_module + use base_modules, only: wp, ip, max_name_len + use config_module, only: cfd_config + use mesh_module, only: mesh_type + + implicit none + private + public :: wp, ip, domain_type, domain_create, is_physical_cell + + type :: domain_type + type(cfd_config), pointer :: config => null() + type(mesh_type), pointer :: mesh => null() + integer(ip) :: nghosts = 0 + integer(ip) :: ist = 1 ! 物理区域起始索引(1-based) + integer(ip) :: ied = 1 ! 物理区域结束索引(exclusive) + integer(ip) :: ntcells = 0 ! 总单元数(含ghost) + contains + procedure :: print_info => domain_print_info + procedure :: get_physical_indices => domain_get_physical_indices + end type domain_type + +contains + + function domain_create(config, mesh) result(domain) + type(cfd_config), target, intent(in) :: config + type(mesh_type), target, intent(in) :: mesh + type(domain_type) :: domain + + domain%config => config + domain%mesh => mesh + + ! 计算ghost层数(参考Julia的_calc_nghosts) + domain%nghosts = calc_nghosts(config) + domain%ist = domain%nghosts + 1 + domain%ied = domain%ist + mesh%ncells + domain%ntcells = mesh%ncells + 2 * domain%nghosts + + if (config%verbose) then + print *, "[DOMAIN] Created:" + print *, " Ghost layers: ", domain%nghosts + print *, " Physical cells: ", domain%ist, " to ", domain%ied - 1 + print *, " Total cells: ", domain%ntcells + end if + end function domain_create + + function calc_nghosts(config) result(nghosts) + type(cfd_config), intent(in) :: config + integer(ip) :: nghosts + + character(len=max_name_len) :: scheme + + scheme = config%recon_scheme + + if (scheme == "eno") then + nghosts = config%spatial_order + else if (index(scheme, "weno") > 0) then + nghosts = config%spatial_order / 2 + 1 + else + print *, "[WARNING] Unknown scheme, using default nghosts=2" + nghosts = 2 + end if + + if (nghosts <= 0) then + print *, "[ERROR] Invalid nghosts: ", nghosts + nghosts = 2 + end if + end function calc_nghosts + + logical function is_physical_cell(this, idx) + class(domain_type), intent(in) :: this + integer(ip), intent(in) :: idx + is_physical_cell = (idx >= this%ist .and. idx < this%ied) + end function is_physical_cell + + function domain_get_physical_indices(this) result(indices) + class(domain_type), intent(in) :: this + integer(ip), allocatable :: indices(:) + integer(ip) :: i, count + + count = this%ied - this%ist + allocate(indices(count)) + + do i = 1, count + indices(i) = this%ist + i - 1 + end do + end function domain_get_physical_indices + + subroutine domain_print_info(this) + class(domain_type), intent(in) :: this + + print *, "=== Domain Information ===" + print *, "Configuration: ", trim(this%config%recon_scheme), & + " order ", this%config%spatial_order + print *, "Ghost layers: ", this%nghosts + print *, "Physical cells: ", this%ist, " to ", this%ied - 1 + print *, "Total cells: ", this%ntcells + print *, "Mesh cells: ", this%mesh%ncells + print *, "==========================" + end subroutine domain_print_info + +end module domain_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..f810f3a1b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/infrastructure/mesh.f90 @@ -0,0 +1,73 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use base_modules, only: wp, ip + + implicit none + public :: wp, ip, mesh_type, mesh_init, mesh_print_info + + ! 网格类型 + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer(ip) :: ncells = 40 + integer(ip) :: nnodes + integer(ip) :: nx + real(wp), allocatable :: x(:) ! 节点坐标 + real(wp), allocatable :: xcc(:) ! 单元中心坐标 + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer(ip), optional, intent(in) :: ncells + + integer(ip) :: i + + ! 设置参数 + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! 计算 + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! 分配内存 + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! 生成节点坐标 + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! 生成单元中心坐标 + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== Mesh Information ===" + print *, "Domain: [", this%xmin, ", ", this%xmax, "]" + print *, "Cells: ", this%ncells + print *, "Nodes: ", this%nnodes + print *, "dx: ", this%dx + print *, "L: ", this%L + print *, "========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/infrastructure/solution.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/src/infrastructure/solution.f90 new file mode 100644 index 000000000..ce88fd8a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/infrastructure/solution.f90 @@ -0,0 +1,131 @@ +! src/infrastructure/solution.f90 +module solution_module + use base_modules, only: wp, ip + use domain_module, only: domain_type + + implicit none + private + public :: wp, ip, solution_type, solution_create, solution_reset + + type :: solution_type + type(domain_type), pointer :: domain => null() + real(wp), allocatable :: u(:) ! 当前解(含ghost) + real(wp), allocatable :: un(:) ! 旧解 + real(wp), allocatable :: q_face_left(:) ! 左界面值 + real(wp), allocatable :: q_face_right(:)! 右界面值 + real(wp), allocatable :: flux(:) ! 通量 + real(wp), allocatable :: res(:) ! 残差 + contains + procedure :: initialize => solution_initialize + procedure :: update_old_field => solution_update_old_field + procedure :: print_info => solution_print_info + procedure :: reset => solution_reset_instance + end type solution_type + +contains + + function solution_create(domain) result(solution) + type(domain_type), target, intent(in) :: domain + type(solution_type) :: solution + + integer(ip) :: ncells, nnodes, ntcells + + solution%domain => domain + + ncells = domain%mesh%ncells + nnodes = domain%mesh%nnodes + ntcells = domain%ntcells + + ! 分配数组(与Julia solution.jl一致) + allocate(solution%u(ntcells), source=0.0_wp) + allocate(solution%un(ntcells), source=0.0_wp) + allocate(solution%q_face_left(nnodes), source=0.0_wp) + allocate(solution%q_face_right(nnodes), source=0.0_wp) + allocate(solution%flux(nnodes), source=0.0_wp) + allocate(solution%res(ncells), source=0.0_wp) + + if (domain%config%verbose) then + print *, "[SOLUTION] Created:" + print *, " u size: ", size(solution%u), " (with ghosts)" + print *, " flux size: ", size(solution%flux) + print *, " res size: ", size(solution%res) + end if + end function solution_create + + subroutine solution_initialize(this, initial_values) + class(solution_type), intent(inout) :: this + real(wp), intent(in), optional :: initial_values(:) + + integer(ip) :: i, idx + type(domain_type), pointer :: domain + + domain => this%domain + + if (present(initial_values)) then + ! 应用初始值到物理区域 + do i = domain%ist, domain%ied - 1 + idx = i - domain%ist + 1 + if (idx <= size(initial_values)) then + this%u(i) = initial_values(idx) + end if + end do + else + ! 默认为0 + this%u = 0.0_wp + end if + + ! 同步旧场(与Julia的update_old_field一致) + call this%update_old_field() + + if (domain%config%verbose) then + print *, "[SOLUTION] Initialized" + print *, " u range: ", minval(this%u), " to ", maxval(this%u) + end if + end subroutine solution_initialize + + subroutine solution_update_old_field(this) + class(solution_type), intent(inout) :: this + this%un = this%u ! 与Julia的 un .= u 一致 + end subroutine solution_update_old_field + + subroutine solution_reset_instance(this) + class(solution_type), intent(inout) :: this + call solution_reset(this) + end subroutine solution_reset_instance + + subroutine solution_reset(solution) + type(solution_type), intent(inout) :: solution + + if (allocated(solution%u)) solution%u = 0.0_wp + if (allocated(solution%un)) solution%un = 0.0_wp + if (allocated(solution%q_face_left)) solution%q_face_left = 0.0_wp + if (allocated(solution%q_face_right)) solution%q_face_right = 0.0_wp + if (allocated(solution%flux)) solution%flux = 0.0_wp + if (allocated(solution%res)) solution%res = 0.0_wp + + if (associated(solution%domain) .and. solution%domain%config%verbose) then + print *, "[SOLUTION] Reset" + end if + end subroutine solution_reset + + subroutine solution_print_info(this) + class(solution_type), intent(in) :: this + + print *, "=== Solution Information ===" + print *, "Arrays:" + print *, " u: ", size(this%u), " elements" + print *, " un: ", size(this%un), " elements" + print *, " q_face_left: ", size(this%q_face_left), " elements" + print *, " q_face_right: ", size(this%q_face_right), " elements" + print *, " flux: ", size(this%flux), " elements" + print *, " res: ", size(this%res), " elements" + + if (allocated(this%u)) then + print *, "Values:" + print *, " u min/max: ", minval(this%u), maxval(this%u) + print *, " un min/max: ", minval(this%un), maxval(this%un) + end if + print *, "============================" + end subroutine solution_print_info + +end module solution_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/manager/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03e/src/manager/CMakeLists.txt new file mode 100644 index 000000000..00c8bf49b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/manager/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/manager/CMakeLists.txt +message(STATUS "配置管理器模块...") + +# 创建管理器库 +add_library(manager STATIC + component_manager.f90 + component_factory.f90 +) + +# 明确依赖关系:管理器依赖所有其他模块 +target_link_libraries(manager + PRIVATE + core + infrastructure + reconstructor + flux +) + +# 设置模块输出目录 +set_target_properties(manager PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "管理器模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/manager/component_factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/src/manager/component_factory.f90 new file mode 100644 index 000000000..de8cbf1a8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/manager/component_factory.f90 @@ -0,0 +1,142 @@ +! src/manager/component_factory.f90 (完整文件) +module component_factory_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use config_module, only: cfd_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + + use eno_reconstructor_module, only: eno_reconstructor, create_eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor, create_weno3_reconstructor + use weno5_reconstructor_module, only: weno5_reconstructor, create_weno5_reconstructor + use rusanov_flux_module, only: rusanov_flux, create_rusanov_flux + + implicit none + private + public :: wp, create_reconstructor, create_flux_calculator + + ! 错误代码 + integer, parameter :: CM_SUCCESS = 0 + integer, parameter :: CM_ERROR_UNKNOWN_SCHEME = 1 + integer, parameter :: CM_ERROR_UNKNOWN_FLUX = 2 + integer, parameter :: CM_ERROR_INVALID_ORDER = 3 + +contains + + ! ==================== 重构器创建 ==================== + + function create_reconstructor(config, status) result(recon) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(reconstructor_base), allocatable :: recon + + character(len=20) :: scheme + integer :: order, error_code + + scheme = trim(adjustl(config%recon_scheme)) + order = config%spatial_order + + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating reconstructor: ", scheme, " order=", order + end if + + ! 处理"weno"作为WENO5的别名(与Julia一致) + if (scheme == "weno" .and. order == 5) then + scheme = "weno5" + end if + + select case(scheme) + case('eno') + allocate(eno_reconstructor :: recon) + select type(recon) + type is(eno_reconstructor) + recon = create_eno_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case('weno3') + allocate(weno3_reconstructor :: recon) + select type(recon) + type is(weno3_reconstructor) + recon = create_weno3_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case('weno5') + allocate(weno5_reconstructor :: recon) + select type(recon) + type is(weno5_reconstructor) + recon = create_weno5_reconstructor() + recon%order = order ! 覆盖默认阶数 + end select + + case default + error_code = CM_ERROR_UNKNOWN_SCHEME + if (config%verbose) then + print *, "[ERROR] Unknown reconstructor scheme: ", scheme + print *, " Available: eno, weno3, weno5" + end if + end select + + ! 检查阶数有效性 + if (error_code == CM_SUCCESS) then + if (order < 1) then + error_code = CM_ERROR_INVALID_ORDER + if (config%verbose) then + print *, "[ERROR] Invalid spatial order: ", order + end if + end if + end if + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Reconstructor creation failed" + end if + end function create_reconstructor + + ! ==================== 通量计算器创建 ==================== + + function create_flux_calculator(config, status) result(flux) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(flux_calculator_base), allocatable :: flux + + character(len=20) :: flux_type + integer :: error_code + + flux_type = trim(adjustl(config%flux_type)) + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating flux calculator: ", flux_type + end if + + select case(flux_type) + case('rusanov') + allocate(rusanov_flux :: flux) + select type(flux) + type is(rusanov_flux) + flux = create_rusanov_flux() + flux%wave_speed_default = config%wave_speed + end select + + case default + error_code = CM_ERROR_UNKNOWN_FLUX + if (config%verbose) then + print *, "[ERROR] Unknown flux type: ", flux_type + print *, " Available: rusanov" + end if + end select + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Flux calculator creation failed" + end if + end function create_flux_calculator + +end module component_factory_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/manager/component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/src/manager/component_manager.f90 new file mode 100644 index 000000000..25eac29be --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/manager/component_manager.f90 @@ -0,0 +1,76 @@ +! src/manager/component_manager.f90 (完整文件) +module component_manager_module + use, intrinsic :: iso_fortran_env, only: wp => real64 + use config_module, only: cfd_config + use component_factory_module, only: create_reconstructor, create_flux_calculator + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + + implicit none + private + public :: wp, component_manager_info, validate_config + public :: create_reconstructor, create_flux_calculator + +contains + + ! ==================== 配置验证 ==================== + + function validate_config(config) result(is_valid) + type(cfd_config), intent(in) :: config + logical :: is_valid + + integer :: status + class(reconstructor_base), allocatable :: test_recon + class(flux_calculator_base), allocatable :: test_flux + + is_valid = .false. + + ! 测试创建重构器 + test_recon = create_reconstructor(config, status) + if (status /= 0) then + if (config%verbose) then + print *, "[CONFIG VALIDATION] Invalid reconstructor configuration" + end if + return + end if + + ! 测试创建通量计算器 + test_flux = create_flux_calculator(config, status) + if (status /= 0) then + if (config%verbose) then + print *, "[CONFIG VALIDATION] Invalid flux configuration" + end if + return + end if + + ! 清理测试组件 + if (allocated(test_recon)) deallocate(test_recon) + if (allocated(test_flux)) deallocate(test_flux) + + is_valid = .true. + + if (config%verbose) then + print *, "[CONFIG VALIDATION] Configuration is valid" + end if + end function validate_config + + ! ==================== 信息显示 ==================== + + subroutine component_manager_info() + print *, "=== Component Manager ===" + print *, "Available reconstructors:" + print *, " - eno (orders: 1-7)" + print *, " - weno3 (order: 3)" + print *, " - weno5 (order: 5)" + print *, "" + print *, "Available flux calculators:" + print *, " - rusanov" + print *, "" + print *, "Features:" + print *, " - Configuration validation" + print *, " - Component creation from config" + print *, " - Error handling with status codes" + print *, "=========================" + end subroutine component_manager_info + +end module component_manager_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..66874a7ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/CMakeLists.txt @@ -0,0 +1,7 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..3fde7746b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,28 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +# Start with just the base module +add_library(flux STATIC + base.f90 + rusanov.f90 +) + +#target_link_libraries(flux PRIVATE core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 明确设置不使用 /Qm64 选项 +if(CMAKE_Fortran_COMPILER_ID MATCHES "IntelLLVM|Intel") + set_target_properties(flux PROPERTIES + Fortran_FLAGS "${CMAKE_Fortran_FLAGS} /Qm64" # 明确设置,避免继承问题 + ) +endif() + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/flux/base.f90 new file mode 100644 index 000000000..7080a7ae4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/flux/base.f90 @@ -0,0 +1,30 @@ +!src/numerics/flux/base.f90 +module flux_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, flux_calculator_base + + ! Base flux calculator type + type :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure :: info => flux_info + procedure :: print_basic_info => flux_print_basic ! 添加辅助方法 + end type flux_calculator_base + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "Flux calculator information:" + print *, " Name: ", trim(this%name) + end subroutine flux_info + + subroutine flux_print_basic(this) + class(flux_calculator_base), intent(in) :: this + print *, " Name: ", trim(this%name) + end subroutine flux_print_basic + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/flux/rusanov.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/flux/rusanov.f90 new file mode 100644 index 000000000..daa9e3bb5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/flux/rusanov.f90 @@ -0,0 +1,41 @@ +! src/numerics/flux/rusanov.f90 +module rusanov_flux_module + use, intrinsic :: iso_fortran_env, only: real64 + use flux_base_module, only: flux_calculator_base + implicit none + + private + public :: real64, rusanov_flux, create_rusanov_flux + + type, extends(flux_calculator_base) :: rusanov_flux + real(real64) :: wave_speed_default = 1.0_real64 + contains + procedure :: info => rusanov_info + end type rusanov_flux + + ! 构造函数接口 + interface rusanov_flux + module procedure create_rusanov_flux + end interface + +contains + + ! 构造函数 + type(rusanov_flux) function create_rusanov_flux() result(this) + this%name = "Rusanov" + this%wave_speed_default = 1.0_real64 + end function create_rusanov_flux + + subroutine rusanov_info(this) + class(rusanov_flux), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Flux calculator information:" + call this%print_basic_info() + + ! 添加Rusanov特有信息 + print *, " Type: Rusanov flux" + print *, " Default wave speed: ", this%wave_speed_default + end subroutine rusanov_info + +end module rusanov_flux_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..c88ea647b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,23 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "Configuring reconstructor module...") + +# Start with just the base module +add_library(reconstructor STATIC + base.f90 + eno.f90 + weno3.f90 + weno5.f90 +) + +target_link_libraries(reconstructor PRIVATE core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Reconstructor module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..53798d021 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/reconstructor/base.f90 @@ -0,0 +1,33 @@ +!src/numerics/reconstructor/base.f90 +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, reconstructor_base + + ! Base reconstructor type + type :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + contains + procedure :: info => reconstructor_info + procedure :: print_basic_info => reconstructor_print_basic ! 添加一个辅助方法 + end type reconstructor_base + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + print *, "Reconstructor information:" + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_info + + subroutine reconstructor_print_basic(this) + class(reconstructor_base), intent(in) :: this + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_print_basic + +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/reconstructor/eno.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/reconstructor/eno.f90 new file mode 100644 index 000000000..f973e8b32 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/reconstructor/eno.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/eno.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, eno_reconstructor, create_eno_reconstructor ! ← 添加这个 + + type, extends(reconstructor_base) :: eno_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => eno_info + end type eno_reconstructor + + ! 构造函数接口 + interface eno_reconstructor + module procedure create_eno_reconstructor + end interface + +contains + + ! 构造函数 + type(eno_reconstructor) function create_eno_reconstructor() result(this) + this%name = "ENO" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_eno_reconstructor + + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加ENO特有信息 + print *, " Type: ENO reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/reconstructor/weno3.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/reconstructor/weno3.f90 new file mode 100644 index 000000000..d5b7a7477 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/reconstructor/weno3.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/weno3.f90 +module weno3_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno3_reconstructor, create_weno3_reconstructor + + type, extends(reconstructor_base) :: weno3_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno3_info + end type weno3_reconstructor + + ! 构造函数接口 + interface weno3_reconstructor + module procedure create_weno3_reconstructor + end interface + +contains + + ! 构造函数 + type(weno3_reconstructor) function create_weno3_reconstructor() result(this) + this%name = "WENO3" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_weno3_reconstructor + + subroutine weno3_info(this) + class(weno3_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加WENO3特有信息 + print *, " Type: WENO-3 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno3_info + +end module weno3_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/reconstructor/weno5.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/reconstructor/weno5.f90 new file mode 100644 index 000000000..a869c67d0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/numerics/reconstructor/weno5.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/weno5.f90(新增) +module weno5_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno5_reconstructor, create_weno5_reconstructor + + type, extends(reconstructor_base) :: weno5_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno5_info + end type weno5_reconstructor + + ! 构造函数接口 + interface weno5_reconstructor + module procedure create_weno5_reconstructor + end interface + +contains + + ! 构造函数 + type(weno5_reconstructor) function create_weno5_reconstructor() result(this) + this%name = "WENO5" + this%order = 5 + this%epsilon = 1.0e-6_real64 + end function create_weno5_reconstructor + + subroutine weno5_info(this) + class(weno5_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加WENO5特有信息 + print *, " Type: WENO-5 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno5_info + +end module weno5_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/physics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03e/src/physics/CMakeLists.txt new file mode 100644 index 000000000..cc4e233ab --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/physics/CMakeLists.txt @@ -0,0 +1,19 @@ +# src/physics/CMakeLists.txt +message(STATUS "配置物理模块...") + +# 创建物理模块库 +add_library(physics STATIC + physics_interface.f90 + equations/linear_convection.f90 + problems/linear_convection_problem.f90 +) + +# 链接依赖 +target_link_libraries(physics PRIVATE base) + +# 设置模块输出目录 +set_target_properties(physics PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "物理模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/physics/equations/linear_convection.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/src/physics/equations/linear_convection.f90 new file mode 100644 index 000000000..fff7be55d --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/physics/equations/linear_convection.f90 @@ -0,0 +1,49 @@ +! src/physics/equations/linear_convection.f90 +module linear_convection_equation + use precision_module, only: wp, ip + use physics_interface, only: physics_equation + implicit none + private + + ! 具体方程类型 - 先声明 + type, extends(physics_equation) :: linear_convection_eq + real(wp) :: wave_speed = 1.0_wp + contains + procedure :: flux => lc_flux + procedure :: speed => lc_speed + end type linear_convection_eq + + ! 公开接口 + public :: wp, ip + public :: linear_convection_eq, create_linear_convection_eq + +contains + + ! 构造函数 + function create_linear_convection_eq(wave_speed) result(eq) + real(wp), intent(in), optional :: wave_speed + type(linear_convection_eq) :: eq + + eq%name = "Linear Convection" + if (present(wave_speed)) then + eq%wave_speed = wave_speed + else + eq%wave_speed = 1.0_wp + end if + end function create_linear_convection_eq + + ! 方法实现 + pure function lc_flux(this, u) result(f) + class(linear_convection_eq), intent(in) :: this + real(wp), intent(in) :: u + real(wp) :: f + f = this%wave_speed * u + end function lc_flux + + pure function lc_speed(this) result(a) + class(linear_convection_eq), intent(in) :: this + real(wp) :: a + a = this%wave_speed + end function lc_speed + +end module linear_convection_equation \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/physics/physics_interface.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/src/physics/physics_interface.f90 new file mode 100644 index 000000000..45002da6c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/physics/physics_interface.f90 @@ -0,0 +1,64 @@ +! src/physics/physics_interface.f90 +module physics_interface + use precision_module, only: wp, ip + implicit none + private + + ! 定义抽象基类型 - 先声明为私有,然后在公开部分导出 + type, abstract :: physics_equation + character(len=:), allocatable :: name + contains + procedure(eq_flux_abs), deferred :: flux + procedure(eq_speed_abs), deferred :: speed + end type physics_equation + + type, abstract :: physics_problem + character(len=:), allocatable :: name + contains + procedure(prob_ic_abs), deferred :: initial_condition + procedure(prob_bc_abs), deferred :: boundary_condition + procedure(prob_exact_abs), deferred :: exact_solution + end type physics_problem + + ! 抽象接口定义 + abstract interface + pure function eq_flux_abs(this, u) result(f) + import :: physics_equation, wp + class(physics_equation), intent(in) :: this + real(wp), intent(in) :: u + real(wp) :: f + end function eq_flux_abs + + pure function eq_speed_abs(this) result(a) + import :: physics_equation, wp + class(physics_equation), intent(in) :: this + real(wp) :: a + end function eq_speed_abs + + subroutine prob_ic_abs(this, x, u) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(in) :: x(:) + real(wp), intent(out) :: u(:) + end subroutine prob_ic_abs + + subroutine prob_bc_abs(this, u, t) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(inout) :: u(:) + real(wp), intent(in), optional :: t + end subroutine prob_bc_abs + + function prob_exact_abs(this, x, t) result(u) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(in) :: x(:), t + real(wp), dimension(size(x)) :: u + end function prob_exact_abs + end interface + + ! 公开接口 - 使用独立的public语句 + public :: wp, ip + public :: physics_equation, physics_problem + +end module physics_interface \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/physics/problems/linear_convection_problem.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/src/physics/problems/linear_convection_problem.f90 new file mode 100644 index 000000000..06226ed13 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/physics/problems/linear_convection_problem.f90 @@ -0,0 +1,118 @@ +! src/physics/problems/linear_convection_problem.f90 +module linear_convection_problem + use precision_module, only: wp, ip + use physics_interface, only: physics_problem + implicit none + private + + ! 具体问题类型 - 先声明 + type, extends(physics_problem) :: linear_convection_prob + real(wp) :: wave_speed = 1.0_wp + real(wp) :: domain_length = 2.0_wp + character(len=20) :: ic_type = "step" + character(len=20) :: boundary_type = "periodic" + contains + procedure :: initial_condition => lc_initial_condition + procedure :: boundary_condition => lc_boundary_condition + procedure :: exact_solution => lc_exact_solution + end type linear_convection_prob + + ! 公开接口 + public :: wp, ip + public :: linear_convection_prob, create_linear_convection_prob + +contains + + ! 构造函数 + function create_linear_convection_prob(wave_speed, domain_length, & + ic_type, boundary_type) result(prob) + real(wp), intent(in), optional :: wave_speed, domain_length + character(len=*), intent(in), optional :: ic_type, boundary_type + type(linear_convection_prob) :: prob + + prob%name = "Linear Convection Problem" + + if (present(wave_speed)) prob%wave_speed = wave_speed + if (present(domain_length)) prob%domain_length = domain_length + if (present(ic_type)) prob%ic_type = ic_type + if (present(boundary_type)) prob%boundary_type = boundary_type + end function create_linear_convection_prob + + ! 初始条件 + subroutine lc_initial_condition(this, x, u) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(in) :: x(:) + real(wp), intent(out) :: u(:) + + integer :: i + + select case (trim(this%ic_type)) + case ("step") + do i = 1, size(x) + if (x(i) >= 0.5_wp .and. x(i) <= 1.0_wp) then + u(i) = 2.0_wp + else + u(i) = 1.0_wp + end if + end do + + case ("sin", "sine") + do i = 1, size(x) + u(i) = sin(2.0_wp * 3.141592653589793_wp * x(i) / this%domain_length) + end do + + case ("gaussian") + do i = 1, size(x) + u(i) = exp(-((x(i) - 0.5_wp) / 0.1_wp)**2) + end do + + case default + ! 默认阶跃函数 + do i = 1, size(x) + if (x(i) >= 0.5_wp .and. x(i) <= 1.0_wp) then + u(i) = 2.0_wp + else + u(i) = 1.0_wp + end if + end do + end select + end subroutine lc_initial_condition + + ! 边界条件(虚拟实现,实际在boundary模块) + subroutine lc_boundary_condition(this, u, t) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(inout) :: u(:) + real(wp), intent(in), optional :: t + + ! 边界条件将在独立模块实现 + print *, "[PROBLEM] Boundary condition placeholder" + if (present(t)) then + print *, " Time = ", t + end if + end subroutine lc_boundary_condition + + ! 精确解(周期性平移) + function lc_exact_solution(this, x, t) result(u) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(in) :: x(:), t + real(wp), dimension(size(x)) :: u + real(wp), dimension(size(x)) :: x_shifted + integer :: i + + ! 周期性平移 + do i = 1, size(x) + x_shifted(i) = x(i) - this%wave_speed * t + ! 确保在 [0, domain_length) 范围内 + do while (x_shifted(i) < 0.0_wp) + x_shifted(i) = x_shifted(i) + this%domain_length + end do + do while (x_shifted(i) >= this%domain_length) + x_shifted(i) = x_shifted(i) - this%domain_length + end do + end do + + ! 重用初始条件函数 + call this%initial_condition(x_shifted, u) + end function lc_exact_solution + +end module linear_convection_problem \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/results.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/src/results.f90 new file mode 100644 index 000000000..f7ce0a7ad --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/results.f90 @@ -0,0 +1,290 @@ +! src/results.f90 (修正版) +module results_module + use base_modules, only: wp, ip + use config_module, only: cfd_config + use mesh_module, only: mesh_type + use domain_module, only: domain_type + use solution_module, only: solution_type + ! use physics_solver_module, only: physics_solver ! 暂时注释掉,避免循环依赖 + + implicit none + private + public :: results_saver, results_saver_create, save_results + + ! 定义字符串长度常量 + integer, parameter :: STR_LEN = 128 + + ! 结果类型 - 对应Julia的result字典 + type :: cfd_results + character(len=STR_LEN) :: solver_name = "" + real(wp), allocatable :: x(:) ! 网格坐标(单元中心) + real(wp), allocatable :: numerical(:) ! 数值解 + real(wp), allocatable :: analytical(:) ! 解析解 + character(len=STR_LEN) :: scheme = "" ! 格式名称 + integer :: order = 0 ! 阶数 + integer :: rk_order = 0 ! RK阶数 + real(wp) :: final_time = 0.0_wp ! 最终时间 + real(wp) :: current_time = 0.0_wp ! 当前时间 + integer :: total_steps = 0 ! 总步数 + integer :: solver_state = 0 ! 求解器状态 + end type cfd_results + + ! 结果保存器 + type :: results_saver + character(len=STR_LEN) :: base_filename = "results" + logical :: verbose = .true. + contains + procedure :: save_text => results_saver_save_text + procedure :: save_binary => results_saver_save_binary + procedure :: load => results_saver_load + end type results_saver + + ! 接口声明 + interface results_saver + module procedure results_saver_constructor + end interface + +contains + + ! 构造函数 + function results_saver_constructor(base_filename, verbose) result(saver) + character(len=*), optional :: base_filename + logical, optional :: verbose + type(results_saver) :: saver + + if (present(base_filename)) then + saver%base_filename = trim(adjustl(base_filename)) + end if + if (present(verbose)) then + saver%verbose = verbose + end if + end function results_saver_constructor + + ! 保持向后兼容的创建函数 + function results_saver_create(base_filename, verbose) result(saver) + character(len=*), optional :: base_filename + logical, optional :: verbose + type(results_saver) :: saver + + saver = results_saver_constructor(base_filename, verbose) + end function results_saver_create + + ! 生成文件名(与Julia风格一致) + function generate_filename(saver, solver_name, mesh_size) result(filename) + class(results_saver), intent(in) :: saver + character(len=*), intent(in) :: solver_name + integer, intent(in) :: mesh_size + character(len=STR_LEN) :: filename + + write(filename, '(A, "_", A, "_", I0, ".dat")') & + trim(saver%base_filename), trim(solver_name), mesh_size + end function generate_filename + + ! 主保存函数 - 生成与Julia兼容的结果 + subroutine save_results(saver, solver_name, config, mesh, domain, solution, & + current_time, total_steps, solver_state) + class(results_saver), intent(in) :: saver + character(len=*), intent(in) :: solver_name + type(cfd_config), intent(in) :: config + type(mesh_type), intent(in) :: mesh + type(domain_type), intent(in) :: domain + type(solution_type), intent(in) :: solution + real(wp), intent(in) :: current_time + integer, intent(in) :: total_steps, solver_state + + type(cfd_results) :: results + character(len=STR_LEN) :: filename + integer :: i, n_physical + + ! 准备结果数据 + results%solver_name = trim(solver_name) + results%scheme = trim(config%recon_scheme) + results%order = config%spatial_order + results%rk_order = config%rk_order + results%final_time = config%final_time + results%current_time = current_time + results%total_steps = total_steps + results%solver_state = solver_state + + ! 分配数组 + n_physical = mesh%ncells + allocate(results%x(n_physical)) + allocate(results%numerical(n_physical)) + allocate(results%analytical(n_physical)) + + ! 填充网格坐标(单元中心) + results%x = mesh%xcc + + ! 填充数值解(仅物理区域) + do i = 1, n_physical + results%numerical(i) = solution%u(domain%ist + i - 1) + end do + + ! 生成解析解(与Julia的exact_solution对应) + call generate_analytical_solution(results%x, config, results%analytical, current_time) + + ! 生成文件名 + filename = generate_filename(saver, solver_name, mesh%ncells) + + ! 保存文件 + if (saver%verbose) then + print *, "[RESULTS] Saving results to: ", trim(filename) + print *, " Solver: ", trim(results%solver_name) + print *, " Scheme: ", trim(results%scheme), " order ", results%order + print *, " Time: ", results%current_time, " / ", results%final_time + print *, " Steps: ", results%total_steps + end if + + call saver%save_text(results, filename) + + ! 清理 + deallocate(results%x, results%numerical, results%analytical) + end subroutine save_results + + ! 生成解析解(匹配Julia的exact_solution逻辑) + subroutine generate_analytical_solution(x, config, analytical, current_time) + real(wp), intent(in) :: x(:), current_time + type(cfd_config), intent(in) :: config + real(wp), intent(out) :: analytical(:) + + integer :: i, n + real(wp) :: x_shifted, L + + n = size(x) + L = config%domain_length + + select case (trim(config%ic_type)) + case ("step") + ! 阶跃函数的精确解(周期性) + do i = 1, n + ! 周期性平移 + x_shifted = x(i) - config%wave_speed * current_time + x_shifted = modulo(x_shifted, L) + if (x_shifted < 0.0_wp) x_shifted = x_shifted + L + + ! 阶跃在 [0.5, 1.0] 内为 2.0,其他为 1.0 + if (x_shifted >= 0.5_wp .and. x_shifted <= 1.0_wp) then + analytical(i) = 2.0_wp + else + analytical(i) = 1.0_wp + end if + end do + + case ("sin", "sine") + ! 正弦波的精确解 + do i = 1, n + x_shifted = x(i) - config%wave_speed * current_time + x_shifted = modulo(x_shifted, L) + analytical(i) = sin(2.0_wp * 3.141592653589793_wp * x_shifted / L) + end do + + case ("gaussian") + ! 高斯脉冲的精确解 + do i = 1, n + x_shifted = x(i) - config%wave_speed * current_time + x_shifted = modulo(x_shifted, L) + analytical(i) = exp(-50.0_wp * (x_shifted - 1.0_wp)**2) + end do + + case default + ! 默认:阶跃函数 + do i = 1, n + x_shifted = x(i) - config%wave_speed * current_time + x_shifted = modulo(x_shifted, L) + if (x_shifted >= 0.5_wp .and. x_shifted <= 1.0_wp) then + analytical(i) = 2.0_wp + else + analytical(i) = 1.0_wp + end if + end do + end select + end subroutine generate_analytical_solution + + ! 文本格式保存(与Julia的纯文本输出兼容) + subroutine results_saver_save_text(this, results, filename) + class(results_saver), intent(in) :: this + type(cfd_results), intent(in) :: results + character(len=*), intent(in) :: filename + + integer :: i, n, unit, ierr + + n = size(results%x) + + ! 打开文件 + open(newunit=unit, file=trim(filename), status='replace', & + action='write', iostat=ierr) + + if (ierr /= 0) then + if (this%verbose) then + print *, "[ERROR] Cannot open file: ", trim(filename) + end if + return + end if + + ! 写入头部信息(类似Julia的输出格式) + write(unit, '(A)') "========================================" + write(unit, '(A)') "CFD SOLVER RESULTS (Fortran)" + write(unit, '(A)') "========================================" + write(unit, '(A, A)') "Solver: ", trim(results%solver_name) + write(unit, '(A, A)') "Scheme: ", trim(results%scheme) + write(unit, '(A, I0)') "Order: ", results%order + write(unit, '(A, I0)') "RK Order: ", results%rk_order + write(unit, '(A, ES15.8)') "Final Time: ", results%final_time + write(unit, '(A, ES15.8)') "Current Time: ", results%current_time + write(unit, '(A, I0)') "Total Steps: ", results%total_steps + write(unit, '(A, I0)') "Solver State: ", results%solver_state + write(unit, '(A, I0)') "Grid Points: ", n + write(unit, '(A)') "========================================" + write(unit, '(A)') "DATA: x, numerical, analytical" + write(unit, '(A)') "========================================" + + ! 写入数据 + do i = 1, n + write(unit, '(3ES20.12)') results%x(i), results%numerical(i), results%analytical(i) + end do + + ! 关闭文件 + close(unit) + + if (this%verbose) then + print *, "[RESULTS] Saved ", n, " data points to ", trim(filename) + end if + end subroutine results_saver_save_text + + ! 二进制保存(可选) + subroutine results_saver_save_binary(this, results, filename) + class(results_saver), intent(in) :: this + type(cfd_results), intent(in) :: results + character(len=*), intent(in) :: filename + + ! 暂时实现文本格式,二进制格式可后续添加 + if (this%verbose) then + print *, "[INFO] Binary save not implemented, using text format" + end if + call this%save_text(results, filename) + end subroutine results_saver_save_binary + + ! 加载结果(暂时简单实现) + subroutine results_saver_load(this, filename, results) + class(results_saver), intent(in) :: this + character(len=*), intent(in) :: filename + type(cfd_results), intent(out) :: results + + ! 简化:只打印文件信息 + if (this%verbose) then + print *, "[RESULTS] Would load from: ", trim(filename) + print *, " Note: Load functionality needs implementation" + end if + + ! 初始化结果结构以避免未初始化警告 + results%solver_name = "" + results%scheme = "" + results%order = 0 + results%rk_order = 0 + results%final_time = 0.0_wp + results%current_time = 0.0_wp + results%total_steps = 0 + results%solver_state = 0 + end subroutine results_saver_load + +end module results_module diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/solver/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03e/src/solver/CMakeLists.txt new file mode 100644 index 000000000..ef6f72df5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/solver/CMakeLists.txt @@ -0,0 +1,22 @@ +# src/solver/CMakeLists.txt +message(STATUS "配置求解器模块...") + +add_library(solver STATIC + base.f90 + physics_solver.f90 +) + +target_link_libraries(solver + PRIVATE + infrastructure + core + physics + manager + results # ← 新增链接结果模块 +) + +set_target_properties(solver PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "求解器模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/solver/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/src/solver/base.f90 new file mode 100644 index 000000000..cfd78c475 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/solver/base.f90 @@ -0,0 +1,264 @@ +! src/solver/base.f90 +module solver_base_module + use base_modules, only: wp => wp, ip => ip ! 重命名以避免冲突 + + use config_module, only: cfd_config + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create + + implicit none + private + + ! 明确导出列表 + public :: wp, ip ! 类型参数 + public :: solver_base, create_solver_base ! 类型和构造函数 + public :: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, SOLVER_RUNNING + public :: SOLVER_COMPLETED, SOLVER_ERROR ! 状态常量 + + ! 求解器状态枚举 + integer, parameter :: SOLVER_UNINITIALIZED = 0 + integer, parameter :: SOLVER_INITIALIZED = 1 + integer, parameter :: SOLVER_RUNNING = 2 + integer, parameter :: SOLVER_COMPLETED = 3 + integer, parameter :: SOLVER_ERROR = 4 + + ! 求解器基类 + type :: solver_base + ! 基本组件 + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + + ! 状态管理 + integer :: state = SOLVER_UNINITIALIZED + character(len=100) :: error_message = "" + real(wp) :: current_time = 0.0_wp + integer(ip) :: current_step = 0 + + ! 时间控制 + real(wp) :: dt_original = 0.0_wp + contains + procedure :: initialize => solver_base_initialize + procedure :: step => solver_base_step + procedure :: run_to_time => solver_base_run_to_time + procedure :: cleanup => solver_base_cleanup + procedure :: get_state => solver_base_get_state + procedure :: get_error => solver_base_get_error + procedure :: print_info => solver_base_print_info + end type solver_base + + ! 构造函数接口 + interface solver_base + module procedure create_solver_base + end interface + +contains + + ! ==================== 构造函数 ==================== + + function create_solver_base(config, mesh) result(solver) + type(cfd_config), intent(in) :: config + type(mesh_type), intent(in) :: mesh + type(solver_base) :: solver + + solver%config = config + solver%mesh = mesh + + ! 创建域 + solver%domain = domain_create(config, mesh) + + ! 创建解 + solver%solution = solution_create(solver%domain) + + ! 保存原始时间步长 + solver%dt_original = config%dt + + if (config%verbose) then + print *, "[SOLVER] Base solver created" + print *, " Mesh cells: ", mesh%ncells + print *, " Domain total cells: ", solver%domain%ntcells + end if + end function create_solver_base + + ! ==================== 初始化 ==================== + + subroutine solver_base_initialize(this) + class(solver_base), intent(inout) :: this + + if (this%state == SOLVER_INITIALIZED) then + if (this%config%verbose) then + print *, "[SOLVER] Already initialized" + end if + return + end if + + ! 初始化解(通过配置) + ! 这里暂时简化,实际需要调用初始条件工厂 + print *, "[INFO] Base solver initialized (simplified)" + + ! 更新状态 + this%state = SOLVER_INITIALIZED + this%current_time = 0.0_wp + this%current_step = 0 + + if (this%config%verbose) then + print *, "[SOLVER] Initialized at t = ", this%current_time + end if + end subroutine solver_base_initialize + + ! ==================== 单步计算(虚方法) ==================== + + subroutine solver_base_step(this, dt) + class(solver_base), intent(inout) :: this + real(wp), intent(in) :: dt + + ! 基类中这只是虚方法,需要在子类中实现 + print *, "[INFO] Base solver step (virtual method)" + print *, " dt = ", dt + print *, " t = ", this%current_time + + ! 更新时间 + this%current_time = this%current_time + dt + this%current_step = this%current_step + 1 + + ! 简单模拟:只是更新状态 + if (this%config%verbose) then + print *, "[SOLVER] Step completed: t = ", this%current_time, & + ", step = ", this%current_step + end if + end subroutine solver_base_step + + ! ==================== 运行到指定时间 ==================== + + subroutine solver_base_run_to_time(this, final_time) + class(solver_base), intent(inout) :: this + real(wp), intent(in) :: final_time + + real(wp) :: dt, t_remaining + integer :: step_count + + if (this%state /= SOLVER_INITIALIZED) then + this%error_message = "Solver not initialized" + this%state = SOLVER_ERROR + if (this%config%verbose) then + print *, "[SOLVER BASE ERROR] Not initialized: ", trim(this%error_message) + end if + return + end if + + this%state = SOLVER_RUNNING + step_count = 0 + + if (this%config%verbose) then + print *, "[SOLVER BASE] Running from t = ", this%current_time, & + " to t = ", final_time + print *, " Time step: ", this%config%dt + end if + + do while (this%current_time < final_time - 1e-12_wp) + ! 计算时间步长 + t_remaining = final_time - this%current_time + dt = min(this%config%dt, t_remaining) + + ! 执行时间步 + call this%step(dt) + + step_count = step_count + 1 + + ! 每50步输出一次进度 + if (mod(step_count, 50) == 0 .and. this%config%verbose) then + print *, "[SOLVER BASE] Progress: t = ", this%current_time, & + " / ", final_time, " (step ", step_count, ")" + end if + end do + + ! 恢复原始时间步长 + this%config%dt = this%dt_original + + ! 更新状态 + this%state = SOLVER_COMPLETED + + if (this%config%verbose) then + print *, "[SOLVER BASE] Run completed:" + print *, " Final time: ", this%current_time + print *, " Total steps: ", this%current_step + print *, " State: ", this%state + end if + end subroutine solver_base_run_to_time + + ! ==================== 清理 ==================== + + subroutine solver_base_cleanup(this) + class(solver_base), intent(inout) :: this + + ! 重置状态 + this%state = SOLVER_UNINITIALIZED + this%current_time = 0.0_wp + this%current_step = 0 + this%error_message = "" + + if (this%config%verbose) then + print *, "[SOLVER] Cleaned up" + end if + end subroutine solver_base_cleanup + + ! ==================== 状态查询 ==================== + + function solver_base_get_state(this) result(state) + class(solver_base), intent(in) :: this + integer :: state + state = this%state + end function solver_base_get_state + + function solver_base_get_error(this) result(error_msg) + class(solver_base), intent(in) :: this + character(len=100) :: error_msg + error_msg = trim(this%error_message) + end function solver_base_get_error + + ! ==================== 信息打印 ==================== + + subroutine solver_base_print_info(this) + class(solver_base), intent(in) :: this + + character(len=20) :: state_str + + ! 状态字符串 + select case (this%state) + case (SOLVER_UNINITIALIZED) + state_str = "Uninitialized" + case (SOLVER_INITIALIZED) + state_str = "Initialized" + case (SOLVER_RUNNING) + state_str = "Running" + case (SOLVER_COMPLETED) + state_str = "Completed" + case (SOLVER_ERROR) + state_str = "Error" + case default + state_str = "Unknown" + end select + + print *, "=== Solver Information ===" + print *, "State: ", trim(state_str) + print *, "Current time: ", this%current_time + print *, "Current step: ", this%current_step + print *, "Error message: '", trim(this%error_message), "'" + + ! 配置信息 + print *, "Configuration:" + print *, " Scheme: ", trim(this%config%recon_scheme) + print *, " Order: ", this%config%spatial_order + print *, " dt: ", this%config%dt + + ! 域信息 + print *, "Domain:" + print *, " Ghost layers: ", this%domain%nghosts + print *, " Physical cells: ", this%domain%ist, " to ", this%domain%ied - 1 + + print *, "=========================" + end subroutine solver_base_print_info + +end module solver_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/src/solver/physics_solver.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/src/solver/physics_solver.f90 new file mode 100644 index 000000000..8701dc298 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/src/solver/physics_solver.f90 @@ -0,0 +1,518 @@ +! src/solver/physics_solver.f90 (简化版) +module physics_solver_module + use base_modules, only: wp => wp, ip => ip + use config_module, only: cfd_config + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create + + use physics_interface, only: physics_equation, physics_problem + use linear_convection_equation, only: linear_convection_eq, create_linear_convection_eq + use linear_convection_problem, only: linear_convection_prob, create_linear_convection_prob + + use component_manager_module, only: create_reconstructor, create_flux_calculator + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + + implicit none + private + + ! 明确导出列表 + public :: wp, ip, physics_solver, create_physics_solver + public :: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, SOLVER_RUNNING + public :: SOLVER_COMPLETED, SOLVER_ERROR + + ! 求解器状态枚举 + integer, parameter :: SOLVER_UNINITIALIZED = 0 + integer, parameter :: SOLVER_INITIALIZED = 1 + integer, parameter :: SOLVER_RUNNING = 2 + integer, parameter :: SOLVER_COMPLETED = 3 + integer, parameter :: SOLVER_ERROR = 4 + + ! 物理求解器类型(不继承,独立实现) + type :: physics_solver + ! 基本组件 + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + + ! 物理组件 + class(physics_equation), allocatable :: equation + class(physics_problem), allocatable :: problem + + ! 数值组件 + class(reconstructor_base), allocatable :: reconstructor + class(flux_calculator_base), allocatable :: flux_calculator + + ! 状态管理 + integer :: state = SOLVER_UNINITIALIZED + character(len=100) :: error_message = "" + real(wp) :: current_time = 0.0_wp + integer(ip) :: current_step = 0 + + ! 时间控制 + real(wp) :: dt_original = 0.0_wp + logical :: physics_initialized = .false. + contains + procedure :: initialize => physics_solver_initialize + procedure :: step => physics_solver_step + procedure :: run_to_time => physics_solver_run_to_time + procedure :: cleanup => physics_solver_cleanup + procedure :: get_state => physics_solver_get_state + procedure :: get_error => physics_solver_get_error + procedure :: print_info => physics_solver_print_info + procedure, private :: create_physics_components + procedure, private :: create_numerical_components + end type physics_solver + + ! 构造函数接口 + interface physics_solver + module procedure create_physics_solver + end interface + +contains + + ! ==================== 构造函数 ==================== + + function create_physics_solver(config, mesh) result(solver) + type(cfd_config), intent(in) :: config + type(mesh_type), intent(in) :: mesh + type(physics_solver) :: solver + + solver%config = config + solver%mesh = mesh + + ! 创建域 + solver%domain = domain_create(config, mesh) + + ! 创建解 + solver%solution = solution_create(solver%domain) + + ! 保存原始时间步长 + solver%dt_original = config%dt + + ! 创建组件 + call solver%create_physics_components() + call solver%create_numerical_components() + + if (config%verbose) then + print *, "[PHYSICS SOLVER] Created:" + print *, " Mesh cells: ", mesh%ncells + print *, " Domain total cells: ", solver%domain%ntcells + end if + end function create_physics_solver + + + function get_physical_solution(this) result(u_physical) + class(physics_solver), intent(in) :: this + real(wp), allocatable :: u_physical(:) + + integer :: i, n_physical + + n_physical = this%mesh%ncells + allocate(u_physical(n_physical)) + + do i = 1, n_physical + u_physical(i) = this%solution%u(this%domain%ist + i - 1) + end do + end function get_physical_solution + + ! ==================== 创建物理组件 ==================== + + subroutine create_physics_components(this) + class(physics_solver), intent(inout) :: this + + ! 检查是否启用物理模块 + if (.not. this%config%enable_physics) then + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Physics module disabled" + end if + return + end if + + ! 创建物理方程 + select case (trim(this%config%equation_type)) + case ("linear_advection") + allocate(linear_convection_eq :: this%equation) + select type(eq => this%equation) + type is(linear_convection_eq) + eq = create_linear_convection_eq(wave_speed=this%config%wave_speed) + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Created linear convection equation" + print *, " Wave speed: ", eq%wave_speed + end if + end select + + case default + if (this%config%verbose) then + print *, "[WARNING] Unknown equation type: ", trim(this%config%equation_type) + print *, " Using linear convection as default" + end if + + allocate(linear_convection_eq :: this%equation) + select type(eq => this%equation) + type is(linear_convection_eq) + eq = create_linear_convection_eq(wave_speed=this%config%wave_speed) + end select + end select + + ! 创建物理问题 + select case (trim(this%config%problem_type)) + case ("linear_advection") + allocate(linear_convection_prob :: this%problem) + select type(prob => this%problem) + type is(linear_convection_prob) + prob = create_linear_convection_prob( & + wave_speed=this%config%wave_speed, & + domain_length=this%config%domain_length, & + ic_type=this%config%ic_type, & + boundary_type=this%config%boundary_type) + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Created linear convection problem" + print *, " IC type: ", trim(prob%ic_type) + end if + end select + + case default + if (this%config%verbose) then + print *, "[WARNING] Unknown problem type: ", trim(this%config%problem_type) + print *, " Using linear convection as default" + end if + + allocate(linear_convection_prob :: this%problem) + select type(prob => this%problem) + type is(linear_convection_prob) + prob = create_linear_convection_prob( & + wave_speed=this%config%wave_speed, & + domain_length=this%config%domain_length, & + ic_type=this%config%ic_type, & + boundary_type=this%config%boundary_type) + end select + end select + + this%physics_initialized = .true. + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Physics components created" + end if + end subroutine create_physics_components + + ! ==================== 创建数值组件 ==================== + + subroutine create_numerical_components(this) + class(physics_solver), intent(inout) :: this + integer :: status + + ! 创建重构器 + this%reconstructor = create_reconstructor(this%config, status) + if (status /= 0) then + print *, "[ERROR] Failed to create reconstructor" + this%state = SOLVER_ERROR + this%error_message = "Failed to create reconstructor" + return + end if + + ! 创建通量计算器 + this%flux_calculator = create_flux_calculator(this%config, status) + if (status /= 0) then + print *, "[ERROR] Failed to create flux calculator" + this%state = SOLVER_ERROR + this%error_message = "Failed to create flux calculator" + return + end if + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Numerical components created" + end if + end subroutine create_numerical_components + + ! ==================== 初始化 ==================== + + subroutine physics_solver_initialize(this) + class(physics_solver), intent(inout) :: this + + if (this%state == SOLVER_INITIALIZED) then + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Already initialized" + end if + return + end if + + ! 如果启用了物理模块,应用初始条件 + if (this%physics_initialized .and. allocated(this%problem)) then + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Applying initial condition" + end if + + select type(prob => this%problem) + type is(linear_convection_prob) + ! 获取网格单元中心坐标 + call prob%initial_condition(this%mesh%xcc, & + this%solution%u(this%domain%ist:this%domain%ied-1)) + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Initial condition applied" + end if + end select + else + ! 简化的初始化:阶跃函数 + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Using simplified initialization" + end if + + ! 在 [0.5, 1.0] 区域内设为 2.0,其他区域为 1.0 + where (this%mesh%xcc >= 0.5_wp .and. this%mesh%xcc <= 1.0_wp) + this%solution%u(this%domain%ist:this%domain%ied-1) = 2.0_wp + elsewhere + this%solution%u(this%domain%ist:this%domain%ied-1) = 1.0_wp + end where + end if + + ! 同步旧场 + call this%solution%update_old_field() + + ! 更新状态 + this%state = SOLVER_INITIALIZED + this%current_time = 0.0_wp + this%current_step = 0 + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Initialized at t = ", this%current_time + end if + end subroutine physics_solver_initialize + + ! ==================== 时间步进 ==================== + + subroutine physics_solver_step(this, dt) + class(physics_solver), intent(inout) :: this + real(wp), intent(in) :: dt + + integer :: i + real(wp) :: u_val, f_val + + if (this%config%verbose .and. mod(this%current_step, 100) == 0) then + print *, "[PHYSICS SOLVER] Step ", this%current_step + 1, & + " dt = ", dt, " t = ", this%current_time + end if + + ! 更新旧场 + call this%solution%update_old_field() + + ! 简化的数值方法 + do i = this%domain%ist, this%domain%ied - 1 + u_val = this%solution%un(i) ! 使用旧值 + + ! 简单的线性对流:u_t + a*u_x = 0 + ! 使用一阶迎风格式 + this%solution%u(i) = u_val - dt * this%config%wave_speed * & + (u_val - this%solution%un(i-1)) / this%mesh%dx + end do + + ! 更新时间 + this%current_time = this%current_time + dt + this%current_step = this%current_step + 1 + + ! 每100步输出一次进度 + if (this%config%verbose .and. mod(this%current_step, 100) == 0) then + print *, "[PHYSICS SOLVER] Step ", this%current_step, & + " completed, t = ", this%current_time + end if + end subroutine physics_solver_step + + ! ==================== 运行到指定时间 ==================== + + subroutine physics_solver_run_to_time(this, final_time) + class(physics_solver), intent(inout) :: this + real(wp), intent(in) :: final_time + + real(wp) :: dt, t_remaining + integer :: step_count + + if (this%state /= SOLVER_INITIALIZED) then + this%error_message = "Solver not initialized" + this%state = SOLVER_ERROR + if (this%config%verbose) then + print *, "[PHYSICS SOLVER ERROR] Not initialized" + end if + return + end if + + this%state = SOLVER_RUNNING + step_count = 0 + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Running from t = ", this%current_time, & + " to t = ", final_time + end if + + do while (this%current_time < final_time - 1e-12_wp) + ! 计算时间步长 + t_remaining = final_time - this%current_time + dt = min(this%config%dt, t_remaining) + + ! 执行时间步 + call this%step(dt) + + step_count = step_count + 1 + + ! 每100步输出一次进度 + if (mod(step_count, 100) == 0 .and. this%config%verbose) then + print *, "[PHYSICS SOLVER] Progress: t = ", this%current_time, & + " / ", final_time + end if + end do + + ! 恢复原始时间步长 + this%config%dt = this%dt_original + + ! 更新状态 + this%state = SOLVER_COMPLETED + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Run completed:" + print *, " Final time: ", this%current_time + print *, " Total steps: ", this%current_step + print *, " Final u range: ", minval(this%solution%u), " to ", maxval(this%solution%u) + end if + end subroutine physics_solver_run_to_time + + ! ==================== 清理 ==================== + + subroutine physics_solver_cleanup(this) + class(physics_solver), intent(inout) :: this + + integer :: old_state + real(wp) :: old_time + integer(ip) :: old_step + + ! 保存清理前的状态用于调试 + old_state = this%state + old_time = this%current_time + old_step = this%current_step + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Cleaning up..." + print *, " Before cleanup - State: ", old_state + print *, " Before cleanup - Time: ", old_time + print *, " Before cleanup - Steps: ", old_step + end if + + ! 清理物理组件 + if (allocated(this%equation)) then + deallocate(this%equation) + if (this%config%verbose) then + print *, " Deallocated equation" + end if + end if + + if (allocated(this%problem)) then + deallocate(this%problem) + if (this%config%verbose) then + print *, " Deallocated problem" + end if + end if + + ! 清理数值组件 + if (allocated(this%reconstructor)) then + deallocate(this%reconstructor) + if (this%config%verbose) then + print *, " Deallocated reconstructor" + end if + end if + + if (allocated(this%flux_calculator)) then + deallocate(this%flux_calculator) + if (this%config%verbose) then + print *, " Deallocated flux calculator" + end if + end if + + ! 重置状态 - 但不重置解数组(保持分配) + this%state = SOLVER_UNINITIALIZED + this%current_time = 0.0_wp + this%current_step = 0 + this%error_message = "" + this%physics_initialized = .false. + + if (this%config%verbose) then + print *, " After cleanup - State: ", this%state + print *, " After cleanup - Time: ", this%current_time + print *, " After cleanup - Steps: ", this%current_step + print *, "[PHYSICS SOLVER] Cleanup completed" + end if + + end subroutine physics_solver_cleanup + + ! ==================== 状态查询 ==================== + + function physics_solver_get_state(this) result(state) + class(physics_solver), intent(in) :: this + integer :: state + state = this%state + end function physics_solver_get_state + + function physics_solver_get_error(this) result(error_msg) + class(physics_solver), intent(in) :: this + character(len=100) :: error_msg + error_msg = trim(this%error_message) + end function physics_solver_get_error + + ! ==================== 信息打印 ==================== + + subroutine physics_solver_print_info(this) + class(physics_solver), intent(in) :: this + + character(len=20) :: state_str + + ! 状态字符串 + select case (this%state) + case (SOLVER_UNINITIALIZED) + state_str = "Uninitialized" + case (SOLVER_INITIALIZED) + state_str = "Initialized" + case (SOLVER_RUNNING) + state_str = "Running" + case (SOLVER_COMPLETED) + state_str = "Completed" + case (SOLVER_ERROR) + state_str = "Error" + case default + write(state_str, '(A, I3)') "Unknown ", this%state + end select + + print *, "=== Physics Solver Information ===" + print *, "State: ", trim(state_str), " (", this%state, ")" + print *, "Current time: ", this%current_time + print *, "Current step: ", this%current_step + print *, "Error message: '", trim(this%error_message), "'" + + ! 配置信息 + print *, "Configuration:" + print *, " Scheme: ", trim(this%config%recon_scheme) + print *, " Order: ", this%config%spatial_order + print *, " dt: ", this%config%dt + print *, " Final time: ", this%config%final_time + + ! 域信息 + print *, "Domain:" + print *, " Ghost layers: ", this%domain%nghosts + print *, " Physical cells: ", this%domain%ist, " to ", this%domain%ied - 1 + + ! 物理信息 + print *, "Physics:" + print *, " Initialized: ", this%physics_initialized + print *, " Equation type: ", trim(this%config%equation_type) + print *, " Problem type: ", trim(this%config%problem_type) + + ! 解信息 + if (allocated(this%solution%u)) then + print *, "Solution:" + print *, " u size: ", size(this%solution%u) + print *, " u physical size: ", this%domain%ied - this%domain%ist + end if + + print *, "===================================" + end subroutine physics_solver_print_info + +end module physics_solver_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/CMakeLists.txt new file mode 100644 index 000000000..b609e28d9 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/CMakeLists.txt @@ -0,0 +1,106 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +# 基础设施测试 +add_executable(test_infrastructure test_infrastructure.f90) +target_link_libraries(test_infrastructure + PRIVATE + infrastructure + core +) + +# 注册系统测试 +add_executable(test_registry test_registry.f90) +target_link_libraries(test_registry + PRIVATE + core + infrastructure +) + +# 物理模块测试 +add_executable(test_physics test_physics.f90) +target_link_libraries(test_physics + PRIVATE + physics + base +) + +# 组件管理器测试 +add_executable(test_component_manager test_component_manager.f90) +target_link_libraries(test_component_manager + PRIVATE + manager + infrastructure +) + +# 配置物理测试 +add_executable(test_config_physics test_config_physics.f90) +target_link_libraries(test_config_physics + PRIVATE + infrastructure + core +) + +# 求解器基础测试 +add_executable(test_solver_base test_solver_base.f90) +target_link_libraries(test_solver_base + PRIVATE + solver + infrastructure + core +) + +# 物理求解器测试 +add_executable(test_physics_solver test_physics_solver.f90) +target_link_libraries(test_physics_solver + PRIVATE + solver + infrastructure + core + physics + manager +) + +# 新增:简单物理求解器测试 +add_executable(test_physics_solver_simple test_physics_solver_simple.f90) +target_link_libraries(test_physics_solver_simple + PRIVATE + solver + infrastructure + core + physics + manager +) + +add_executable(test_simple_link test_simple_link.f90) +target_link_libraries(test_simple_link + PRIVATE + reconstructor + flux +) + + +add_executable(test_factory_simple test_factory_simple.f90) +target_link_libraries(test_factory_simple + PRIVATE + core + infrastructure + reconstructor + flux +) + +add_executable(test_domain_solution test_domain_solution.f90) +target_link_libraries(test_domain_solution + PRIVATE + infrastructure + core +) + +add_executable(test_component_manager_physics test_component_manager_physics.f90) +target_link_libraries(test_component_manager_physics + PRIVATE + manager + infrastructure + physics + core +) diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_architecture.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_architecture.f90 new file mode 100644 index 000000000..1c40d4677 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_architecture.f90 @@ -0,0 +1,117 @@ +! tests/test_architecture.f90 +program test_architecture + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module + use config_module + use mesh_module + use component_manager_module + + implicit none + + print *, "=== CFD架构测试 ===" + print *, "" + + ! 测试1: 基本系统 + call test_basic_systems() + + ! 测试2: 组件注册 + call test_registry_integration() + + ! 测试3: 配置验证 + call test_config_validation() + + ! 测试4: 完整流程 + call test_full_workflow() + + print *, "" + print *, "=== 架构测试完成 ===" + +contains + + subroutine test_basic_systems() + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "1. 测试基本系统..." + print *, "-------------------" + + ! 测试配置 + call config_print(config) + print *, "✓ 配置创建成功" + + ! 测试网格 + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "✓ 网格创建成功" + print *, "" + end subroutine test_basic_systems + + subroutine test_registry_integration() + print *, "2. 测试注册系统集成..." + print *, "------------------------" + + call initialize_registry(verbose=.true.) + + ! 注册核心组件 + print *, "注册核心组件:" + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + ! 显示注册内容 + call component_registry%list_simple() + print *, "注册表大小: ", registry_get_size() + + call cleanup_registry() + print *, "✓ 注册系统测试完成" + print *, "" + end subroutine test_registry_integration + + subroutine test_config_validation() + type(cfd_config) :: config + logical :: is_valid + + print *, "3. 测试配置验证..." + print *, "-------------------" + + ! 测试有效配置 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + print *, "测试配置:" + print *, " - 重构器: ", trim(config%recon_scheme), " 阶数: ", config%spatial_order + print *, " - 通量: ", trim(config%flux_type) + print *, " - 波速: ", config%wave_speed + + ! 这里调用验证函数(需要先实现) + ! is_valid = validate_config(config) + print *, "✓ 配置验证接口准备就绪" + print *, "" + end subroutine test_config_validation + + subroutine test_full_workflow() + print *, "4. 测试完整流程..." + print *, "-------------------" + + print *, "步骤1: 初始化系统 ✓" + print *, "步骤2: 创建网格 ✓" + print *, "步骤3: 设置配置 ✓" + print *, "步骤4: 创建组件 (待实现)" + print *, "步骤5: 初始化解 (待实现)" + print *, "步骤6: 时间推进 (待实现)" + print *, "步骤7: 输出结果 (待实现)" + print *, "" + + print *, "✓ 流程框架定义完成" + end subroutine test_full_workflow + +end program test_architecture \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_cfd_architecture.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_cfd_architecture.f90 new file mode 100644 index 000000000..1c40d4677 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_cfd_architecture.f90 @@ -0,0 +1,117 @@ +! tests/test_architecture.f90 +program test_architecture + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module + use config_module + use mesh_module + use component_manager_module + + implicit none + + print *, "=== CFD架构测试 ===" + print *, "" + + ! 测试1: 基本系统 + call test_basic_systems() + + ! 测试2: 组件注册 + call test_registry_integration() + + ! 测试3: 配置验证 + call test_config_validation() + + ! 测试4: 完整流程 + call test_full_workflow() + + print *, "" + print *, "=== 架构测试完成 ===" + +contains + + subroutine test_basic_systems() + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "1. 测试基本系统..." + print *, "-------------------" + + ! 测试配置 + call config_print(config) + print *, "✓ 配置创建成功" + + ! 测试网格 + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "✓ 网格创建成功" + print *, "" + end subroutine test_basic_systems + + subroutine test_registry_integration() + print *, "2. 测试注册系统集成..." + print *, "------------------------" + + call initialize_registry(verbose=.true.) + + ! 注册核心组件 + print *, "注册核心组件:" + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + ! 显示注册内容 + call component_registry%list_simple() + print *, "注册表大小: ", registry_get_size() + + call cleanup_registry() + print *, "✓ 注册系统测试完成" + print *, "" + end subroutine test_registry_integration + + subroutine test_config_validation() + type(cfd_config) :: config + logical :: is_valid + + print *, "3. 测试配置验证..." + print *, "-------------------" + + ! 测试有效配置 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + print *, "测试配置:" + print *, " - 重构器: ", trim(config%recon_scheme), " 阶数: ", config%spatial_order + print *, " - 通量: ", trim(config%flux_type) + print *, " - 波速: ", config%wave_speed + + ! 这里调用验证函数(需要先实现) + ! is_valid = validate_config(config) + print *, "✓ 配置验证接口准备就绪" + print *, "" + end subroutine test_config_validation + + subroutine test_full_workflow() + print *, "4. 测试完整流程..." + print *, "-------------------" + + print *, "步骤1: 初始化系统 ✓" + print *, "步骤2: 创建网格 ✓" + print *, "步骤3: 设置配置 ✓" + print *, "步骤4: 创建组件 (待实现)" + print *, "步骤5: 初始化解 (待实现)" + print *, "步骤6: 时间推进 (待实现)" + print *, "步骤7: 输出结果 (待实现)" + print *, "" + + print *, "✓ 流程框架定义完成" + end subroutine test_full_workflow + +end program test_architecture \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_component_manager.f90 new file mode 100644 index 000000000..f60c35056 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_component_manager.f90 @@ -0,0 +1,111 @@ +! tests/test_component_manager.f90 +program test_component_manager + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use component_manager_module, only: create_reconstructor, create_flux_calculator + use component_manager_module, only: component_manager_info, validate_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + implicit none + + type(cfd_config) :: config + class(reconstructor_base), allocatable :: recon + class(flux_calculator_base), allocatable :: flux + integer :: status + logical :: is_valid + + print *, "=== Component Manager Test ===" + print *, "" + + ! 显示组件管理器信息 + call component_manager_info() + print *, "" + + ! 测试1: 基本配置 + print *, "1. Testing basic ENO3 + Rusanov configuration..." + print *, "-----------------------------------------------" + + config%verbose = .true. + call config_print(config) + + ! 配置ENO3重构 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + call config_print(config) + print *, "" + + ! 验证配置 + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Configuration is valid" + else + print *, "[ERROR] Configuration is invalid" + end if + print *, "" + + ! 测试2: 创建组件 + print *, "2. Testing component creation..." + print *, "--------------------------------" + + ! 创建重构器(带状态检查) + recon = create_reconstructor(config, status) + if (status == 0) then + print *, "[OK] Reconstructor created successfully" + call recon%info() + else + print *, "[ERROR] Failed to create reconstructor, code:", status + end if + print *, "" + + ! 创建通量计算器 + flux = create_flux_calculator(config, status) + if (status == 0) then + print *, "[OK] Flux calculator created successfully" + call flux%info() + else + print *, "[ERROR] Failed to create flux calculator, code:", status + end if + print *, "" + + ! 测试3: WENO3重构测试 + print *, "3. Testing WENO3 configuration..." + print *, "---------------------------------" + + call config_with_reconstruction(config, "weno3", 3) + + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] WENO3 configuration is valid" + + ! 创建WENO3重构器 + recon = create_reconstructor(config) + call recon%info() + else + print *, "[ERROR] WENO3 configuration is invalid" + end if + print *, "" + + ! 测试4: 错误配置测试 + print *, "4. Testing invalid configuration..." + print *, "-----------------------------------" + + config%recon_scheme = "unknown_scheme" + config%flux_type = "unknown_flux" + + is_valid = validate_config(config) + if (.not. is_valid) then + print *, "[OK] Invalid configuration correctly rejected" + else + print *, "[ERROR] Invalid configuration should have been rejected" + end if + + ! 清理 + if (allocated(recon)) deallocate(recon) + if (allocated(flux)) deallocate(flux) + + print *, "" + print *, "=== Component manager test completed successfully ===" + +end program test_component_manager \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_component_manager_physics.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_component_manager_physics.f90 new file mode 100644 index 000000000..f2becca95 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_component_manager_physics.f90 @@ -0,0 +1,120 @@ +! tests/test_component_manager_physics.f90 (简化版) +program test_component_manager_physics + use base_modules, only: wp + use config_module, only: cfd_config, config_print, config_with_reconstruction + use component_manager_module, only: component_manager_info, validate_config + + implicit none + + type(cfd_config) :: config + logical :: is_valid + + print *, "=== Component Manager Physics Test (Simplified) ===" + print *, "" + + ! 测试1: 显示组件管理器信息 + print *, "1. Testing component manager info..." + print *, "-------------------------------------" + call component_manager_info() + print *, "" + + ! 测试2: 物理模块测试(默认) + print *, "2. Testing physics module with default configuration..." + print *, "------------------------------------------------------" + + config%verbose = .true. + call config_print(config) + + ! 验证配置 + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Default configuration is valid" + else + print *, "[ERROR] Default configuration is invalid" + end if + print *, "" + + ! 测试3: 测试物理配置 + print *, "3. Testing physics configuration..." + print *, "------------------------------------" + + ! 修改物理参数 + config%equation_type = "linear_advection" + config%problem_type = "linear_advection" + config%wave_speed = 2.5_wp + config%domain_length = 3.0_wp + + print *, "Modified physics configuration:" + print *, " Equation type: ", trim(config%equation_type) + print *, " Problem type: ", trim(config%problem_type) + print *, " Wave speed: ", config%wave_speed + print *, " Domain length: ", config%domain_length + print *, " Physics enabled: ", config%enable_physics + + ! 验证修改后的配置 + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Modified physics configuration is valid" + else + print *, "[ERROR] Modified physics configuration is invalid" + end if + print *, "" + + ! 测试4: 数值组件测试 + print *, "4. Testing numerical components with physics..." + print *, "-----------------------------------------------" + + call config_with_reconstruction(config, "weno3", 3) + config%flux_type = "rusanov" + + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Combined physics+numerics configuration is valid" + else + print *, "[ERROR] Combined configuration is invalid" + end if + print *, "" + + ! 测试5: 物理模块禁用测试 + print *, "5. Testing physics module disabled..." + print *, "---------------------------------------" + + config%enable_physics = .false. + config%verbose = .false. + + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Configuration valid even with physics disabled" + else + print *, "[ERROR] Configuration should be valid with physics disabled" + end if + print *, "" + + ! 测试6: 错误配置测试 + print *, "6. Testing error handling..." + print *, "-----------------------------" + + config%verbose = .true. + config%enable_physics = .true. + config%recon_scheme = "unknown_scheme" + config%flux_type = "unknown_flux" + config%equation_type = "unknown_equation" + config%problem_type = "unknown_problem" + + is_valid = validate_config(config) + if (.not. is_valid) then + print *, "[OK] Invalid configuration correctly rejected" + else + print *, "[ERROR] Invalid configuration should have been rejected" + end if + print *, "" + + print *, "=== Component Manager Physics Test Summary ===" + print *, "✓ Component manager info works" + print *, "✓ Configuration validation works with physics" + print *, "✓ Error handling works correctly" + print *, "✓ Combined physics+numerics validation works" + print *, "" + print *, "下一步: 集成物理模块到求解器框架" + +end program test_component_manager_physics \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_config_physics.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_config_physics.f90 new file mode 100644 index 000000000..c6fef5c0b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_config_physics.f90 @@ -0,0 +1,141 @@ +! tests/test_config_physics.f90 (修复版) +program test_config_physics + use base_modules, only: wp + use config_module, only: cfd_config, config_print, config_with_reconstruction + + implicit none + + type(cfd_config) :: config + + print *, "=== Configuration Physics Test (Simplified) ===" + print *, "" + + ! 测试1: 默认配置 + print *, "1. Testing default configuration..." + print *, "-----------------------------------" + call config_print(config) + print *, "" + + ! 测试2: 验证基础物理字段 + print *, "2. Testing basic physics fields..." + print *, "----------------------------------" + + print *, "Verifying default physics fields:" + + if (trim(config%equation_type) == "linear_advection") then + print *, " ✓ Default equation type: linear_advection" + else + print *, " ✗ Unexpected equation type: ", trim(config%equation_type) + end if + + if (trim(config%problem_type) == "linear_advection") then + print *, " ✓ Default problem type: linear_advection" + else + print *, " ✗ Unexpected problem type: ", trim(config%problem_type) + end if + + if (abs(config%domain_length - 2.0_wp) < 1e-10_wp) then + print *, " ✓ Default domain length: 2.0" + else + print *, " ✗ Unexpected domain length: ", config%domain_length + end if + + if (config%enable_physics) then + print *, " ✓ Physics enabled by default" + else + print *, " ✗ Physics not enabled by default" + end if + + print *, "" + + ! 测试3: 使用类型绑定的方法(正确的方法名) + print *, "3. Testing type-bound procedures..." + print *, "--------------------------------------" + + call config%set_physics_parameters( & + equation_type="burgers_equation", & + problem_type="sod_shock_tube", & + domain_length=3.0_wp, & + enable_physics=.false.) + + print *, "After set_physics_parameters:" + print *, " Equation type: ", trim(config%equation_type) + print *, " Problem type: ", trim(config%problem_type) + print *, " Domain length: ", config%domain_length + print *, " Physics enabled: ", config%enable_physics + + if (trim(config%equation_type) == "burgers_equation") then + print *, " ✓ Equation type modified successfully via set_physics_parameters" + end if + + if (trim(config%problem_type) == "sod_shock_tube") then + print *, " ✓ Problem type modified successfully via set_physics_parameters" + end if + + if (abs(config%domain_length - 3.0_wp) < 1e-10_wp) then + print *, " ✓ Domain length modified successfully via set_physics_parameters" + end if + + if (.not. config%enable_physics) then + print *, " ✓ Physics disabled successfully via set_physics_parameters" + end if + + print *, "" + + ! 测试4: 调用get_physics_info方法 + print *, "4. Testing get_physics_info method..." + print *, "--------------------------------------" + call config%get_physics_info() + print *, "" + + ! 测试5: 高斯脉冲配置 + print *, "5. Testing Gaussian pulse configuration..." + print *, "-----------------------------------------" + + config%ic_type = "gaussian" + config%pulse_center = 0.6_wp + config%pulse_width = 0.15_wp + + print *, "Gaussian pulse parameters:" + print *, " IC type: ", trim(config%ic_type) + print *, " Center: ", config%pulse_center + print *, " Width: ", config%pulse_width + + if (trim(config%ic_type) == "gaussian") then + print *, " ✓ Gaussian IC type set" + end if + + if (abs(config%pulse_center - 0.6_wp) < 1e-10_wp) then + print *, " ✓ Pulse center set" + end if + + if (abs(config%pulse_width - 0.15_wp) < 1e-10_wp) then + print *, " ✓ Pulse width set" + end if + + print *, "" + + ! 测试6: 重构配置 + print *, "6. Testing reconstruction configuration..." + print *, "------------------------------------------" + + call config_with_reconstruction(config, "weno", 5) + + print *, "Reconstruction configuration:" + print *, " Scheme: ", trim(config%recon_scheme) + print *, " Order: ", config%spatial_order + + if (trim(config%recon_scheme) == "weno" .and. config%spatial_order == 5) then + print *, " ✓ WENO5 configuration successful" + else + print *, " ✗ Reconstruction configuration failed" + end if + + print *, "" + + print *, "=== Configuration Physics Test Complete ===" + print *, "✓ Config module updated with physics support" + print *, "✓ Fields can be directly accessed and modified" + print *, "✓ Type-bound procedures work correctly" + +end program test_config_physics \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_domain_solution.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_domain_solution.f90 new file mode 100644 index 000000000..ff659bac2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_domain_solution.f90 @@ -0,0 +1,102 @@ +! tests/test_domain_solution.f90 +program test_domain_solution + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create, solution_reset + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + real(wp), allocatable :: initial_values(:) + integer :: i + + print *, "=== Domain and Solution Test ===" + print *, "" + + ! 测试1: 不同重构方案的ghost层计算 + print *, "1. Testing ghost layer calculation..." + print *, "--------------------------------------" + + ! ENO3 + call config_with_reconstruction(config, "eno", 3) + config%verbose = .false. + call mesh%init(ncells=10) + domain = domain_create(config, mesh) + print *, "ENO3: nghosts = ", domain%nghosts, " (expected: 3)" + + ! WENO3 + call config_with_reconstruction(config, "weno3", 3) + domain = domain_create(config, mesh) + print *, "WENO3: nghosts = ", domain%nghosts, " (expected: 2)" + + ! WENO5 + call config_with_reconstruction(config, "weno", 5) + domain = domain_create(config, mesh) + print *, "WENO5: nghosts = ", domain%nghosts, " (expected: 3)" + print *, "" + + ! 测试2: Solution数组 + print *, "2. Testing solution arrays..." + print *, "------------------------------" + + call config_with_reconstruction(config, "eno", 3) + config%verbose = .true. + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=10) + + domain = domain_create(config, mesh) + call domain%print_info() + print *, "" + + solution = solution_create(domain) + call solution%print_info() + print *, "" + + ! 测试3: 初始化和更新 + print *, "3. Testing initialization and update..." + print *, "----------------------------------------" + + allocate(initial_values(mesh%ncells)) + do i = 1, mesh%ncells + initial_values(i) = sin(2.0_wp * 3.14159265358979_wp * mesh%xcc(i) / mesh%L) + end do + + call solution%initialize(initial_values) + print *, "After initialization:" + print *, " u range: ", minval(solution%u), " to ", maxval(solution%u) + print *, " un range: ", minval(solution%un), " to ", maxval(solution%un) + + ! 修改当前解,测试更新 + solution%u = solution%u * 2.0_wp + call solution%update_old_field() + print *, "After update: max|u - un| = ", maxval(abs(solution%u - solution%un)) + print *, "" + + ! 测试4: 重置 + print *, "4. Testing reset..." + print *, "-------------------" + + call solution_reset(solution) + print *, "After reset:" + print *, " u max: ", maxval(abs(solution%u)) + print *, " un max: ", maxval(abs(solution%un)) + print *, " flux max: ", maxval(abs(solution%flux)) + print *, "" + + deallocate(initial_values) + + print *, "=== Test Summary ===" + print *, "✓ Ghost layer calculation works" + print *, "✓ Domain creation works" + print *, "✓ Solution arrays work" + print *, "✓ Initialization works" + print *, "✓ Field update works" + print *, "✓ Reset works" + print *, "" + print *, "Ready for next step: Implementing Physics modules" + +end program test_domain_solution \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_factory_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_factory_simple.f90 new file mode 100644 index 000000000..db65da7cf --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_factory_simple.f90 @@ -0,0 +1,58 @@ +! tests/test_factory_simple.f90 (修复版) +program test_factory_simple + use base_modules, only: wp ! ← 添加这行 + use config_module, only: cfd_config, config_print + use mesh_module, only: mesh_type + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(eno_reconstructor) :: eno + type(weno3_reconstructor) :: weno3 + type(rusanov_flux) :: rusanov + + print *, "=== Factory Pattern Simple Test ===" + print *, "" + + ! Test 1: Basic systems + print *, "1. Testing basic systems..." + print *, "-----------------------------" + call config_print(config) + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 2: Creating reconstructors + print *, "2. Testing reconstructors..." + print *, "------------------------------" + + ! 创建并测试ENO重构器 + print *, "Creating ENO reconstructor..." + eno = eno_reconstructor() ! 使用构造函数 + call eno%info() ! 必须调用info方法 + + print *, "" + print *, "Creating WENO3 reconstructor..." + weno3 = weno3_reconstructor() ! 使用构造函数 + call weno3%info() ! 必须调用info方法 + print *, "" + + ! Test 3: Creating flux calculator + print *, "3. Testing flux calculator..." + print *, "-------------------------------" + + print *, "Creating Rusanov flux calculator..." + rusanov = rusanov_flux() ! 使用构造函数 + call rusanov%info() ! 必须调用info方法 + print *, "" + + print *, "=== Factory pattern simple test completed successfully ===" + +end program test_factory_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_infrastructure.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_infrastructure.f90 new file mode 100644 index 000000000..22fa92d1a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_infrastructure.f90 @@ -0,0 +1,56 @@ +! tests/test_infrastructure.f90 (原test_basic_only.f90) +program test_infrastructure + use base_modules, only: wp + use config_module, only: cfd_config, config_print + use mesh_module, only: mesh_type + use registry_module, only: registry_init, registry_cleanup, & + register_component_simple, list_components + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "=== 基础设施测试 ===" + print *, "" + + ! 测试1: 配置 + print *, "1. 测试配置模块..." + print *, "-------------------" + call config_print(config) + print *, "" + + ! 测试2: 网格 + print *, "2. 测试网格模块..." + print *, "------------------" + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=5) + print *, "网格初始化:" + print *, " 单元数: ", mesh%ncells + print *, " 节点数: ", mesh%nnodes + print *, " 网格间距: ", mesh%dx + print *, "" + + ! 测试3: 注册系统 + print *, "3. 测试注册系统..." + print *, "------------------" + + call registry_init() + + ! 注册组件(使用简化版本) + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("flux", "rusanov") + + ! 列出组件 + call list_components() + print *, "" + + ! 清理 + call registry_cleanup() + + print *, "=== 基础设施测试通过 ===" + print *, "✓ 配置模块工作正常" + print *, "✓ 网格模块工作正常" + print *, "✓ 注册系统工作正常" + +end program test_infrastructure \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_physics.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_physics.f90 new file mode 100644 index 000000000..3dababb6a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_physics.f90 @@ -0,0 +1,91 @@ +! tests/test_physics.f90 (原test_physics_minimal.f90) +program test_physics + use base_modules, only: wp, ip + use linear_convection_equation, only: linear_convection_eq, create_linear_convection_eq + use linear_convection_problem, only: linear_convection_prob, create_linear_convection_prob + + implicit none + + type(linear_convection_eq) :: eq + type(linear_convection_prob) :: prob + real(wp) :: u, f, a + real(wp), allocatable :: x(:), u_ic(:), u_exact(:) + integer :: i, nx = 10 + + print *, "=== 物理模块基础测试 ===" + print *, "" + + ! 测试1: 方程功能 + print *, "1. 测试方程功能..." + print *, "-------------------" + + eq = create_linear_convection_eq(wave_speed=2.0_wp) + print *, "方程: ", eq%name + print *, "波速: ", eq%wave_speed + + u = 1.5_wp + f = eq%flux(u) + a = eq%speed() + + print *, "u = ", u + print *, "F(u) = ", f, " (期望: 3.0)" + print *, "波速 a = ", a, " (期望: 2.0)" + + if (abs(f - 3.0_wp) < 1e-10_wp .and. abs(a - 2.0_wp) < 1e-10_wp) then + print *, "✓ 方程功能正常" + else + print *, "✗ 方程功能异常" + end if + print *, "" + + ! 测试2: 问题功能 + print *, "2. 测试问题功能..." + print *, "-------------------" + + prob = create_linear_convection_prob(ic_type="step", domain_length=2.0_wp) + print *, "问题: ", prob%name + print *, "初始条件类型: ", trim(prob%ic_type) + print *, "域长度: ", prob%domain_length + + allocate(x(nx), u_ic(nx), u_exact(nx)) + do i = 1, nx + x(i) = 0.0_wp + (i-1) * 0.2_wp + end do + + ! 测试初始条件 + call prob%initial_condition(x, u_ic) + print *, "初始条件范围: ", minval(u_ic), " 到 ", maxval(u_ic) + + ! 测试精确解 + u_exact = prob%exact_solution(x, 0.0_wp) + print *, "t=0时精确解范围: ", minval(u_exact), " 到 ", maxval(u_exact) + + ! 检查阶跃函数 + if (abs(u_ic(1) - 1.0_wp) < 1e-10_wp .and. & + abs(u_ic(6) - 2.0_wp) < 1e-10_wp) then + print *, "✓ 阶跃初始条件正确" + else + print *, "✗ 阶跃初始条件错误" + end if + + ! 检查精确解与初始条件一致 + if (maxval(abs(u_ic - u_exact)) < 1e-10_wp) then + print *, "✓ t=0时精确解与初始条件一致" + else + print *, "✗ 精确解计算错误" + end if + print *, "" + + ! 测试3: 边界条件接口 + print *, "3. 测试边界条件接口..." + print *, "----------------------" + call prob%boundary_condition(u_ic, 0.0_wp) + print *, "✓ 边界条件接口正常" + print *, "" + + deallocate(x, u_ic, u_exact) + + print *, "=== 物理模块测试完成 ===" + print *, "下一步: 将物理模块集成到现有系统中" + +end program test_physics \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_physics_solver.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_physics_solver.f90 new file mode 100644 index 000000000..ba49ffbac --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_physics_solver.f90 @@ -0,0 +1,133 @@ +! tests/test_physics_solver.f90 (修复版) +program test_physics_solver + use base_modules, only: wp ! 使用一致的wp定义 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use mesh_module, only: mesh_type + use solver_base_module, only: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, & + SOLVER_COMPLETED, SOLVER_ERROR + use physics_solver_module, only: physics_solver + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(physics_solver) :: psolver + character(len=100) :: error_msg + integer :: state + + print *, "=== Physics Solver Test ===" + print *, "" + + ! 测试1: 创建物理求解器(默认物理配置) + print *, "1. Creating physics solver (default physics)..." + print *, "------------------------------------------------" + + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%dt = 0.01_wp + config%enable_physics = .true. + + call config_print(config) + + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=10) + + psolver = physics_solver(config, mesh) + call psolver%print_info() + print *, "" + + ! 测试2: 初始化 + print *, "2. Initializing physics solver..." + print *, "----------------------------------" + + call psolver%initialize() + state = psolver%get_state() + error_msg = psolver%get_error() + print *, "State after initialization: ", state + print *, "Expected: ", SOLVER_INITIALIZED + print *, "Match? ", state == SOLVER_INITIALIZED + print *, "Error message: '", trim(error_msg), "'" + print *, "" + + ! 测试3: 运行一小段时间 + print *, "3. Running physics solver (short time)..." + print *, "------------------------------------------" + + call psolver%run_to_time(0.02_wp) + state = psolver%get_state() + error_msg = psolver%get_error() + print *, "State after short run: ", state + print *, "Expected: ", SOLVER_COMPLETED + print *, "Match? ", state == SOLVER_COMPLETED + print *, "Current time: ", psolver%current_time + print *, "Current step: ", psolver%current_step + print *, "" + + ! 测试4: 禁用物理模块 + print *, "4. Testing physics solver with physics disabled..." + print *, "--------------------------------------------------" + + config%enable_physics = .false. + config%verbose = .false. + + psolver = physics_solver(config, mesh) + call psolver%initialize() + call psolver%run_to_time(0.01_wp) + + state = psolver%get_state() + error_msg = psolver%get_error() + print *, "State with physics disabled: ", state + print *, "Expected: ", SOLVER_COMPLETED + print *, "Match? ", state == SOLVER_COMPLETED + print *, "" + + ! 测试5: 不同物理配置 + print *, "5. Testing different physics configurations..." + print *, "----------------------------------------------" + + config%verbose = .true. + config%enable_physics = .true. + config%equation_type = "linear_advection" + config%problem_type = "linear_advection" + config%wave_speed = 2.5_wp + config%domain_length = 3.0_wp + config%ic_type = "gaussian" + + psolver = physics_solver(config, mesh) + call psolver%initialize() + + print *, "Physics configuration test completed" + print *, "" + + ! 测试6: 清理和错误处理 + print *, "6. Testing cleanup and error handling..." + print *, "----------------------------------------" + + call psolver%cleanup() + state = psolver%get_state() + error_msg = psolver%get_error() + print *, "State after cleanup: ", state + print *, "Expected: ", SOLVER_UNINITIALIZED + print *, "Match? ", state == SOLVER_UNINITIALIZED + + ! 尝试运行已清理的求解器 + call psolver%run_to_time(0.01_wp) + state = psolver%get_state() + error_msg = psolver%get_error() + print *, "State after error attempt: ", state + print *, "Expected: ", SOLVER_ERROR + print *, "Match? ", state == SOLVER_ERROR + print *, "Error message: '", trim(error_msg), "'" + print *, "" + + ! 最终信息 + print *, "=== Physics Solver Test Complete ===" + print *, "✓ Physics solver creation works" + print *, "✓ Physics component initialization works" + print *, "✓ Physics-enabled time stepping works" + print *, "✓ Physics disabled mode works" + print *, "✓ Different physics configurations work" + print *, "✓ Cleanup and error handling work" + print *, "" + print *, "下一步: 实现完整的数值方法(重构、通量、时间积分)" + +end program test_physics_solver \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_physics_solver_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_physics_solver_simple.f90 new file mode 100644 index 000000000..13312efde --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_physics_solver_simple.f90 @@ -0,0 +1,161 @@ +! tests/test_physics_solver_simple.f90 +program test_physics_solver_simple + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction + use mesh_module, only: mesh_type + use physics_solver_module, only: physics_solver, SOLVER_COMPLETED + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(physics_solver) :: solver + real(wp) :: final_time, final_step + integer :: state + + print *, "=========================================" + print *, " 简单物理求解器测试" + print *, "=========================================" + print *, "" + + ! 步骤1: 配置 + print *, "[步骤1] 配置求解器..." + print *, "---------------------" + + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%dt = 0.01_wp + config%final_time = 0.1_wp + config%wave_speed = 1.0_wp + config%ic_type = "step" + config%boundary_type = "periodic" + config%equation_type = "linear_advection" + config%problem_type = "linear_advection" + config%enable_physics = .true. + config%domain_length = 1.0_wp + + print *, "配置参数:" + print *, " 重构格式: ", trim(config%recon_scheme) + print *, " 时间步长: ", config%dt + print *, " 最终时间: ", config%final_time + print *, " 波速: ", config%wave_speed + print *, "" + + ! 步骤2: 创建网格 + print *, "[步骤2] 创建网格..." + print *, "-------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + + print *, "网格信息:" + print *, " 单元数: ", mesh%ncells + print *, " 节点数: ", mesh%nnodes + print *, " 网格间距: ", mesh%dx + print *, "" + + ! 步骤3: 创建求解器 + print *, "[步骤3] 创建求解器..." + print *, "---------------------" + + solver = physics_solver(config, mesh) + + print *, "求解器创建成功" + print *, " 初始状态: ", solver%get_state() + print *, "" + + ! 步骤4: 初始化 + print *, "[步骤4] 初始化求解器..." + print *, "-----------------------" + + call solver%initialize() + + state = solver%get_state() + print *, "初始化完成" + print *, " 状态: ", state + print *, " 当前时间: ", solver%current_time + print *, " 当前步数: ", solver%current_step + print *, "" + + ! 步骤5: 运行求解器 + print *, "[步骤5] 运行求解器..." + print *, "---------------------" + + call solver%run_to_time(config%final_time) + + state = solver%get_state() + print *, "运行完成" + print *, " 状态: ", state + print *, " 最终时间: ", solver%current_time + print *, " 总步数: ", solver%current_step + print *, "" + + ! 步骤6: 保存结果 + print *, "[步骤6] 保存结果..." + print *, "-------------------" + + final_time = solver%current_time + final_step = real(solver%current_step, wp) + state = solver%get_state() + + print *, "保存的结果:" + print *, " 状态: ", state + print *, " 时间: ", final_time + print *, " 步数: ", final_step + print *, "" + + ! 步骤7: 清理求解器 + print *, "[步骤7] 清理求解器..." + print *, "---------------------" + + call solver%cleanup() + + print *, "清理后状态:" + print *, " 状态: ", solver%get_state() + print *, " 时间: ", solver%current_time + print *, " 步数: ", solver%current_step + print *, "" + + ! 步骤8: 验证结果 + print *, "[步骤8] 验证结果..." + print *, "-------------------" + + print *, "验证标准:" + print *, " 1. 运行后状态应为 COMPLETED (", SOLVER_COMPLETED, ")" + print *, " 2. 最终时间应接近 ", config%final_time + print *, " 3. 步数应大于 0" + print *, "" + + if (state == SOLVER_COMPLETED) then + print *, "✓ 状态验证通过: COMPLETED" + else + print *, "✗ 状态验证失败: 期望 ", SOLVER_COMPLETED, ", 实际 ", state + end if + + if (abs(final_time - config%final_time) < 1e-5_wp) then + print *, "✓ 时间验证通过: ", final_time, " ≈ ", config%final_time + else + print *, "✗ 时间验证失败: ", final_time, " ≠ ", config%final_time + end if + + if (final_step > 0) then + print *, "✓ 步数验证通过: ", final_step, " > 0" + else + print *, "✗ 步数验证失败: ", final_step, " ≤ 0" + end if + + print *, "" + + ! 最终判断 + if (state == SOLVER_COMPLETED .and. & + abs(final_time - config%final_time) < 1e-5_wp .and. & + final_step > 0) then + print *, "=========================================" + print *, " 所有测试通过! ✓" + print *, "=========================================" + else + print *, "=========================================" + print *, " 测试失败 ✗" + print *, "=========================================" + end if + +end program test_physics_solver_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_registry.f90 new file mode 100644 index 000000000..e82651ffb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_registry.f90 @@ -0,0 +1,87 @@ +! tests/test_registry.f90 (原test_minimal_simple.f90) +program test_registry + use base_modules, only: wp + use registry_module + use config_module + use mesh_module + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== 注册系统功能测试 ===" + print *, "" + + ! 测试1: 配置系统 + print *, "1. 测试配置系统" + print *, "--------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! 测试2: 网格系统 + print *, "2. 测试网格系统" + print *, "--------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! 测试3: 注册系统 + print *, "3. 测试注册系统" + print *, "--------------" + + call registry_init() + + ! 注册组件 + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call list_components() + print *, "注册表大小: ", registry_get_size() + print *, "" + + ! 测试组件查找 + print *, "4. 测试组件查找" + print *, "--------------" + + if (has_component_simple("reconstructor", "eno")) then + print *, "找到: reconstructor.eno" + else + print *, "未找到: reconstructor.eno" + end if + + if (has_component_simple("reconstructor", "unknown")) then + print *, "找到: reconstructor.unknown" + else + print *, "未找到: reconstructor.unknown" + end if + print *, "" + + ! 测试获取可用组件 + print *, "5. 测试注册系统功能" + print *, "------------------" + print *, "注册表已初始化: ", registry_is_initialized() + print *, "组件数量: ", registry_get_size() + print *, "" + + ! 清理 + call registry_cleanup() + + print *, "=== 注册系统测试完成 ===" + +end program test_registry \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_simple_link.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_simple_link.f90 new file mode 100644 index 000000000..71cc614e8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_simple_link.f90 @@ -0,0 +1,78 @@ +! tests/test_simple_link.f90 +program test_simple_link + use base_modules, only: wp ! ← 添加这行 + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call registry_init() + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call list_components() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component_simple("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component_simple("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Cleanup + call registry_cleanup() + + print *, "=== Minimal test completed successfully ===" + +end program test_simple_link \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_solver_base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_solver_base.f90 new file mode 100644 index 000000000..6cfe47e41 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_solver_base.f90 @@ -0,0 +1,99 @@ +! tests/test_solver_base.f90 (修复版) +program test_solver_base + ! 所有 USE 语句必须在程序开始处 + use base_modules, only: wp + use config_module, only: cfd_config, config_print, config_with_reconstruction + use mesh_module, only: mesh_type + use solver_base_module, only: solver_base, SOLVER_UNINITIALIZED, & + SOLVER_INITIALIZED, SOLVER_COMPLETED, SOLVER_ERROR + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(solver_base) :: solver + integer :: state + + print *, "=== Solver Base Test ===" + print *, "" + + ! 测试1: 创建求解器 + print *, "1. Creating solver..." + print *, "----------------------" + + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%dt = 0.01_wp + + call config_print(config) + + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=10) + + solver = solver_base(config, mesh) + call solver%print_info() + print *, "" + + ! 测试2: 初始化 + print *, "2. Initializing solver..." + print *, "-------------------------" + + call solver%initialize() + state = solver%get_state() + print *, "State after initialization: ", state + print *, "Expected: ", SOLVER_INITIALIZED + print *, "Match? ", state == SOLVER_INITIALIZED + print *, "Error message: '", trim(solver%get_error()), "'" + print *, "" + + ! 测试3: 运行求解器 + print *, "3. Running solver..." + print *, "--------------------" + + call solver%run_to_time(0.05_wp) + state = solver%get_state() + print *, "State after run: ", state + print *, "Expected: ", SOLVER_COMPLETED + print *, "Match? ", state == SOLVER_COMPLETED + print *, "Current time: ", solver%current_time + print *, "Current step: ", solver%current_step + print *, "" + + ! 测试4: 再次运行(从已完成状态) + print *, "4. Running again from completed state..." + print *, "----------------------------------------" + + ! 需要先清理才能重新运行 + call solver%cleanup() + call solver%initialize() + call solver%run_to_time(0.1_wp) + + call solver%print_info() + print *, "" + + ! 测试5: 错误处理 + print *, "5. Testing error states..." + print *, "--------------------------" + + ! 创建一个未初始化的求解器 + call solver%cleanup() + state = solver%get_state() + print *, "Uninitialized state: ", state + print *, "Expected: ", SOLVER_UNINITIALIZED + print *, "Match? ", state == SOLVER_UNINITIALIZED + + ! 尝试运行未初始化的求解器 + call solver%run_to_time(0.01_wp) + state = solver%get_state() + print *, "State after error: ", state + print *, "Expected: ", SOLVER_ERROR + print *, "Match? ", state == SOLVER_ERROR + print *, "Error message: '", trim(solver%get_error()), "'" + print *, "" + + print *, "=== Solver Base Test Complete ===" + print *, "✓ Solver base class works" + print *, "✓ State management works" + print *, "✓ Time stepping framework works" + print *, "✓ Error handling works" + +end program test_solver_base \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_solver_framework.f90 b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_solver_framework.f90 new file mode 100644 index 000000000..6754323d0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03e/tests/test_solver_framework.f90 @@ -0,0 +1,91 @@ +! tests/test_solver_framework.f90 +program test_solver_framework + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use mesh_module, only: mesh_type + use solver_module, only: cfd_solver, solver_create, solver_run, solver_cleanup + use solver_module, only: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, & + SOLVER_COMPLETED, SOLVER_ERROR + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(cfd_solver) :: solver + + print *, "=== 求解器框架测试 ===" + print *, "" + + ! 测试1: 基本创建 + print *, "1. 测试求解器创建..." + print *, "----------------------" + + ! 创建配置 + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.0_real64 + config%dt = 0.01_real64 + + call config_print(config) + print *, "" + + ! 创建网格 + call mesh%init(xmin=0.0_real64, xmax=2.0_real64, ncells=20) + call mesh%print_info() + print *, "" + + ! 创建求解器 + solver = solver_create(config, mesh) + print *, "✓ 求解器创建成功" + print *, " 状态: ", solver%get_state() + print *, "" + + ! 测试2: 求解器初始化 + print *, "2. 测试求解器初始化..." + print *, "------------------------" + + call solver%initialize() + print *, "✓ 求解器初始化完成" + print *, " 状态: ", solver%get_state() + print *, " 错误信息: '", trim(solver%get_error()), "'" + print *, "" + + ! 测试3: 简单运行 + print *, "3. 测试求解器运行..." + print *, "----------------------" + + call solver_run(solver, 0.05_real64) ! 运行到0.05秒 + print *, "✓ 求解器运行完成" + print *, " 最终状态: ", solver%get_state() + print *, "" + + ! 测试4: 清理 + print *, "4. 测试求解器清理..." + print *, "----------------------" + + call solver_cleanup(solver) + print *, "✓ 求解器清理完成" + print *, " 状态: ", solver%get_state() + print *, "" + + ! 测试5: 错误处理 + print *, "5. 测试错误处理..." + print *, "-------------------" + + ! 尝试重复初始化 + call solver%initialize() + print *, " 重复初始化状态: ", solver%get_state() + print *, " 错误信息: '", trim(solver%get_error()), "'" + + call solver_cleanup(solver) + print *, "" + + print *, "=== 框架测试总结 ===" + print *, "✓ 求解器创建/初始化/运行/清理流程验证完成" + print *, "✓ 状态管理正常工作" + print *, "✓ 错误处理机制就绪" + print *, "" + print *, "下一步: 添加实际数值计算功能" + +end program test_solver_framework \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03f/CMakeLists.txt new file mode 100644 index 000000000..55859dc2a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) +add_subdirectory(examples) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/README.md b/example/1d-linear-convection/weno3/fortran/registry/03f/README.md new file mode 100644 index 000000000..c4889757b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/README.md @@ -0,0 +1,221 @@ +# Fortran CFD Project + +基于工厂模式和注册系统的CFD求解器框架。 + +## 项目结构 + +``` +fortran_cfd/ +├── CMakeLists.txt # 根CMake配置 +├── scripts/ # 构建脚本 +│ ├── build.py # Python构建脚本(推荐) +│ ├── build.bat # Batch包装脚本 +│ ├── build.ps1 # PowerShell包装脚本 +│ └── requirements.txt # Python依赖 +├── src/ # 源代码 +│ ├── CMakeLists.txt +│ ├── core/ # 核心模块(注册系统、工厂接口) +│ ├── infrastructure/ # 基础设施(配置、网格) +│ └── numerics/ # 数值方法 +├── tests/ # 测试 +│ ├── CMakeLists.txt +│ ├── test_minimal_simple.f90 # 简单测试 +│ └── test_factory.f90 # 工厂模式测试 +└── README.md # 项目说明(本文件) +``` + +## 快速开始 + +### 使用Python脚本(推荐) + +```bash +# 进入脚本目录 +cd scripts + +# 基本构建 +python build.py + +# 清理并构建 +python build.py --clean + +# 发布构建 +python build.py --build-type Release --clean + +# 并行构建(使用所有核心) +python build.py -j + +# 不运行测试 +python build.py --no-tests + +# 查看帮助 +python build.py --help +``` + +### 使用批处理脚本 + +```bash +cd scripts +.\build.bat +``` + +### 使用PowerShell脚本 + +```powershell +cd scripts +.\build.ps1 +``` + +## 手动构建 + +```bash +# 设置Intel环境 +cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && powershell' + +# 配置项目 +mkdir build +cd build +cmake -G "Visual Studio 17 2022" -A x64 -T fortran=ifx .. + +# 构建项目 +cmake --build . --config Debug + +# 运行测试 +Debug\test_simple.exe +Debug\test_factory.exe +``` + +## 功能特性 + +✅ 组件注册系统 +✅ 工厂模式 +✅ ENO/WENO重构器 +✅ Rusanov通量计算器 +✅ 可扩展架构 +✅ 自动化测试 + +## 依赖 + +- Intel oneAPI(包含ifx编译器) +- CMake 3.12+ +- Python 3.6+(仅用于构建脚本) + +## 源代码文件 + +### 核心模块 +- `src/core/registry.f90` - 组件注册系统 +- `src/core/factory_interfaces.f90` - 工厂接口 + +### 基础设施 +- `src/infrastructure/config.f90` - 配置管理 +- `src/infrastructure/mesh.f90` - 网格生成 + +### 数值方法 +- `src/numerics/reconstructor/base.f90` - 重构器基类 +- `src/numerics/reconstructor/eno.f90` - ENO重构器 +- `src/numerics/reconstructor/weno3.f90` - WENO-3重构器 +- `src/numerics/flux/base.f90` - 通量计算器基类 +- `src/numerics/flux/rusanov.f90` - Rusanov通量 + +### 测试 +- `tests/test_minimal_simple.f90` - 简单功能测试 +- `tests/test_factory.f90` - 工厂模式测试 + +## 构建脚本 + +### Python脚本 (build.py) +主构建脚本,支持: +- 自动设置Intel oneAPI环境 +- 参数化构建选项 +- 并行编译 +- 自动化测试 +- 彩色输出 + +### Batch脚本 (build.bat) +Windows批处理包装器,调用Python脚本。 + +### PowerShell脚本 (build.ps1) +PowerShell包装器,调用Python脚本。 + +## 示例输出 + +成功构建后,会看到类似输出: + +``` +============================================================ + Fortran CFD Project Builder +============================================================ + +[1/4] Setting up Intel Fortran compiler... +✓ Intel oneAPI environment configured + +[2/4] Preparing build directory... +✓ Cleaned build directory + +[3/4] Configuring project... + $ cmake -G Visual Studio 17 2022 -A x64 -T fortran=ifx -DCMAKE_BUILD_TYPE=Debug .. +✓ CMake configuration successful + +[4/4] Building project... + $ cmake --build . --config Debug +✓ Build completed in 12.3 seconds + +============================================================ + Running Tests +============================================================ + +[TEST] Simple functionality test... +✓ Simple test passed + +[TEST] Factory pattern test... +✓ Factory test passed + +============================================================ + Build Summary +============================================================ +✓ Build directory: D:\path\to\build +✓ Build type: Debug +✓ Total time: 15.2s +``` + +## 开发指南 + +### 添加新组件 + +1. 在相应模块中创建Fortran源文件 +2. 实现工厂函数 +3. 使用`register_component_with_factory`注册组件 +4. 添加测试 + +### 扩展架构 + +项目采用模块化设计: +1. **注册系统** - 管理所有可用的组件 +2. **工厂模式** - 动态创建组件实例 +3. **抽象接口** - 确保组件兼容性 +4. **配置驱动** - 通过配置文件控制行为 + +## 故障排除 + +### 常见问题 + +1. **Intel环境未找到** + - 检查Intel oneAPI安装路径 + - 手动运行`setvars.bat` + +2. **CMake配置失败** + - 确保使用正确的生成器:`Visual Studio 17 2022` + - 检查Fortran编译器是否可用 + +3. **构建失败** + - 检查依赖项是否完整 + - 查看详细的错误信息 + +### 调试建议 + +- 使用`--verbose`参数获取详细输出 +- 检查`build/CMakeCache.txt`了解配置详情 +- 查看编译器错误日志 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/examples/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03f/examples/CMakeLists.txt new file mode 100644 index 000000000..cff652147 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/examples/CMakeLists.txt @@ -0,0 +1,39 @@ +# examples/CMakeLists.txt (修正版) +message(STATUS "配置示例程序...") + +# 主示例程序:ENO/WENO对比(当前版本) +add_executable(run_eno_weno + run_eno_weno.f90 +) + +target_link_libraries(run_eno_weno + PRIVATE + solver + infrastructure + core + physics + manager + results + boundary + initial_condition +) + +# 集成版本示例 +add_executable(run_eno_weno_integrated + run_eno_weno_integrated.f90 +) + +target_link_libraries(run_eno_weno_integrated + PRIVATE + solver_integrated # 主要模块 + infrastructure + base + boundary # 集成求解器需要的边界条件 + initial_condition # 集成求解器需要的初始条件 +) + +install(TARGETS run_eno_weno run_eno_weno_integrated + RUNTIME DESTINATION bin/examples +) + +message(STATUS "示例程序配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/examples/run_eno_weno.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/examples/run_eno_weno.f90 new file mode 100644 index 000000000..ffa6ff9d0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/examples/run_eno_weno.f90 @@ -0,0 +1,327 @@ +! examples/run_eno_weno.f90 (修正版) +program run_eno_weno + ! Example program: ENO/WENO comparison analysis + + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction, config_print + use mesh_module, only: mesh_type + use registry_module, only: registry_init, registry_cleanup, initialize_default_components + use physics_solver_module, only: physics_solver, SOLVER_COMPLETED + use results_module, only: results_saver, save_results ! 修改:只导入需要的内容 + + implicit none + + type(cfd_config) :: config_eno3, config_weno3, config_weno5 + type(mesh_type) :: mesh + type(physics_solver) :: solver_eno3, solver_weno3, solver_weno5 + + ! 结果保存器 + type(results_saver) :: saver ! 直接声明类型 + + ! Variables to save results + real(wp) :: time_eno3, time_weno3, time_weno5 + integer :: steps_eno3, steps_weno3, steps_weno5 + integer :: state_eno3, state_weno3, state_weno5 + logical :: all_success + + ! Debug: print start marker + print *, "==========================================" + print *, "START: ENO/WENO Comparison Analysis" + print *, "==========================================" + print *, "" + + ! Step 0: Initialize system + print *, "[STEP 0] Initializing system..." + print *, "--------------------------------" + + call registry_init(verbose=.true.) + print *, "Registry initialized" + + call initialize_default_components() + print *, "Default components registered" + print *, "" + + ! Step 1: Create mesh + print *, "[STEP 1] Creating computational mesh..." + print *, "----------------------------------------" + + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=40) + call mesh%print_info() + print *, "" + + ! 创建结果保存器 - 使用构造函数而不是赋值 + saver = results_saver("results", .true.) ! 直接使用构造函数 + + ! ========== ENO3 Solver ========== + print *, "[STEP 2] Configuring and running ENO3 solver..." + print *, "-----------------------------------------------" + + ! Configure ENO3 + config_eno3%verbose = .true. + config_eno3%ic_type = "step" + config_eno3%wave_speed = 1.0_wp + config_eno3%final_time = 0.625_wp + config_eno3%dt = 0.0025_wp + config_eno3%rk_order = 2 + config_eno3%boundary_type = "periodic" + config_eno3%equation_type = "linear_advection" + config_eno3%problem_type = "linear_advection" + config_eno3%enable_physics = .true. + config_eno3%domain_length = 2.0_wp + + call config_with_reconstruction(config_eno3, "eno", 3) + + print *, "ENO3 configuration:" + call config_print(config_eno3) + print *, "" + + ! Create and run ENO3 solver + print *, "Creating ENO3 solver instance..." + ! 替换这三行: + ! call physics_solver_constructor(solver_eno3, config_eno3, mesh) + + ! 改为: + solver_eno3 = physics_solver(config_eno3, mesh) + + print *, "Initializing ENO3 solver..." + call solver_eno3%initialize() + + print *, "Running ENO3 solver..." + call solver_eno3%run_to_time(config_eno3%final_time) + + ! Immediately save ENO3 results + time_eno3 = solver_eno3%current_time + steps_eno3 = solver_eno3%current_step + state_eno3 = solver_eno3%get_state() + + print *, "ENO3 solver completed" + print *, " Final time: ", time_eno3 + print *, " Total steps: ", steps_eno3 + print *, " State: ", state_eno3 + print *, "" + + ! 保存ENO3结果到文件 + call save_results(saver, "ENO3", & + solver_eno3%config, solver_eno3%mesh, solver_eno3%domain, & + solver_eno3%solution, & + solver_eno3%current_time, solver_eno3%current_step, solver_eno3%get_state()) + + ! ========== WENO3 Solver ========== + print *, "[STEP 3] Configuring and running WENO3 solver..." + print *, "------------------------------------------------" + + ! Configure WENO3 + config_weno3%verbose = .true. + config_weno3%ic_type = "step" + config_weno3%wave_speed = 1.0_wp + config_weno3%final_time = 0.625_wp + config_weno3%dt = 0.0025_wp + config_weno3%rk_order = 2 + config_weno3%boundary_type = "periodic" + config_weno3%equation_type = "linear_advection" + config_weno3%problem_type = "linear_advection" + config_weno3%enable_physics = .true. + config_weno3%domain_length = 2.0_wp + + call config_with_reconstruction(config_weno3, "weno3", 3) + + print *, "WENO3 configuration:" + call config_print(config_weno3) + print *, "" + + ! Create and run WENO3 solver + print *, "Creating WENO3 solver instance..." + call physics_solver_constructor(solver_weno3, config_weno3, mesh) + + print *, "Initializing WENO3 solver..." + call solver_weno3%initialize() + + print *, "Running WENO3 solver..." + call solver_weno3%run_to_time(config_weno3%final_time) + + ! Immediately save WENO3 results + time_weno3 = solver_weno3%current_time + steps_weno3 = solver_weno3%current_step + state_weno3 = solver_weno3%get_state() + + print *, "WENO3 solver completed" + print *, " Final time: ", time_weno3 + print *, " Total steps: ", steps_weno3 + print *, " State: ", state_weno3 + print *, "" + + ! 保存WENO3结果到文件 + call save_results(saver, "WENO3", & + solver_weno3%config, solver_weno3%mesh, solver_weno3%domain, & + solver_weno3%solution, time_weno3, steps_weno3, state_weno3) + + ! ========== WENO5 Solver ========== + print *, "[STEP 4] Configuring and running WENO5 solver..." + print *, "------------------------------------------------" + + ! Configure WENO5 + config_weno5%verbose = .true. + config_weno5%ic_type = "step" + config_weno5%wave_speed = 1.0_wp + config_weno5%final_time = 0.625_wp + config_weno5%dt = 0.0025_wp + config_weno5%rk_order = 2 + config_weno5%boundary_type = "periodic" + config_weno5%equation_type = "linear_advection" + config_weno5%problem_type = "linear_advection" + config_weno5%enable_physics = .true. + config_weno5%domain_length = 2.0_wp + + call config_with_reconstruction(config_weno5, "weno", 5) + + print *, "WENO5 configuration:" + call config_print(config_weno5) + print *, "" + + ! Create and run WENO5 solver + print *, "Creating WENO5 solver instance..." + call physics_solver_constructor(solver_weno5, config_weno5, mesh) + + print *, "Initializing WENO5 solver..." + call solver_weno5%initialize() + + print *, "Running WENO5 solver..." + call solver_weno5%run_to_time(config_weno5%final_time) + + ! Immediately save WENO5 results + time_weno5 = solver_weno5%current_time + steps_weno5 = solver_weno5%current_step + state_weno5 = solver_weno5%get_state() + + print *, "WENO5 solver completed" + print *, " Final time: ", time_weno5 + print *, " Total steps: ", steps_weno5 + print *, " State: ", state_weno5 + print *, "" + + ! 保存WENO5结果到文件 + call save_results(saver, "WENO5", & + solver_weno5%config, solver_weno5%mesh, solver_weno5%domain, & + solver_weno5%solution, time_weno5, steps_weno5, state_weno5) + + ! ========== Results Summary ========== + print *, "==========================================" + print *, " RESULTS SUMMARY" + print *, "==========================================" + print *, "" + + print *, "Solver Performance Comparison:" + print *, "------------------------------" + + print *, "ENO3:" + print *, " Final time: ", time_eno3 + print *, " Total steps: ", steps_eno3 + print *, " State: ", state_eno3 + print *, " Results saved to: results_ENO3_40.dat" + print *, "" + + print *, "WENO3:" + print *, " Final time: ", time_weno3 + print *, " Total steps: ", steps_weno3 + print *, " State: ", state_weno3 + print *, " Results saved to: results_WENO3_40.dat" + print *, "" + + print *, "WENO5:" + print *, " Final time: ", time_weno5 + print *, " Total steps: ", steps_weno5 + print *, " State: ", state_weno5 + print *, " Results saved to: results_WENO5_40.dat" + print *, "" + + ! ========== Final Judgment ========== + print *, "==========================================" + print *, " FINAL JUDGMENT" + print *, "==========================================" + print *, "" + + all_success = (state_eno3 == SOLVER_COMPLETED) .and. & + (state_weno3 == SOLVER_COMPLETED) .and. & + (state_weno5 == SOLVER_COMPLETED) + + if (all_success) then + print *, "✓ ALL SOLVERS SUCCESSFULLY COMPLETED!" + print *, "" + print *, "Parameter Summary:" + print *, " Grid cells: ", mesh%ncells + print *, " Time step: ", config_eno3%dt + print *, " Final time: ", config_eno3%final_time + print *, " Wave speed: ", config_eno3%wave_speed + print *, " IC type: ", trim(config_eno3%ic_type) + print *, "" + print *, "Performance Comparison:" + print *, " ENO3: ", steps_eno3, " steps" + print *, " WENO3: ", steps_weno3, " steps" + print *, " WENO5: ", steps_weno5, " steps" + print *, "" + print *, "To visualize results:" + print *, " python ../python/plot_results.py --auto" + else + print *, "✗ SOME SOLVERS FAILED" + print *, "" + print *, "Failure Analysis:" + if (state_eno3 /= SOLVER_COMPLETED) then + print *, " • ENO3 failed with state: ", state_eno3 + end if + if (state_weno3 /= SOLVER_COMPLETED) then + print *, " • WENO3 failed with state: ", state_weno3 + end if + if (state_weno5 /= SOLVER_COMPLETED) then + print *, " • WENO5 failed with state: ", state_weno5 + end if + end if + + print *, "" + print *, "==========================================" + print *, " ANALYSIS COMPLETE" + print *, "==========================================" + + ! ========== Cleanup ========== + print *, "" + print *, "[STEP 5] Cleaning up system..." + print *, "--------------------------------" + + call solver_eno3%cleanup() + call solver_weno3%cleanup() + call solver_weno5%cleanup() + call registry_cleanup() + + print *, "All solvers cleaned up" + print *, "Registry cleaned up" + print *, "" + + ! ========== Wait for user input before exit ========== + print *, "==========================================" + print *, "Press ENTER to exit..." + print *, "==========================================" + + ! Wait for user input (uncomment if needed) + ! read(*,*) + + ! Alternative: add a small delay + print *, "Program will exit in 3 seconds..." + call sleep(3) + + print *, "" + print *, "==========================================" + print *, " PROGRAM END" + print *, "==========================================" + +contains + + ! 辅助函数:显式创建physics_solver(如果原始代码使用函数而不是子程序) + subroutine physics_solver_constructor(solver, config, mesh) + type(physics_solver), intent(out) :: solver + type(cfd_config), intent(in) :: config + type(mesh_type), intent(in) :: mesh + + ! 使用赋值构造函数(如果physics_solver_module中有相应的接口) + solver = physics_solver(config, mesh) + end subroutine physics_solver_constructor + +end program run_eno_weno \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/examples/run_eno_weno_integrated.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/examples/run_eno_weno_integrated.f90 new file mode 100644 index 000000000..776293051 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/examples/run_eno_weno_integrated.f90 @@ -0,0 +1,176 @@ +! examples/run_eno_weno_integrated.f90 (完整修复版) +program run_eno_weno_integrated + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction, config_print + use mesh_module, only: mesh_type + use solver_integrated_module, only: integrated_solver, SOLVER_INITIALIZED, SOLVER_COMPLETED + + implicit none + + type(cfd_config) :: config_eno3, config_weno3, config_weno5 + type(mesh_type) :: mesh + type(integrated_solver) :: solver_eno3, solver_weno3, solver_weno5 + + ! 结果变量 + real(wp) :: time_eno3, time_weno3, time_weno5 + integer :: steps_eno3, steps_weno3, steps_weno5 + integer :: state_eno3, state_weno3, state_weno5 + logical :: all_success + + ! 定义常量(避免导入冲突) + integer, parameter :: SOLVER_READY = 0 + integer, parameter :: SOLVER_RUNNING = 2 + integer, parameter :: SOLVER_ERROR = -1 + + print *, "==========================================" + print *, "ENO/WENO 对比分析 (集成版本)" + print *, "==========================================" + print *, "" + + ! 步骤1: 创建网格 + print *, "[STEP 1] 创建计算网格..." + print *, "-----------------------------------" + + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=40) + call mesh%print_info() + print *, "" + + ! ========== ENO3 求解器 ========== + print *, "[STEP 2] 配置和运行 ENO3 求解器..." + print *, "-----------------------------------" + + ! 初始化配置 + config_eno3%verbose = .true. + config_eno3%ic_type = "step" + config_eno3%wave_speed = 1.0_wp + config_eno3%final_time = 0.625_wp + config_eno3%dt = 0.0025_wp + config_eno3%boundary_type = "periodic" + config_eno3%equation_type = "linear_advection" + config_eno3%problem_type = "linear_advection" + config_eno3%domain_length = 2.0_wp + config_eno3%enable_physics = .true. + + call config_with_reconstruction(config_eno3, "eno", 3) + call config_print(config_eno3) + print *, "" + + ! 创建和运行求解器 + solver_eno3%config = config_eno3 + solver_eno3%mesh = mesh + + ! 控制数据模式 (当前使用简单数据) + call solver_eno3%enable_real_data(.false.) ! 设置为false使用简单数据 + + call solver_eno3%initialize() + call solver_eno3%run_to_time(config_eno3%final_time) + + ! 获取结果 + time_eno3 = solver_eno3%current_time + steps_eno3 = solver_eno3%current_step + state_eno3 = solver_eno3%get_state() + + print *, "ENO3 完成:" + print *, " 最终时间: ", time_eno3 + print *, " 总步数: ", steps_eno3 + print *, " 状态: ", state_eno3 + print *, "" + + ! ========== WENO3 求解器 ========== + print *, "[STEP 3] 配置和运行 WENO3 求解器..." + print *, "-----------------------------------" + + ! 配置 (复制ENO3配置,只改重构格式) + config_weno3 = config_eno3 + call config_with_reconstruction(config_weno3, "weno3", 3) + + ! 运行 + solver_weno3%config = config_weno3 + solver_weno3%mesh = mesh + call solver_weno3%enable_real_data(.false.) + + call solver_weno3%initialize() + call solver_weno3%run_to_time(config_weno3%final_time) + + time_weno3 = solver_weno3%current_time + steps_weno3 = solver_weno3%current_step + state_weno3 = solver_weno3%get_state() + + print *, "WENO3 完成:" + print *, " 最终时间: ", time_weno3 + print *, " 总步数: ", steps_weno3 + print *, " 状态: ", state_weno3 + print *, "" + + ! ========== WENO5 求解器 ========== + print *, "[STEP 4] 配置和运行 WENO5 求解器..." + print *, "-----------------------------------" + + config_weno5 = config_eno3 + call config_with_reconstruction(config_weno5, "weno", 5) + + solver_weno5%config = config_weno5 + solver_weno5%mesh = mesh + call solver_weno5%enable_real_data(.false.) + + call solver_weno5%initialize() + call solver_weno5%run_to_time(config_weno5%final_time) + + time_weno5 = solver_weno5%current_time + steps_weno5 = solver_weno5%current_step + state_weno5 = solver_weno5%get_state() + + print *, "WENO5 完成:" + print *, " 最终时间: ", time_weno5 + print *, " 总步数: ", steps_weno5 + print *, " 状态: ", state_weno5 + print *, "" + + ! ========== 结果汇总 ========== + print *, "==========================================" + print *, " 结果汇总" + print *, "==========================================" + print *, "" + + all_success = (state_eno3 == SOLVER_COMPLETED) .and. & + (state_weno3 == SOLVER_COMPLETED) .and. & + (state_weno5 == SOLVER_COMPLETED) + + if (all_success) then + print *, "✓ 所有求解器成功完成!" + print *, "" + print *, "性能对比:" + print *, " ENO3: ", steps_eno3, " 步" + print *, " WENO3: ", steps_weno3, " 步" + print *, " WENO5: ", steps_weno5, " 步" + print *, "" + print *, "网格信息:" + print *, " 单元数: ", mesh%ncells + print *, " 域长度: ", mesh%L + print *, " 网格间距: ", mesh%dx + print *, "" + print *, "数据模式: 简单数据 (一阶迎风格式)" + print *, "切换真实计算: 调用 solver%enable_real_data(.true.)" + else + print *, "✗ 部分求解器失败" + print *, " ENO3状态: ", state_eno3, " (期望: ", SOLVER_COMPLETED, ")" + print *, " WENO3状态: ", state_weno3, " (期望: ", SOLVER_COMPLETED, ")" + print *, " WENO5状态: ", state_weno5, " (期望: ", SOLVER_COMPLETED, ")" + end if + + print *, "" + print *, "==========================================" + print *, " 分析完成" + print *, "==========================================" + + ! 清理 + call solver_eno3%cleanup() + call solver_weno3%cleanup() + call solver_weno5%cleanup() + + ! 等待用户输入 + print *, "" + print *, "按 ENTER 键退出..." + read(*,*) + +end program run_eno_weno_integrated \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/python/plot_results.py b/example/1d-linear-convection/weno3/fortran/registry/03f/python/plot_results.py new file mode 100644 index 000000000..cf8e40175 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/python/plot_results.py @@ -0,0 +1,419 @@ +#!/usr/bin/env python3 +# python/plot_results.py +""" +Fortran CFD 结果可视化脚本 +与Julia的plotter.jl功能类似,但直接读取Fortran生成的文本文件 +""" + +import numpy as np +import matplotlib.pyplot as plt +import os +import sys +import glob +from pathlib import Path +import re + +class FortranResultsPlotter: + """Fortran结果绘图器""" + + def __init__(self, style='default'): + self.style = style + self.setup_styles() + + def setup_styles(self): + """设置绘图样式""" + if self.style == 'default': + self.styles = { + 'numerical': { + 'color': 'blue', + 'linestyle': '-', + 'marker': 'o', + 'markerfacecolor': 'none', + 'markersize': 4, + 'linewidth': 1 + }, + 'analytical': { + 'color': 'red', + 'linestyle': '--', + 'marker': '', + 'linewidth': 1.5 + }, + 'comparison': [ + {'color': 'black', 'linestyle': '-', 'marker': 'o', 'markerfacecolor': 'none'}, + {'color': 'blue', 'linestyle': '--', 'marker': 's', 'markerfacecolor': 'none'}, + {'color': 'green', 'linestyle': ':', 'marker': '^', 'markerfacecolor': 'none'} + ] + } + + def read_fortran_results(self, filename): + """读取Fortran生成的结果文件""" + data = {} + + try: + with open(filename, 'r', encoding='utf-8', errors='ignore') as f: + lines = f.readlines() + + data_lines = [] + in_data_section = False # 新状态:是否在数据区域 + + for line in lines: + line = line.strip() + if not line: + continue + + # 跳过所有分隔线 + if line.startswith("===="): + continue + + # 检测数据区域开始 + if line == "DATA: x, numerical, analytical": + in_data_section = True + continue + + if not in_data_section: + # 解析头部信息 + if "Solver:" in line: + data['solver'] = line.split(":", 1)[1].strip() + elif "Scheme:" in line: + data['scheme'] = line.split(":", 1)[1].strip() + elif "Order:" in line: + if "RK Order:" in line: + data['rk_order'] = int(line.split(":", 1)[1].strip()) + else: + data['order'] = int(line.split(":", 1)[1].strip()) + elif "Current Time:" in line: + data['time'] = float(line.split(":", 1)[1].strip()) + elif "Grid Points:" in line: + data['n_points'] = int(line.split(":", 1)[1].strip()) + else: + # 解析数据行 + parts = line.split() + if len(parts) >= 3: + try: + x = float(parts[0]) + numerical = float(parts[1]) + analytical = float(parts[2]) + data_lines.append([x, numerical, analytical]) + except ValueError: + continue # 忽略无法解析的行 + + if data_lines: + import numpy as np + data_array = np.array(data_lines) + data['x'] = data_array[:, 0] + data['numerical'] = data_array[:, 1] + data['analytical'] = data_array[:, 2] + + print(f"Read {len(data['x'])} points from {filename}") + print(f" Solver: {data.get('solver', 'N/A')}") + print(f" Scheme: {data.get('scheme', 'N/A')} order {data.get('order', 'N/A')}") + print(f" Time: {data.get('time', 'N/A')}") + + return data + else: + print(f"Warning: No data found in {filename}") + return None + + except Exception as e: + print(f"Error reading {filename}: {e}") + return None + + def plot_single_result(self, filename, title=None, show=True, save_path=None): + """绘制单个结果文件""" + data = self.read_fortran_results(filename) + if data is None: + return False + + fig, ax = plt.subplots(figsize=(10, 6)) + + # 自动生成标题 + if title is None: + title = f"1D Convection (t={data['time']:.3f})\n" + title += f"{data['order']}th-order {data['scheme'].upper()} + {data['rk_order']}nd-order RK" + + # 绘制数值解 + ax.plot(data['x'], data['numerical'], + label=f"Numerical ({data['scheme'].upper()}{data['order']})", + **self.styles['numerical']) + + # 绘制解析解 + ax.plot(data['x'], data['analytical'], + label="Analytical", + **self.styles['analytical']) + + # 设置图形属性 + ax.set_title(title, fontsize=12) + ax.set_xlabel("x", fontsize=10) + ax.set_ylabel("u", fontsize=10) + ax.legend(fontsize=9) + ax.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + + # 保存或显示 + if save_path: + plt.savefig(save_path, dpi=150, bbox_inches='tight') + print(f"Saved plot to {save_path}") + + if show: + plt.show() + else: + plt.close() + + return True + + def format_scheme_label(self, scheme: str, order: int) -> str: + s = scheme.upper() + if s.startswith("WENO"): + return f"WENO{order}" + else: + return f"{s}{order}" + + def plot_comparison(self, filenames, labels=None, title=None, show=True, save_path=None): + """比较多个结果文件""" + all_data = [] + print(f"plot_comparison filenames={filenames}") + + # 读取所有文件 + for filename in filenames: + print(f"plot_comparison filename={filename}") + data = self.read_fortran_results(filename) + if data is not None: + all_data.append(data) + + if not all_data: + print("No valid data to plot") + return False + + fig, axes = plt.subplots(2, 2, figsize=(14, 10)) + + # 子图1:所有方法比较 + for i, data in enumerate(all_data): + #print(f"i,all_data={i,all_data}") + if labels and i < len(labels): + label = labels[i] + else: + label = f"{data['scheme'].upper()}{data['order']}" + + style_idx = i % len(self.styles['comparison']) + style = self.styles['comparison'][style_idx] + + axes[0, 0].plot(data['x'], data['numerical'], + label=label, **style) + + axes[0, 0].plot(all_data[0]['x'], all_data[0]['analytical'], + label="Analytical", **self.styles['analytical']) + + axes[0, 0].set_xlabel('x', fontsize=12) + axes[0, 0].set_ylabel('u(x)', fontsize=12) + axes[0, 0].set_title('Numerical Solutions Comparison', fontsize=14) + axes[0, 0].legend() + axes[0, 0].grid(True, alpha=0.3) + + # 子图2:误差分析 + for i, data in enumerate(all_data): + if labels and i < len(labels): + label = labels[i] + else: + label = f"{data['scheme'].upper()}{data['order']}" + + error = np.abs(data['numerical'] - data['analytical']) + axes[0, 1].semilogy(data['x'], error, label=label) + + axes[0, 1].set_xlabel('x', fontsize=12) + axes[0, 1].set_ylabel('Absolute Error', fontsize=12) + axes[0, 1].set_title('Error Comparison', fontsize=14) + axes[0, 1].legend() + axes[0, 1].grid(True, alpha=0.3) + + # 子图3:数值解细节(放大) + x_min, x_max = all_data[0]['x'].min(), all_data[0]['x'].max() + zoom_center = 1.0 # 阶跃函数位置附近 + zoom_width = 0.3 + + for i, data in enumerate(all_data): + if labels and i < len(labels): + label = labels[i] + else: + label = f"{data['scheme'].upper()}{data['order']}" + + style_idx = i % len(self.styles['comparison']) + style = self.styles['comparison'][style_idx] + + axes[1, 0].plot(data['x'], data['numerical'], label=label, **style) + + axes[1, 0].plot(all_data[0]['x'], all_data[0]['analytical'], + label="Analytical", **self.styles['analytical']) + + axes[1, 0].set_xlim(zoom_center - zoom_width/2, zoom_center + zoom_width/2) + axes[1, 0].set_xlabel('x', fontsize=12) + axes[1, 0].set_ylabel('u(x)', fontsize=12) + axes[1, 0].set_title('Zoomed View (x ≈ 1.0)', fontsize=14) + axes[1, 0].legend() + axes[1, 0].grid(True, alpha=0.3) + + # 子图4:性能统计 + axes[1, 1].axis('off') + + # 计算并显示L2误差 + errors = [] + schemes = [] + + for data in all_data: + error = np.sqrt(np.mean((data['numerical'] - data['analytical'])**2)) + errors.append(error) + schemes.append(f"{data['scheme'].upper()}{data['order']}") + + # 创建表格数据 + table_data = [] + for i, (scheme, error) in enumerate(zip(schemes, errors)): + table_data.append([scheme, f"{error:.2e}"]) + + # 在子图中显示表格 + table = axes[1, 1].table(cellText=table_data, + colLabels=['Scheme', 'L2 Error'], + loc='center', + cellLoc='center', + colWidths=[0.3, 0.4]) + + table.auto_set_font_size(False) + table.set_fontsize(10) + table.scale(1, 1.5) + + axes[1, 1].set_title('Performance Summary (L2 Error)', fontsize=14) + + # 设置总标题 + if title is None: + time = all_data[0]['time'] + schemes_str = ", ".join([self.format_scheme_label(d['scheme'], d['order']) for d in all_data]) + title = f"1D Convection Comparison (t={time:.3f})\n{schemes_str}" + + fig.suptitle(title, fontsize=16) + plt.tight_layout() + + # 保存或显示 + if save_path: + plt.savefig(save_path, dpi=150, bbox_inches='tight') + print(f"Saved comparison plot to {save_path}") + + if show: + plt.show() + else: + plt.close() + + return True + + def get_scheme_from_filename(self, filename): + print(f"filename={filename}") + stem = Path(filename).stem # e.g., "results_ENO3_40" + parts = stem.split('_') + if len(parts) >= 2: + return parts[1] # "ENO3", "WENO3", "WENO5" + return "" + + def plot_eno_weno_comparison(self, result_dir=".", save_path="eno_weno_comparison.png"): + """自动绘制ENO/WENO对比图(类似Julia的功能)""" + # 查找结果文件 + pattern = os.path.join(result_dir, "results_*.dat") + files = glob.glob(pattern) + + if not files: + print(f"No result files found matching {pattern}") + return False + + # 重新分类 + file_info = [] + eno_files = [] + weno3_files = [] + weno5_files = [] + for f in files: + print(f"f={f}") + print(f"type(f)={type(f)}") + scheme = self.get_scheme_from_filename(f) + print(f"scheme={scheme}") + if scheme == "ENO3": + eno_files.append(f) + elif scheme == "WENO3": + weno3_files.append(f) + elif scheme == "WENO5": + weno5_files.append(f) + + # 按求解器类型分类 + print(f"eno_files={eno_files}") + print(f"weno3_files={weno3_files}") + print(f"weno5_files={weno5_files}") + + # 选择最新的文件(如果有多组) + files_to_plot = [] + labels = [] + + if eno_files: + files_to_plot.append(sorted(eno_files)[-1]) + labels.append("ENO3") + + if weno3_files: + files_to_plot.append(sorted(weno3_files)[-1]) + labels.append("WENO3") + + if weno5_files: + files_to_plot.append(sorted(weno5_files)[-1]) + labels.append("WENO5") + + if len(files_to_plot) >= 2: + print(f"Plotting comparison of {len(files_to_plot)} solvers") + return self.plot_comparison(files_to_plot, labels=labels, + save_path=save_path, show=True) + else: + print("Not enough different solvers for comparison") + return False + +def main(): + """主函数""" + import argparse + + parser = argparse.ArgumentParser(description='Fortran CFD Results Visualizer') + parser.add_argument('--file', help='Plot single result file') + parser.add_argument('--compare', nargs='+', help='Compare multiple files') + parser.add_argument('--dir', default='.', help='Directory containing result files') + parser.add_argument('--auto', action='store_true', help='Auto plot ENO/WENO comparison') + parser.add_argument('--save', help='Save plot to file (without showing)') + parser.add_argument('--no-show', action='store_true', help='Don\'t show plot') + + args = parser.parse_args() + + plotter = FortranResultsPlotter() + + if args.file: + # 绘制单个文件 + plotter.plot_single_result(args.file, save_path=args.save, + show=not args.no_show) + + elif args.compare: + # 比较多个文件 + plotter.plot_comparison(args.compare, save_path=args.save, + show=not args.no_show) + + elif args.auto: + # 自动绘制比较图 + save_path = args.save or "eno_weno_comparison.png" + plotter.plot_eno_weno_comparison(args.dir, save_path=save_path) + + else: + # 默认:显示帮助 + parser.print_help() + + # 也显示可用的结果文件 + print("\nAvailable result files:") + pattern = os.path.join(args.dir, "results_*.dat") + files = glob.glob(pattern) + + if files: + for f in sorted(files): + print(f" {os.path.basename(f)}") + + print(f"\nTo plot ENO/WENO comparison automatically:") + print(f" python plot_results.py --auto") + else: + print(" No result files found") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/build.bat b/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/build.bat new file mode 100644 index 000000000..6fd6dc031 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/build.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Project Builder +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python build script with full Intel environment support... +echo. + +python build.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Build failed + pause + exit /b 1 +) + +echo. +echo [INFO] Build completed successfully! +echo. +echo [INFO] To run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/build.py b/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/build.py new file mode 100644 index 000000000..3bf6d5375 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/build.py @@ -0,0 +1,629 @@ +#!/usr/bin/env python3 +""" +Fortran CFD Project Builder - 完整Python解决方案 +在Python内部处理Intel oneAPI环境配置 +""" + +import os +import sys +import subprocess +import shutil +import argparse +import time +import platform +import tempfile +from pathlib import Path + +class IntelEnvironment: + """Intel oneAPI环境管理器""" + + def __init__(self): + self.setvars_path = None + self.env_vars = {} + + def find_setvars(self): + """查找setvars.bat文件""" + possible_paths = [ + r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat", + r"C:\Program Files\Intel\oneAPI\setvars.bat", + r"C:\Program Files (x86)\Intel\oneAPI\compiler\latest\env\vars.bat", + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\setvars.bat"), + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\compiler\latest\env\vars.bat"), + ] + + for path in possible_paths: + if os.path.exists(path): + self.setvars_path = path + return True + + return False + + def setup_environment(self): + """设置Intel环境""" + if not self.find_setvars(): + return False + + try: + # 创建临时的批处理文件来捕获环境变量 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + f.write(f'@echo off\n') + f.write(f'call "{self.setvars_path}" >nul 2>&1\n') + f.write(f'set\n') # 输出所有环境变量 + temp_bat = f.name + + # 运行批处理文件并捕获输出 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + shell=True + ) + + # 解析环境变量 + for line in result.stdout.split('\n'): + line = line.strip() + if '=' in line: + key, value = line.split('=', 1) + self.env_vars[key.strip()] = value.strip() + + # 清理临时文件 + os.unlink(temp_bat) + + # 更新当前进程的环境变量 + os.environ.update(self.env_vars) + + return True + + except Exception as e: + print(f"设置Intel环境失败: {e}") + return False + + def get_compiler_info(self): + """获取编译器信息""" + info = {} + + # 检查ifx编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + env={**os.environ, **self.env_vars} if self.env_vars else os.environ + ) + + if result.returncode == 0: + for line in result.stdout.split('\n'): + if 'Version' in line or '版本' in line: + info['ifx_version'] = line.strip() + break + except: + pass + + # 检查环境变量 + info['ifx_root'] = self.env_vars.get('IFX_ROOT', '') + info['compiler_root'] = self.env_vars.get('ONEAPI_ROOT', '') + + return info + +class BuildSystem: + """构建系统主类""" + + def __init__(self): + self.project_root = Path(__file__).parent.parent + self.build_dir = self.project_root / "build" + self.intel_env = IntelEnvironment() + + # 设置控制台编码 + if sys.platform == "win32": + try: + import ctypes + # 设置控制台输出为UTF-8 + ctypes.windll.kernel32.SetConsoleOutputCP(65001) + except: + pass + + def print_header(self, text): + """打印标题""" + print(f"\n{'='*70}") + print(f" {text}") + print(f"{'='*70}\n") + + def print_step(self, step, total, message): + """打印步骤""" + print(f"[{step}/{total}] {message}...") + + def print_success(self, message): + """打印成功""" + print(f"\033[92m✓ {message}\033[0m") + + def print_error(self, message): + """打印错误""" + print(f"\033[91m✗ {message}\033[0m") + + def print_warning(self, message): + """打印警告""" + print(f"\033[93m! {message}\033[0m") + + def print_info(self, message): + """打印信息""" + print(f"\033[94mℹ {message}\033[0m") + + def check_prerequisites(self): + """检查前提条件""" + self.print_step(1, 6, "检查前提条件") + + # 检查Python版本 + python_version = sys.version.split()[0] + self.print_info(f"Python版本: {python_version}") + + # 检查平台 + self.print_info(f"平台: {platform.system()} {platform.release()}") + self.print_info(f"处理器核心数: {os.cpu_count()}") + + # 检查CMake + try: + result = subprocess.run( + ["cmake", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + version_line = result.stdout.split('\n')[0] + self.print_success(f"CMake: {version_line}") + else: + self.print_error("CMake未找到") + return False + except FileNotFoundError: + self.print_error("CMake未安装") + return False + + return True + + def setup_intel_environment(self, args): + """设置Intel环境""" + self.print_step(2, 6, "配置Intel oneAPI环境") + + if not self.intel_env.find_setvars(): + self.print_warning("未找到Intel oneAPI setvars.bat") + self.print_info("将尝试使用系统环境中的编译器") + + # 检查是否能直接访问编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + self.print_success("Intel编译器在系统PATH中找到") + return True + else: + self.print_warning("Intel编译器未在PATH中找到") + except: + self.print_warning("无法访问Intel编译器") + + return True # 继续,让CMake自己找编译器 + + # 设置环境 + if self.intel_env.setup_environment(): + compiler_info = self.intel_env.get_compiler_info() + + if compiler_info.get('ifx_version'): + self.print_success(f"Intel Fortran编译器: {compiler_info['ifx_version']}") + elif compiler_info.get('ifx_root'): + self.print_success(f"Intel编译器路径: {compiler_info['ifx_root']}") + else: + self.print_success("Intel oneAPI环境配置完成") + + return True + else: + self.print_warning("Intel环境配置失败,将继续使用系统环境") + return True + + def clean_build_directory(self, args): + """清理构建目录""" + if args.clean and self.build_dir.exists(): + self.print_info("清理构建目录...") + try: + shutil.rmtree(self.build_dir) + self.print_success("构建目录已清理") + except Exception as e: + self.print_error(f"清理失败: {e}") + if not args.force: + return False + return True + + def run_command(self, cmd, cwd=None, check=True, env=None): + """运行命令""" + if isinstance(cmd, list): + cmd_str = ' '.join(str(c) for c in cmd if c) + else: + cmd_str = str(cmd) + + print(f" \033[96m$\033[0m {cmd_str}") + + try: + # 合并环境变量 + exec_env = os.environ.copy() + if env: + exec_env.update(env) + if self.intel_env.env_vars: + exec_env.update(self.intel_env.env_vars) + + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=False, + env=exec_env + ) + + # 处理输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or '完成' in line or '生成' in line: + print(f" \033[92m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + if check and result.returncode != 0: + self.print_error(f"命令执行失败,退出码: {result.returncode}") + return False + + return True + + except Exception as e: + self.print_error(f"命令执行异常: {e}") + return False + + def configure_cmake(self, args): + """配置CMake""" + self.print_step(3, 6, "配置CMake项目") + + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + f"-DCMAKE_BUILD_TYPE={args.build_type}", + ] + + if args.compiler == "ifx": + cmake_cmd.extend(["-T", "fortran=ifx"]) + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + success = self.run_command(cmake_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("CMake配置完成") + else: + self.print_error("CMake配置失败") + + return success + + def build_project(self, args): + """构建项目""" + self.print_step(4, 6, "构建项目") + + build_cmd = [ + "cmake", + "--build", ".", + "--config", args.build_type, + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + success = self.run_command(build_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("项目构建完成") + else: + self.print_error("构建失败") + + return success + + def run_tests_with_environment(self, test_exe): + """运行单个测试,确保有Intel环境""" + try: + # 创建临时的批处理文件来运行测试 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'"{test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'"{test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + return result + + except Exception as e: + print(f"运行测试失败: {e}") + return None + + def run_tests(self, args): + """运行测试""" + self.print_step(5, 6, "运行测试") + + # 查找测试可执行文件 + test_dir = self.build_dir / "bin" / args.build_type + if not test_dir.exists(): + test_dir = self.build_dir / "bin" + if not test_dir.exists(): + test_dir = self.build_dir + + test_files = list(test_dir.glob("test_*.exe")) + + if not test_files: + self.print_warning("未找到测试程序") + return True + + all_passed = True + + for test_exe in sorted(test_files): + test_name = test_exe.stem + self.print_info(f"运行测试: {test_name}") + print(f" {'-'*50}") + + # 运行测试 + result = self.run_tests_with_environment(str(test_exe)) + + if result is None: + self.print_error(f" {test_name} 运行失败") + all_passed = False + continue + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + print(f" {line}") + + if result.returncode == 0: + self.print_success(f" {test_name} 通过") + else: + self.print_error(f" {test_name} 失败 (退出码: {result.returncode})") + all_passed = False + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + print() # 空行 + + return all_passed + + def create_test_runner(self, args): + """创建独立的测试运行器""" + self.print_step(6, 6, "创建测试运行器") + + runner_path = self.build_dir / "run_tests.bat" + + content = f'''@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Test Runner +echo ======================================== +echo. + +REM Setup Intel oneAPI environment +set "SETVARS_PATH={self.intel_env.setvars_path or ''}" +if exist "%SETVARS_PATH%" ( + call "%SETVARS_PATH%" >nul + echo [INFO] Intel environment configured +) else ( + echo [WARNING] Intel environment not found + echo [WARNING] Tests may fail without runtime libraries +) + +echo. + +REM Run all test executables +set "TEST_COUNT=0" +set "PASS_COUNT=0" + +for %%f in ("bin\\{args.build_type}\\test_*.exe") do ( + set /a TEST_COUNT+=1 + echo [TEST %%f] + echo {'-'*50} + + %%f + if errorlevel 1 ( + echo [FAILED] %%f + ) else ( + echo [PASSED] %%f + set /a PASS_COUNT+=1 + ) + echo. +) + +echo ======================================== +echo Tests: %PASS_COUNT%/%TEST_COUNT% passed +if %PASS_COUNT% equ %TEST_COUNT% ( + echo [SUCCESS] All tests passed! +) else ( + echo [FAILURE] Some tests failed +) +echo ======================================== + +pause +''' + + with open(runner_path, 'w', encoding='utf-8') as f: + f.write(content) + + self.print_success(f"测试运行器已创建: {runner_path}") + self.print_info(f"使用方法: cd build && run_tests.bat") + + return runner_path + + def generate_report(self, args, build_time, tests_passed): + """生成构建报告""" + self.print_header("构建完成") + + print(f"项目: {self.project_root.name}") + print(f"构建类型: {args.build_type}") + print(f"编译器: {args.compiler}") + print(f"并行作业: {args.jobs}") + print(f"总耗时: {build_time:.1f}秒") + print(f"测试结果: {'全部通过' if tests_passed else '有失败'}") + + # 显示生成的可执行文件 + bin_dir = self.build_dir / "bin" / args.build_type + if bin_dir.exists(): + print(f"\n生成的可执行文件:") + for exe in sorted(bin_dir.glob("*.exe")): + size_mb = exe.stat().st_size / (1024 * 1024) + print(f" • {exe.name} ({size_mb:.2f} MB)") + + # 显示测试运行器信息 + runner_path = self.build_dir / "run_tests.bat" + if runner_path.exists(): + print(f"\n独立测试运行器:") + print(f" • {runner_path.name}") + print(f" 在Intel oneAPI环境中运行所有测试") + + def run(self): + """运行构建系统""" + parser = argparse.ArgumentParser( + description="Fortran CFD项目构建工具", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认构建 + %(prog)s --clean # 清理后构建 + %(prog)s --build-type Release # Release构建 + %(prog)s --no-tests # 只构建,不运行测试 + %(prog)s -j8 --verbose # 8线程并行构建,详细输出 + """ + ) + + parser.add_argument("--build-type", choices=["Debug", "Release"], + default="Debug", help="构建类型") + parser.add_argument("--compiler", choices=["ifx", "ifort"], + default="ifx", help="Fortran编译器") + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-tests", action="store_true", + help="跳过测试") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + + args = parser.parse_args() + + # 开始构建 + start_time = time.time() + + self.print_header("Fortran CFD 项目构建系统") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 检查前提条件 + if not self.check_prerequisites(): + if not args.force: + return 1 + + # 2. 设置Intel环境 + if not self.setup_intel_environment(args): + if not args.force: + return 1 + + # 3. 清理目录 + if not self.clean_build_directory(args): + if not args.force: + return 1 + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 4. 配置CMake + if not self.configure_cmake(args): + if not args.force: + return 1 + + # 5. 构建项目 + if not self.build_project(args): + if not args.force: + return 1 + + # 6. 运行测试和创建测试运行器 + tests_passed = True + if not args.no_tests: + tests_passed = self.run_tests(args) + + # 创建测试运行器 + self.create_test_runner(args) # 传递 args 参数 + + # 7. 生成报告 + build_time = time.time() - start_time + self.generate_report(args, build_time, tests_passed) + + return 0 if tests_passed else 1 + + except KeyboardInterrupt: + self.print_error("\n构建被用户中断") + return 1 + except Exception as e: + self.print_error(f"构建过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + builder = BuildSystem() + return builder.run() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/requirements.txt b/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/requirements.txt new file mode 100644 index 000000000..d21a12b45 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/requirements.txt @@ -0,0 +1,2 @@ +# 构建脚本的Python依赖(可选) +# 目前没有特殊依赖,保持空文件或添加未来可能需要的包 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/run_all_steps.bat b/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/run_all_steps.bat new file mode 100644 index 000000000..d506149b2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/run_all_steps.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo CFD Project: All Steps +echo ======================================== +echo. + +echo [INFO] Starting Step 1: Physics Modules Test... +call run_step1.bat + +if errorlevel 1 ( + echo [ERROR] Step 1 failed + pause + exit /b 1 +) + +echo. +echo [INFO] Starting Step 2: Configuration Physics Update... +call run_step2.bat + +if errorlevel 1 ( + echo [ERROR] Step 2 failed + pause + exit /b 1 +) + +echo. +echo ======================================== +echo All Steps Completed Successfully! +echo ======================================== +echo. +echo [INFO] Next: Update component manager for physics support +echo [INFO] Run: run_step3.bat (to be created) +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/run_example.py b/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/run_example.py new file mode 100644 index 000000000..d7c199178 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/run_example.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# scripts/run_example.py +""" +运行ENO/WENO示例程序 +""" + +import os +import sys +import subprocess +from pathlib import Path + +# 添加当前目录到路径 +sys.path.insert(0, str(Path(__file__).parent)) + +try: + from build import BuildSystem +except ImportError: + print("错误: 找不到build.py,请确保在scripts目录中运行") + sys.exit(1) + +def run_example(): + """运行示例程序""" + builder = BuildSystem() + + print("\n" + "="*70) + print(" 运行ENO/WENO对比示例程序") + print("="*70 + "\n") + + # 检查是否已构建 + exe_path = builder.build_dir / "bin" / "Debug" / "example_eno_weno_comparison.exe" + + if not exe_path.exists(): + print("示例程序未构建,先构建项目...") + print("-"*50) + + # 使用简化的构建 + result = subprocess.run( + ["python", "build.py", "--no-tests", "--clean"], + cwd=builder.project_root / "scripts", + capture_output=True, + text=True + ) + + if result.returncode != 0: + print("构建失败:") + print(result.stderr) + return False + + # 运行示例程序 + print("运行示例程序...") + print("-"*50) + + try: + result = subprocess.run( + [str(exe_path)], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace' + ) + + print(result.stdout) + + if result.stderr: + print("标准错误输出:") + print(result.stderr) + + return result.returncode == 0 + + except Exception as e: + print(f"运行示例程序失败: {e}") + return False + +def main(): + """主函数""" + success = run_example() + + if success: + print("\n" + "="*70) + print(" ✓ 示例程序运行成功") + print("="*70) + return 0 + else: + print("\n" + "="*70) + print(" ✗ 示例程序运行失败") + print("="*70) + return 1 + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/run_step1.bat b/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/run_step1.bat new file mode 100644 index 000000000..0b6b1f17e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/run_step1.bat @@ -0,0 +1,39 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Step 1: Physics Modules Test +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python step1 script with full Intel environment support... +echo. + +python run_step1.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Step 1 failed + pause + exit /b 1 +) + +echo. +echo [INFO] Step 1 completed successfully! +echo. +echo [INFO] Next step: Update config to include physics settings +echo [INFO] Run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/run_step1.py b/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/run_step1.py new file mode 100644 index 000000000..5e087a690 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/run_step1.py @@ -0,0 +1,319 @@ +#!/usr/bin/env python3 +""" +Step 1: Physics Modules Test +扩展build.py,专门用于测试物理模块 +""" + +import os +import sys +import subprocess +import time +from pathlib import Path + +# 添加当前目录到路径,以便导入build.py的类 +sys.path.insert(0, str(Path(__file__).parent)) + +try: + from build import IntelEnvironment, BuildSystem +except ImportError: + print("Error: Cannot import build.py. Make sure build.py is in the same directory.") + sys.exit(1) + +class Step1System(BuildSystem): + """Step 1 测试系统,继承自BuildSystem""" + + def __init__(self): + super().__init__() + self.test_name = "test_physics_minimal" + self.test_exe = None + + def find_test_executable(self): + """查找测试可执行文件""" + possible_paths = [ + self.build_dir / "bin" / "Debug" / f"{self.test_name}.exe", + self.build_dir / "Debug" / f"{self.test_name}.exe", + self.build_dir / f"{self.test_name}.exe", + self.build_dir / "bin" / f"{self.test_name}.exe", + ] + + for path in possible_paths: + if path.exists(): + self.test_exe = path + self.print_success(f"Found test executable: {path}") + return True + + # 如果没有找到,尝试搜索 + self.print_warning(f"Could not find {self.test_name}.exe") + self.print_info("Searching for test executables...") + + try: + result = subprocess.run( + ["dir", str(self.build_dir), "/s", "/b", "*.exe"], + capture_output=True, + text=True, + encoding='utf-8', + shell=True + ) + + if result.returncode == 0: + test_files = [line.strip() for line in result.stdout.split('\n') + if line and 'test_' in line.lower()] + + if test_files: + self.print_info("Found test files:") + for test_file in test_files: + self.print_info(f" {test_file}") + return False + except: + pass + + return False + + def run_test_with_intel_env(self): + """在Intel环境下运行测试""" + if not self.test_exe: + self.print_error("No test executable found") + return False + + self.print_step(1, 2, f"Running test: {self.test_exe.name}") + + try: + # 创建临时的批处理文件来运行测试(包含Intel环境) + import tempfile + + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + # 设置Intel环境 + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'echo [INFO] Intel environment configured\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'echo [WARNING] Intel environment not found\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + self.print_info(f"Command: {temp_bat}") + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower() or 'fail' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or 'pass' in line.lower() or '✓' in line: + print(f" \033[92m{line}\033[0m") + elif '=' in line or '---' in line: + print(f" \033[96m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + self.print_warning("Test stderr output:") + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + self.print_step(2, 2, "Test execution completed") + + if result.returncode == 0: + self.print_success("Test passed") + return True + else: + self.print_error(f"Test failed (exit code: {result.returncode})") + return False + + except Exception as e: + self.print_error(f"Failed to run test: {e}") + return False + + def build_project_if_needed(self, args): + """如果需要,构建项目""" + if args.no_build: + self.print_info("Skipping build (--no-build flag)") + return True + + self.print_step(1, 3, "Building project") + + # 调用父类的构建方法 + build_args = argparse.Namespace() + build_args.clean = args.clean + build_args.build_type = "Debug" + build_args.compiler = "ifx" + build_args.no_tests = True # 不运行所有测试 + build_args.jobs = os.cpu_count() + build_args.verbose = args.verbose + build_args.force = args.force + + # 清理构建目录 + if args.clean and self.build_dir.exists(): + self.print_info("Cleaning build directory...") + import shutil + try: + shutil.rmtree(self.build_dir) + self.print_success("Build directory cleaned") + except Exception as e: + self.print_error(f"Clean failed: {e}") + if not args.force: + return False + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 配置CMake + self.print_step(2, 3, "Configuring CMake") + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + "-DCMAKE_BUILD_TYPE=Debug", + "-T", "fortran=ifx", + ] + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + if not self.run_command(cmake_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + # 构建项目 + self.print_step(3, 3, "Building project") + build_cmd = [ + "cmake", + "--build", ".", + "--config", "Debug", + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + if not self.run_command(build_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + self.print_success("Build completed") + return True + + def run(self): + """运行Step 1测试""" + parser = argparse.ArgumentParser( + description="Step 1: Physics Modules Test", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认运行 + %(prog)s --clean # 清理后构建并测试 + %(prog)s --no-build # 只运行测试,不重新构建 + %(prog)s --verbose # 详细输出 + %(prog)s -j4 # 使用4个并行作业构建 + """ + ) + + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-build", action="store_true", + help="不重新构建,直接运行测试") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + + args = parser.parse_args() + + # 开始测试 + start_time = time.time() + + self.print_header("Step 1: Physics Modules Implementation Test") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 设置Intel环境 + self.print_step(1, 4, "Setting up Intel oneAPI environment") + if not self.setup_intel_environment(args): + if not args.force: + self.print_error("Intel environment setup failed") + return 1 + self.print_warning("Intel environment setup failed, continuing...") + + # 2. 构建项目(如果需要) + self.print_step(2, 4, "Building project if needed") + if not self.build_project_if_needed(args): + if not args.force: + return 1 + + # 3. 查找测试可执行文件 + self.print_step(3, 4, "Finding test executable") + if not self.find_test_executable(): + if not args.force: + return 1 + self.print_warning("Test executable not found, but continuing due to --force") + return 0 + + # 4. 运行测试 + self.print_step(4, 4, "Running physics module test") + test_passed = self.run_test_with_intel_env() + + # 生成报告 + test_time = time.time() - start_time + self.print_header("Step 1 Complete") + + print(f"测试: {'通过 ✓' if test_passed else '失败 ✗'}") + print(f"测试程序: {self.test_exe.name if self.test_exe else '未找到'}") + print(f"总耗时: {test_time:.1f}秒") + + if test_passed: + print(f"\n下一步: 更新配置以包含物理设置") + print(f"建议: 修改config.f90,添加physics相关字段") + return 0 + else: + if args.force: + self.print_warning("测试失败,但由于--force标志继续执行") + return 0 + return 1 + + except KeyboardInterrupt: + self.print_error("\n测试被用户中断") + return 1 + except Exception as e: + self.print_error(f"测试过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + system = Step1System() + return system.run() + +if __name__ == "__main__": + # 需要导入argparse + import argparse + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/run_step2.bat b/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/run_step2.bat new file mode 100644 index 000000000..9c1f62de4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/run_step2.bat @@ -0,0 +1,39 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Step 2: Configuration Physics Update +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python step2 script with full Intel environment support... +echo. + +python run_step2.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Step 2 failed + pause + exit /b 1 +) + +echo. +echo [INFO] Step 2 completed successfully! +echo. +echo [INFO] Next step: Update component manager to support physics +echo [INFO] Run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/run_step2.py b/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/run_step2.py new file mode 100644 index 000000000..c16b76088 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/run_step2.py @@ -0,0 +1,284 @@ +#!/usr/bin/env python3 +""" +Step 2: Configuration Physics Update +测试配置模块的物理功能更新 +""" + +import os +import sys +import subprocess +import time +from pathlib import Path + +# 添加当前目录到路径,以便导入build.py的类 +sys.path.insert(0, str(Path(__file__).parent)) + +try: + from build import IntelEnvironment, BuildSystem +except ImportError: + print("Error: Cannot import build.py. Make sure build.py is in the same directory.") + sys.exit(1) + +class Step2System(BuildSystem): + """Step 2 测试系统,继承自BuildSystem""" + + def __init__(self): + super().__init__() + self.test_name = "test_config_physics" + self.test_exe = None + + def find_test_executable(self): + """查找测试可执行文件""" + possible_paths = [ + self.build_dir / "bin" / "Debug" / f"{self.test_name}.exe", + self.build_dir / "Debug" / f"{self.test_name}.exe", + self.build_dir / f"{self.test_name}.exe", + self.build_dir / "bin" / f"{self.test_name}.exe", + ] + + for path in possible_paths: + if path.exists(): + self.test_exe = path + self.print_success(f"Found test executable: {path}") + return True + + return False + + def run_test_with_intel_env(self): + """在Intel环境下运行测试""" + if not self.test_exe: + self.print_error("No test executable found") + return False + + self.print_step(1, 2, f"Running test: {self.test_exe.name}") + + try: + # 创建临时的批处理文件来运行测试(包含Intel环境) + import tempfile + + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + # 设置Intel环境 + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'echo [INFO] Intel environment configured\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'echo [WARNING] Intel environment not found\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + self.print_info(f"Command: {temp_bat}") + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower() or 'fail' in line.lower() or '✗' in line: + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or 'pass' in line.lower() or '✓' in line: + print(f" \033[92m{line}\033[0m") + elif '=' in line or '---' in line or '===' in line: + print(f" \033[96m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + self.print_warning("Test stderr output:") + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + self.print_step(2, 2, "Test execution completed") + + if result.returncode == 0: + self.print_success("Test passed") + return True + else: + self.print_error(f"Test failed (exit code: {result.returncode})") + return False + + except Exception as e: + self.print_error(f"Failed to run test: {e}") + return False + + def build_project(self, args): + """构建项目""" + if args.no_build: + self.print_info("Skipping build (--no-build flag)") + return True + + self.print_step(1, 3, "Building project") + + # 清理构建目录 + if args.clean and self.build_dir.exists(): + self.print_info("Cleaning build directory...") + import shutil + try: + shutil.rmtree(self.build_dir) + self.print_success("Build directory cleaned") + except Exception as e: + self.print_error(f"Clean failed: {e}") + if not args.force: + return False + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 配置CMake + self.print_step(2, 3, "Configuring CMake") + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + "-DCMAKE_BUILD_TYPE=Debug", + "-T", "fortran=ifx", + ] + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + if not self.run_command(cmake_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + # 构建项目 + self.print_step(3, 3, "Building project") + build_cmd = [ + "cmake", + "--build", ".", + "--config", "Debug", + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + if not self.run_command(build_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + self.print_success("Build completed") + return True + + def run(self): + """运行Step 2测试""" + import argparse + + parser = argparse.ArgumentParser( + description="Step 2: Configuration Physics Update", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认运行 + %(prog)s --clean # 清理后构建并测试 + %(prog)s --no-build # 只运行测试,不重新构建 + %(prog)s --verbose # 详细输出 + """ + ) + + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-build", action="store_true", + help="不重新构建,直接运行测试") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + + args = parser.parse_args() + + # 开始测试 + start_time = time.time() + + self.print_header("Step 2: Configuration Physics Update") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 设置Intel环境 + self.print_step(1, 4, "Setting up Intel oneAPI environment") + if not self.setup_intel_environment(args): + if not args.force: + self.print_error("Intel environment setup failed") + return 1 + self.print_warning("Intel environment setup failed, continuing...") + + # 2. 构建项目(如果需要) + self.print_step(2, 4, "Building project if needed") + if not self.build_project(args): + if not args.force: + return 1 + + # 3. 查找测试可执行文件 + self.print_step(3, 4, "Finding test executable") + if not self.find_test_executable(): + self.print_error(f"Test executable {self.test_name}.exe not found") + if not args.force: + return 1 + self.print_warning("Test executable not found, but continuing due to --force") + return 0 + + # 4. 运行测试 + self.print_step(4, 4, "Running configuration physics test") + test_passed = self.run_test_with_intel_env() + + # 生成报告 + test_time = time.time() - start_time + self.print_header("Step 2 Complete") + + print(f"测试: {'通过 ✓' if test_passed else '失败 ✗'}") + print(f"测试程序: {self.test_exe.name if self.test_exe else '未找到'}") + print(f"总耗时: {test_time:.1f}秒") + + if test_passed: + print(f"\n下一步: 更新组件管理器以支持物理模块") + print(f"建议: 修改component_manager.f90,添加physics组件创建") + return 0 + else: + if args.force: + self.print_warning("测试失败,但由于--flag继续执行") + return 0 + return 1 + + except KeyboardInterrupt: + self.print_error("\n测试被用户中断") + return 1 + except Exception as e: + self.print_error(f"测试过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + system = Step2System() + return system.run() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/test_integrated.py b/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/test_integrated.py new file mode 100644 index 000000000..91020a945 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/scripts/test_integrated.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# scripts/test_integrated.py +""" +测试集成求解器 +""" +import subprocess +import sys +import os +from pathlib import Path + +def run_test(): + """运行集成测试""" + # 构建项目 + print("构建项目...") + result = subprocess.run( + ["python", "scripts/build.py", "--no-tests", "--clean"], + capture_output=True, + text=True + ) + + if result.returncode != 0: + print("构建失败:") + print(result.stderr) + return False + + # 运行集成示例 + print("\n运行集成示例...") + exe_path = Path("build/bin/Debug/run_eno_weno_integrated.exe") + + if not exe_path.exists(): + print(f"可执行文件不存在: {exe_path}") + return False + + result = subprocess.run( + [str(exe_path)], + capture_output=True, + text=True, + encoding='utf-8' + ) + + print(result.stdout) + if result.stderr: + print("错误输出:") + print(result.stderr) + + return result.returncode == 0 + +if __name__ == "__main__": + success = run_test() + sys.exit(0 if success else 1) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03f/src/CMakeLists.txt new file mode 100644 index 000000000..ef816d134 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/CMakeLists.txt @@ -0,0 +1,41 @@ +# 修改后的完整版本: +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 按依赖顺序添加子目录 +add_subdirectory(base) # 1. 基础类型和精度 +add_subdirectory(core) # 2. 核心注册系统 +add_subdirectory(infrastructure) # 3. 基础设施(配置、网格、域、解) +add_subdirectory(physics) # 4. 物理模块(方程和问题) +add_subdirectory(numerics) # 5. 数值方法(重构器、通量、时间积分) +add_subdirectory(manager) # 6. 组件管理器和工厂 +add_subdirectory(solver) # 7. 求解器 + +# ==================== 新增:边界条件和初始条件模块 ==================== +message(STATUS "配置边界条件模块...") +add_subdirectory(boundary) + +message(STATUS "配置初始条件模块...") +add_subdirectory(initial_condition) + +# ==================== 新增:结果模块 ==================== +message(STATUS "配置结果模块...") + +add_library(results STATIC + results.f90 # 新增文件 +) + +target_link_libraries(results + PRIVATE + base + infrastructure + solver +) + +set_target_properties(results PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "集成求解器库配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/base/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03f/src/base/CMakeLists.txt new file mode 100644 index 000000000..74f4aa65f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/base/CMakeLists.txt @@ -0,0 +1,16 @@ +# src/base/CMakeLists.txt +message(STATUS "Configuring base module...") + +add_library(base STATIC + modules.f90 + precision.f90 # 新增 +) + +set_target_properties(base PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Base module configured") + +# src/core/CMakeLists.txt +message(STATUS "Configuring core module...") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/base/modules.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/base/modules.f90 new file mode 100644 index 000000000..43aaee241 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/base/modules.f90 @@ -0,0 +1,36 @@ +! src/base/modules.f90 +module base_modules + use, intrinsic :: iso_fortran_env, only: real64, int32 + implicit none + + public :: wp, ip, max_name_len, string_len, cfd_config_base, component_info + + integer, parameter :: wp = real64 + integer, parameter :: ip = int32 + integer, parameter :: string_len = 100 + integer, parameter :: max_name_len = 32 + + ! 基础配置类型 + type :: cfd_config_base + character(len=max_name_len) :: ic_type = "step" + character(len=max_name_len) :: recon_scheme = "eno" + character(len=max_name_len) :: flux_type = "rusanov" + integer(ip) :: rk_order = 1 + real(wp) :: wave_speed = 1.0_wp + real(wp) :: final_time = 0.625_wp + real(wp) :: dt = 0.025_wp + character(len=max_name_len) :: boundary_type = "periodic" + integer(ip) :: spatial_order = 2 + character(len=max_name_len) :: equation_type = "linear_advection" + character(len=max_name_len) :: problem_type = "linear_advection" + logical :: verbose = .true. + end type cfd_config_base + + ! 组件信息类型 + type :: component_info + character(len=max_name_len) :: category = "" + character(len=max_name_len) :: name = "" + integer(ip) :: order = 0 + end type component_info + +end module base_modules \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/base/precision.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/base/precision.f90 new file mode 100644 index 000000000..4ac5fd7ef --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/base/precision.f90 @@ -0,0 +1,9 @@ +! src/base/precision.f90(简单版本) +module precision_module + use base_modules, only: wp, ip + implicit none + + ! 重新导出,确保兼容 + public :: wp, ip + +end module precision_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/boundary/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03f/src/boundary/CMakeLists.txt new file mode 100644 index 000000000..a9909f1e5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/boundary/CMakeLists.txt @@ -0,0 +1,23 @@ +# src/boundary/CMakeLists.txt +message(STATUS "配置边界条件模块...") + +add_library(boundary STATIC + boundary_base.f90 # 基类 + periodic.f90 # 周期性边界 + dirichlet.f90 # Dirichlet边界 + neumann.f90 # Neumann边界 + factory.f90 # 工厂 +) + +target_link_libraries(boundary + PRIVATE + base + infrastructure + core # 需要注册系统 +) + +set_target_properties(boundary PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "边界条件模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/boundary/boundary_base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/boundary/boundary_base.f90 new file mode 100644 index 000000000..2f7b11163 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/boundary/boundary_base.f90 @@ -0,0 +1,35 @@ +! src/boundary/boundary_base.f90 (修正版) +module boundary_base_module + use base_modules, only: wp, ip + implicit none + private + + type, abstract, public :: boundary_condition + character(len=:), allocatable :: name + contains + procedure(apply_interface), deferred :: apply + procedure :: get_name => bc_get_name + end type + + abstract interface + subroutine apply_interface(this, u, nghosts, ist, ied) + import :: boundary_condition, wp, ip + class(boundary_condition), intent(in) :: this + real(wp), intent(inout) :: u(:) + integer(ip), intent(in) :: nghosts, ist, ied + end subroutine apply_interface + end interface + +contains + + function bc_get_name(this) result(name) + class(boundary_condition), intent(in) :: this + character(len=:), allocatable :: name + if (allocated(this%name)) then + name = this%name + else + name = "unnamed" + end if + end function + +end module boundary_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/boundary/dirichlet.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/boundary/dirichlet.f90 new file mode 100644 index 000000000..e7647bea5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/boundary/dirichlet.f90 @@ -0,0 +1,55 @@ +! src/boundary/dirichlet.f90 +module dirichlet_boundary_module + use base_modules, only: wp, ip + use boundary_base_module, only: boundary_condition + implicit none + private + + type, extends(boundary_condition), public :: dirichlet_boundary + real(wp) :: left_value = 1.0_wp + real(wp) :: right_value = 2.0_wp + contains + procedure :: apply => dirichlet_apply + procedure :: set_values => dirichlet_set_values + end type + + interface dirichlet_boundary + module procedure create_dirichlet_boundary + end interface + +contains + + type(dirichlet_boundary) function create_dirichlet_boundary(left_val, right_val) result(this) + real(wp), optional, intent(in) :: left_val, right_val + + this%name = "dirichlet" + if (present(left_val)) this%left_value = left_val + if (present(right_val)) this%right_value = right_val + end function + + subroutine dirichlet_set_values(this, left_val, right_val) + class(dirichlet_boundary), intent(inout) :: this + real(wp), intent(in) :: left_val, right_val + this%left_value = left_val + this%right_value = right_val + end subroutine + + subroutine dirichlet_apply(this, u, nghosts, ist, ied) + class(dirichlet_boundary), intent(in) :: this + real(wp), intent(inout) :: u(:) + integer(ip), intent(in) :: nghosts, ist, ied + + integer :: i + + ! 左边界 + do i = 0, nghosts-1 + u(ist-1-i) = this%left_value + end do + + ! 右边界 + do i = 0, nghosts-1 + u(ied+i) = this%right_value + end do + end subroutine + +end module dirichlet_boundary_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/boundary/factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/boundary/factory.f90 new file mode 100644 index 000000000..012363b05 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/boundary/factory.f90 @@ -0,0 +1,41 @@ +! src/boundary/factory.f90 +module boundary_factory_module + use base_modules, only: wp, ip + use boundary_base_module, only: boundary_condition + use periodic_boundary_module, only: periodic_boundary + implicit none + private + + public :: create_boundary_condition, initialize_boundary_factory + +contains + + subroutine initialize_boundary_factory() + print *, "[BOUNDARY FACTORY] Boundary conditions placeholder" + end subroutine + + subroutine create_boundary_condition(bc_type, bc_instance, left_val, right_val) + character(len=*), intent(in) :: bc_type + class(boundary_condition), allocatable, intent(out) :: bc_instance + real(wp), optional, intent(in) :: left_val, right_val + + ! 暂时只实现周期性边界 + allocate(periodic_boundary :: bc_instance) + + select case (trim(bc_type)) + case ("periodic") + select type(bc => bc_instance) + type is (periodic_boundary) + bc%name = "periodic" + end select + + case default + print *, "[WARNING] Using periodic as default boundary" + select type(bc => bc_instance) + type is (periodic_boundary) + bc%name = "periodic" + end select + end select + end subroutine + +end module boundary_factory_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/boundary/neumann.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/boundary/neumann.f90 new file mode 100644 index 000000000..908869d94 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/boundary/neumann.f90 @@ -0,0 +1,41 @@ +! src/boundary/neumann.f90 +module neumann_boundary_module + use base_modules, only: wp, ip + use boundary_base_module, only: boundary_condition + implicit none + private + + type, extends(boundary_condition), public :: neumann_boundary + contains + procedure :: apply => neumann_apply + end type + + interface neumann_boundary + module procedure create_neumann_boundary + end interface + +contains + + type(neumann_boundary) function create_neumann_boundary() result(this) + this%name = "neumann" + end function + + subroutine neumann_apply(this, u, nghosts, ist, ied) + class(neumann_boundary), intent(in) :: this + real(wp), intent(inout) :: u(:) + integer(ip), intent(in) :: nghosts, ist, ied + + integer :: i + + ! 左边界零梯度:u[ist-1-i] = u[ist+i] + do i = 0, nghosts-1 + u(ist-1-i) = u(ist+i) + end do + + ! 右边界零梯度:u[ied+i] = u[ied-1-i] + do i = 0, nghosts-1 + u(ied+i) = u(ied-1-i) + end do + end subroutine + +end module neumann_boundary_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/boundary/periodic.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/boundary/periodic.f90 new file mode 100644 index 000000000..1fad1f036 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/boundary/periodic.f90 @@ -0,0 +1,44 @@ +! src/boundary/periodic.f90 (修正版) +module periodic_boundary_module + use base_modules, only: wp, ip + use boundary_base_module, only: boundary_condition + implicit none + private + + type, extends(boundary_condition), public :: periodic_boundary + contains + procedure :: apply => periodic_apply + procedure :: get_name => bc_get_name + end type + +contains + + subroutine periodic_apply(this, u, nghosts, ist, ied) + class(periodic_boundary), intent(in) :: this + real(wp), intent(inout) :: u(:) + integer(ip), intent(in) :: nghosts, ist, ied + + integer :: i + + ! 左ghost层:u[ist-1-i] = u[ied-1-i] + do i = 0, nghosts-1 + u(ist-1-i) = u(ied-1-i) + end do + + ! 右ghost层:u[ied+i] = u[ist+i] + do i = 0, nghosts-1 + u(ied+i) = u(ist+i) + end do + end subroutine + + function bc_get_name(this) result(name) + class(periodic_boundary), intent(in) :: this + character(len=:), allocatable :: name + if (allocated(this%name)) then + name = this%name + else + name = "periodic" + end if + end function + +end module periodic_boundary_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03f/src/core/CMakeLists.txt new file mode 100644 index 000000000..d8b8df064 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/core/CMakeLists.txt @@ -0,0 +1,14 @@ +# src/core/CMakeLists.txt +message(STATUS "Configuring core module...") + +add_library(core STATIC + registry.f90 +) + +target_link_libraries(core PRIVATE base) + +set_target_properties(core PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Core module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/core/factory_base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/core/factory_base.f90 new file mode 100644 index 000000000..302418a15 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/core/factory_base.f90 @@ -0,0 +1,57 @@ +! src/core/factory_base.f90 +module factory_base_module + use base_modules, only: wp, ip + use registry_module, only: create_component, has_component + + implicit none + private + public :: wp, ip, factory_base, factory_create + + ! 工厂基类 + type :: factory_base + character(len=max_name_length) :: category = "" + contains + procedure :: create => factory_base_create + procedure :: get_available => factory_base_get_available + end type factory_base + + ! 便捷函数类型 + abstract interface + function factory_function_interface(category, name) result(instance) + import :: wp + character(len=*), intent(in) :: category, name + class(*), allocatable :: instance + end function factory_function_interface + end interface + +contains + + ! 创建工厂实例 + function factory_create(category) result(factory) + character(len=*), intent(in) :: category + type(factory_base) :: factory + factory%category = trim(category) + end function factory_create + + ! 工厂创建方法 + function factory_base_create(this, name) result(instance) + class(factory_base), intent(in) :: this + character(len=*), intent(in) :: name + class(*), allocatable :: instance + + instance = create_component(this%category, name) + end function factory_base_create + + ! 获取可用组件列表(简化版) + subroutine factory_base_get_available(this, names, count) + class(factory_base), intent(in) :: this + character(len=*), allocatable, intent(out) :: names(:) + integer(ip), intent(out) :: count + + ! 这里需要实现从注册表获取列表的逻辑 + ! 暂时返回空列表 + count = 0 + allocate(character(len=max_name_length) :: names(0)) + end subroutine factory_base_get_available + +end module factory_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/core/factory_integrated.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/core/factory_integrated.f90 new file mode 100644 index 000000000..c58864c9d --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/core/factory_integrated.f90 @@ -0,0 +1,41 @@ +! src/core/factory_integrated.f90 +module factory_integrated + use base_modules, only: wp, ip + use registry_module, only: register_component_simple + implicit none + private + + public :: register_all_components + +contains + + subroutine register_all_components() + ! 方程 + call register_component_simple("equation", "linear_advection") + + ! 问题 + call register_component_simple("problem", "linear_advection") + + ! 重构器 + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + + ! 通量 + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist_osher") + + ! 边界条件 + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + + ! 初始条件 + call register_component_simple("initial_condition", "step") + call register_component_simple("initial_condition", "sin") + call register_component_simple("initial_condition", "gaussian") + + print *, "[FACTORY] All components registered" + end subroutine + +end module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..a960167c3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/core/factory_interfaces.f90 @@ -0,0 +1,17 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, factory_procedure + + ! Factory procedure interface + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/core/physics_solver_integrated.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/core/physics_solver_integrated.f90 new file mode 100644 index 000000000..56840e678 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/core/physics_solver_integrated.f90 @@ -0,0 +1,127 @@ +! src/solver/physics_solver_integrated.f90 +module physics_solver_integrated + use base_modules, only: wp, ip + use config_module, only: cfd_config + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create + use registry_module, only: create_component + + implicit none + private + + type, public :: physics_solver_integrated + ! 核心组件 + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + + ! 物理组件 + class(*), allocatable :: equation + class(*), allocatable :: problem + + ! 数值组件 + class(*), allocatable :: reconstructor + class(*), allocatable :: flux_calculator + class(*), allocatable :: boundary_condition + class(*), allocatable :: residual_calculator + + contains + procedure :: initialize => solver_initialize + procedure :: run => solver_run + procedure :: cleanup => solver_cleanup + procedure, private :: create_components + procedure, private :: apply_boundary + end type + +contains + + subroutine solver_initialize(this) + class(physics_solver_integrated), intent(inout) :: this + + ! 创建域和解 + this%domain = domain_create(this%config, this%mesh) + this%solution = solution_create(this%domain) + + ! 创建所有组件 + call this%create_components() + + ! 应用初始条件 + call this%apply_initial_condition() + + print *, "[SOLVER] Initialized with physics integration" + end subroutine + + subroutine create_components(this) + class(physics_solver_integrated), intent(inout) :: this + + ! 创建方程 + call create_component("equation", "linear_advection", this%config, this%equation) + + ! 创建问题 + call create_component("problem", "linear_advection", this%config, this%problem) + + ! 创建数值组件 + call create_component("reconstructor", this%config%recon_scheme, & + this%config, this%reconstructor) + call create_component("flux", this%config%flux_type, & + this%config, this%flux_calculator) + call create_component("boundary", this%config%boundary_type, & + this%cfd_context(), this%boundary_condition) + end subroutine + + subroutine apply_initial_condition(this) + class(physics_solver_integrated), intent(inout) :: this + + ! 通过问题创建初始条件 + select type(prob => this%problem) + type is (linear_advection_problem) + class(*), allocatable :: ic + call prob%create_ic(this%config, ic) + + ! 应用初始条件到解 + select type(ic_inst => ic) + type is (step_function_ic) + call ic_inst%apply(this%solution) + end select + end select + end subroutine + + function cfd_context(this) result(ctx) + class(physics_solver_integrated), intent(in) :: this + type(cfd_context_type) :: ctx + + ! 创建包含求解器所有组件的上下文 + ctx%config => this%config + ctx%domain => this%domain + ctx%solution => this%solution + ctx%equation => this%equation + end function + + subroutine solver_run(this, final_time) + class(physics_solver_integrated), intent(inout) :: this + real(wp), intent(in) :: final_time + + real(wp) :: t, dt, dt_original + integer :: step + + dt_original = this%config%dt + t = 0.0_wp + step = 0 + + do while (t < final_time - 1e-12_wp) + dt = min(this%config%dt, final_time - t) + + ! 时间步进(需要实现) + ! call this%time_step(dt) + + t = t + dt + step = step + 1 + end do + + this%config%dt = dt_original + print *, "[SOLVER] Completed at t = ", t, ", steps = ", step + end subroutine + +end module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/core/registry.f90 new file mode 100644 index 000000000..d155aa19b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/core/registry.f90 @@ -0,0 +1,257 @@ +! src/core/registry.f90 (更新版) +module registry_module + use base_modules, only: wp, ip, max_name_len, component_info + + implicit none + private + + ! 明确公开所有需要的接口 + public :: wp, ip ! 类型参数 + public :: component_info ! 类型 + public :: registry_init, registry_cleanup ! 初始化/清理 + public :: register_component_simple ! 注册组件 + public :: has_component_simple ! 检查组件 + public :: list_components ! 列出组件 + public :: registry_is_initialized ! 检查初始化状态 + public :: registry_get_size ! 获取大小 + public :: initialize_default_components ! 新增:初始化默认组件 + + ! 全局注册表 + type :: component_registry + type(component_info), allocatable :: components(:) + integer(ip) :: count = 0 + integer(ip) :: capacity = 100 + logical :: initialized = .false. + logical :: verbose = .true. + logical :: default_components_added = .false. ! 新增:标记是否已添加默认组件 + end type component_registry + + type(component_registry) :: registry + +contains + + ! ==================== 公共API ==================== + + subroutine registry_init(verbose) + logical, optional, intent(in) :: verbose + + if (registry%initialized) then + if (registry%verbose) then + print *, "[REGISTRY] Already initialized" + end if + return + end if + + if (present(verbose)) then + registry%verbose = verbose + end if + + allocate(registry%components(registry%capacity)) + registry%initialized = .true. + + if (registry%verbose) then + print *, "[REGISTRY] Initialized with capacity:", registry%capacity + end if + end subroutine registry_init + + subroutine registry_cleanup() + if (allocated(registry%components)) then + deallocate(registry%components) + end if + registry%initialized = .false. + registry%count = 0 + registry%default_components_added = .false. ! 重置标记 + + if (registry%verbose) then + print *, "[REGISTRY] Cleaned up" + end if + end subroutine registry_cleanup + + ! 新增:初始化默认组件 + subroutine initialize_default_components() + if (.not. registry%initialized) then + call registry_init() + end if + + if (registry%default_components_added) then + if (registry%verbose) then + print *, "[REGISTRY] Default components already added" + end if + return + end if + + ! 注册重构器 + call register_component_simple("reconstructor", "eno", order=3) + call register_component_simple("reconstructor", "weno3", order=3) + call register_component_simple("reconstructor", "weno5", order=5) + + ! 注册通量计算器 + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + + ! 注册边界条件 + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + + ! 注册时间积分器 + call register_component_simple("integrator", "rk1", order=1) + call register_component_simple("integrator", "rk2", order=2) + call register_component_simple("integrator", "rk3", order=3) + + ! 注册方程 + call register_component_simple("equation", "linear_advection") + + ! 注册问题 + call register_component_simple("problem", "linear_advection") + + ! 注册初始条件 + call register_component_simple("initial_condition", "step") + call register_component_simple("initial_condition", "sin") + call register_component_simple("initial_condition", "gaussian") + + registry%default_components_added = .true. + + if (registry%verbose) then + print *, "[REGISTRY] Default components registered" + print *, "[REGISTRY] Total components:", registry%count + end if + end subroutine initialize_default_components + + subroutine register_component_simple(category, name, order) + character(len=*), intent(in) :: category, name + integer(ip), optional, intent(in) :: order + + integer(ip) :: i + type(component_info) :: info + + if (.not. registry%initialized) then + call registry_init() + end if + + ! 检查是否已存在 + do i = 1, registry%count + if (trim(registry%components(i)%category) == trim(category) .and. & + trim(registry%components(i)%name) == trim(name)) then + if (registry%verbose) then + print *, "[WARN] Overwriting component: ", trim(category), ".", trim(name) + end if + + ! 更新 + if (present(order)) then + registry%components(i)%order = order + else + registry%components(i)%order = 0 + end if + return + end if + end do + + ! 扩展数组 + if (registry%count >= registry%capacity) then + call expand_registry() + end if + + ! 添加新组件 + registry%count = registry%count + 1 + + info%category = trim(category) + info%name = trim(name) + info%order = 0 + if (present(order)) then + info%order = order + end if + + registry%components(registry%count) = info + + if (registry%verbose) then + print *, "[OK] Registered simple: ", trim(category), ".", trim(name) + end if + end subroutine register_component_simple + + logical function has_component_simple(category, name) + character(len=*), intent(in) :: category, name + + integer(ip) :: i + + has_component_simple = .false. + + if (.not. registry%initialized) return + + do i = 1, registry%count + if (trim(registry%components(i)%category) == trim(category) .and. & + trim(registry%components(i)%name) == trim(name)) then + has_component_simple = .true. + return + end if + end do + end function has_component_simple + + subroutine list_components(category) + character(len=*), optional, intent(in) :: category + + integer(ip) :: i, count + + if (.not. registry%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + if (registry%count == 0) then + print *, "[INFO] No components registered" + return + end if + + count = 0 + print *, "=== Registry Contents ===" + do i = 1, registry%count + if (.not. present(category) .or. & + trim(registry%components(i)%category) == trim(category)) then + call print_component_info(registry%components(i)) + count = count + 1 + end if + end do + + print *, "Total:", count, "components" + print *, "==========================" + end subroutine list_components + + ! ==================== 新增函数 ==================== + + logical function registry_is_initialized() + ! 检查注册表是否已初始化 + registry_is_initialized = registry%initialized + end function registry_is_initialized + + integer(ip) function registry_get_size() + ! 获取注册表中的组件数量 + registry_get_size = registry%count + end function registry_get_size + + ! ==================== 内部辅助函数 ==================== + + subroutine expand_registry() + type(component_info), allocatable :: temp(:) + + registry%capacity = registry%capacity * 2 + allocate(temp(registry%capacity)) + temp(1:registry%count) = registry%components(1:registry%count) + call move_alloc(temp, registry%components) + + if (registry%verbose) then + print *, "[INFO] Registry expanded to capacity:", registry%capacity + end if + end subroutine expand_registry + + subroutine print_component_info(info) + type(component_info), intent(in) :: info + + if (info%order > 0) then + print *, " [", trim(info%category), ".", trim(info%name), & + " (order:", info%order, ")]" + else + print *, " [", trim(info%category), ".", trim(info%name), "]" + end if + end subroutine print_component_info + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/core/registry_initializer.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/core/registry_initializer.f90 new file mode 100644 index 000000000..44023d1dd --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/core/registry_initializer.f90 @@ -0,0 +1,39 @@ +! src/core/registry_initializer.f90 (新增文件) +module registry_initializer_module + use registry_module, only: register_component_simple + implicit none + private + public :: initialize_default_registry + +contains + + subroutine initialize_default_registry() + ! 注册重构器 + call register_component_simple("reconstructor", "eno", order=3) + call register_component_simple("reconstructor", "weno3", order=3) + call register_component_simple("reconstructor", "weno5", order=5) + + ! 注册通量计算器 + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + + ! 注册边界条件 + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + + ! 注册时间积分器 + call register_component_simple("integrator", "rk1", order=1) + call register_component_simple("integrator", "rk2", order=2) + call register_component_simple("integrator", "rk3", order=3) + + ! 注册方程 + call register_component_simple("equation", "linear_advection") + + ! 注册问题 + call register_component_simple("problem", "linear_advection") + + print *, "[REGISTRY] Default components registered" + end subroutine initialize_default_registry + +end module registry_initializer_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03f/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..70cbbd2f3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/infrastructure/CMakeLists.txt @@ -0,0 +1,17 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "Configuring infrastructure module...") + +add_library(infrastructure STATIC + config.f90 + mesh.f90 + domain.f90 # 新增 + solution.f90 # 新增 +) + +target_link_libraries(infrastructure PRIVATE base) + +set_target_properties(infrastructure PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Infrastructure module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/infrastructure/config.f90 new file mode 100644 index 000000000..7586a1a50 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/infrastructure/config.f90 @@ -0,0 +1,144 @@ +! src/infrastructure/config.f90 (修复版) +module config_module + use base_modules, only: wp, ip, max_name_len, cfd_config_base + + implicit none + public :: wp, ip, cfd_config, config_print, config_with_reconstruction + + ! 扩展配置类型 - 添加物理相关字段 + type, extends(cfd_config_base) :: cfd_config + ! 物理参数 + real(wp) :: left_boundary_value = 1.0_wp + real(wp) :: right_boundary_value = 2.0_wp + real(wp) :: domain_length = 2.0_wp + + ! 新增:物理模块相关配置 + real(wp) :: pulse_center = 0.5_wp ! 高斯脉冲中心 + real(wp) :: pulse_width = 0.1_wp ! 高斯脉冲宽度 + logical :: enable_physics = .true. ! 是否启用物理模块 + contains + ! 新增:物理相关配置方法 + procedure :: set_physics_parameters + procedure :: get_physics_info + end type cfd_config + +contains + + subroutine config_print(cfg) + type(cfd_config), intent(in) :: cfg + + print *, "=== CFD Configuration ===" + print *, "Initial condition: ", trim(cfg%ic_type) + print *, "Reconstruction: ", trim(cfg%recon_scheme), " (order:", cfg%spatial_order, ")" + print *, "Flux type: ", trim(cfg%flux_type) + print *, "Time integration: RK", cfg%rk_order + print *, "Wave speed: ", cfg%wave_speed + print *, "Final time: ", cfg%final_time + print *, "Time step: ", cfg%dt + print *, "Boundary: ", trim(cfg%boundary_type) + + ! 新增:物理配置信息 + print *, "--- Physics Configuration ---" + print *, "Equation type: ", trim(cfg%equation_type) + print *, "Problem type: ", trim(cfg%problem_type) + print *, "Domain length: ", cfg%domain_length + print *, "Physics enabled: ", cfg%enable_physics + + if (cfg%ic_type == "gaussian") then + print *, "Pulse center: ", cfg%pulse_center + print *, "Pulse width: ", cfg%pulse_width + end if + + print *, "===============================" + end subroutine config_print + + subroutine config_with_reconstruction(cfg, scheme, order) + type(cfd_config), intent(inout) :: cfg + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + integer :: i + + ! 转换为小写 + cfg%recon_scheme = scheme + do i = 1, len_trim(cfg%recon_scheme) + if (cfg%recon_scheme(i:i) >= 'A' .and. cfg%recon_scheme(i:i) <= 'Z') then + cfg%recon_scheme(i:i) = char(ichar(cfg%recon_scheme(i:i)) + 32) + end if + end do + + ! 设置阶数 + if (present(order)) then + cfg%spatial_order = order + else + if (index(cfg%recon_scheme, 'weno') > 0) then + cfg%spatial_order = 5 + else if (trim(cfg%recon_scheme) == 'eno') then + cfg%spatial_order = 3 + end if + end if + + if (cfg%verbose) then + print *, "[CONFIG] Reconstruction: ", trim(cfg%recon_scheme), & + " Order: ", cfg%spatial_order + end if + end subroutine config_with_reconstruction + + ! ========== 新增:物理参数设置方法 ========== + + subroutine set_physics_parameters(this, equation_type, problem_type, & + domain_length, enable_physics) + class(cfd_config), intent(inout) :: this + character(len=*), intent(in), optional :: equation_type, problem_type + real(wp), intent(in), optional :: domain_length + logical, intent(in), optional :: enable_physics + + if (present(equation_type)) then + this%equation_type = trim(equation_type) + if (this%verbose) then + print *, "[CONFIG] Set equation type: ", trim(this%equation_type) + end if + end if + + if (present(problem_type)) then + this%problem_type = trim(problem_type) + if (this%verbose) then + print *, "[CONFIG] Set problem type: ", trim(this%problem_type) + end if + end if + + if (present(domain_length)) then + this%domain_length = domain_length + if (this%verbose) then + print *, "[CONFIG] Set domain length: ", this%domain_length + end if + end if + + if (present(enable_physics)) then + this%enable_physics = enable_physics + if (this%verbose) then + print *, "[CONFIG] Physics module enabled: ", this%enable_physics + end if + end if + end subroutine set_physics_parameters + + subroutine get_physics_info(this) + class(cfd_config), intent(in) :: this + + print *, "=== Physics Configuration Info ===" + print *, "Equation type: ", trim(this%equation_type) + print *, "Problem type: ", trim(this%problem_type) + print *, "Domain length: ", this%domain_length + print *, "Wave speed: ", this%wave_speed + print *, "Physics enabled: ", this%enable_physics + + if (this%ic_type == "gaussian") then + print *, "Pulse parameters:" + print *, " Center: ", this%pulse_center + print *, " Width: ", this%pulse_width + end if + + print *, "==================================" + end subroutine get_physics_info + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/infrastructure/domain.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/infrastructure/domain.f90 new file mode 100644 index 000000000..c3662f039 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/infrastructure/domain.f90 @@ -0,0 +1,102 @@ +! src/infrastructure/domain.f90 +module domain_module + use base_modules, only: wp, ip, max_name_len + use config_module, only: cfd_config + use mesh_module, only: mesh_type + + implicit none + private + public :: wp, ip, domain_type, domain_create, is_physical_cell + + type :: domain_type + type(cfd_config), pointer :: config => null() + type(mesh_type), pointer :: mesh => null() + integer(ip) :: nghosts = 0 + integer(ip) :: ist = 1 ! 物理区域起始索引(1-based) + integer(ip) :: ied = 1 ! 物理区域结束索引(exclusive) + integer(ip) :: ntcells = 0 ! 总单元数(含ghost) + contains + procedure :: print_info => domain_print_info + procedure :: get_physical_indices => domain_get_physical_indices + end type domain_type + +contains + + function domain_create(config, mesh) result(domain) + type(cfd_config), target, intent(in) :: config + type(mesh_type), target, intent(in) :: mesh + type(domain_type) :: domain + + domain%config => config + domain%mesh => mesh + + ! 计算ghost层数(参考Julia的_calc_nghosts) + domain%nghosts = calc_nghosts(config) + domain%ist = domain%nghosts + 1 + domain%ied = domain%ist + mesh%ncells + domain%ntcells = mesh%ncells + 2 * domain%nghosts + + if (config%verbose) then + print *, "[DOMAIN] Created:" + print *, " Ghost layers: ", domain%nghosts + print *, " Physical cells: ", domain%ist, " to ", domain%ied - 1 + print *, " Total cells: ", domain%ntcells + end if + end function domain_create + + function calc_nghosts(config) result(nghosts) + type(cfd_config), intent(in) :: config + integer(ip) :: nghosts + + character(len=max_name_len) :: scheme + + scheme = config%recon_scheme + + if (scheme == "eno") then + nghosts = config%spatial_order + else if (index(scheme, "weno") > 0) then + nghosts = config%spatial_order / 2 + 1 + else + print *, "[WARNING] Unknown scheme, using default nghosts=2" + nghosts = 2 + end if + + if (nghosts <= 0) then + print *, "[ERROR] Invalid nghosts: ", nghosts + nghosts = 2 + end if + end function calc_nghosts + + logical function is_physical_cell(this, idx) + class(domain_type), intent(in) :: this + integer(ip), intent(in) :: idx + is_physical_cell = (idx >= this%ist .and. idx < this%ied) + end function is_physical_cell + + function domain_get_physical_indices(this) result(indices) + class(domain_type), intent(in) :: this + integer(ip), allocatable :: indices(:) + integer(ip) :: i, count + + count = this%ied - this%ist + allocate(indices(count)) + + do i = 1, count + indices(i) = this%ist + i - 1 + end do + end function domain_get_physical_indices + + subroutine domain_print_info(this) + class(domain_type), intent(in) :: this + + print *, "=== Domain Information ===" + print *, "Configuration: ", trim(this%config%recon_scheme), & + " order ", this%config%spatial_order + print *, "Ghost layers: ", this%nghosts + print *, "Physical cells: ", this%ist, " to ", this%ied - 1 + print *, "Total cells: ", this%ntcells + print *, "Mesh cells: ", this%mesh%ncells + print *, "==========================" + end subroutine domain_print_info + +end module domain_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..f810f3a1b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/infrastructure/mesh.f90 @@ -0,0 +1,73 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use base_modules, only: wp, ip + + implicit none + public :: wp, ip, mesh_type, mesh_init, mesh_print_info + + ! 网格类型 + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer(ip) :: ncells = 40 + integer(ip) :: nnodes + integer(ip) :: nx + real(wp), allocatable :: x(:) ! 节点坐标 + real(wp), allocatable :: xcc(:) ! 单元中心坐标 + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer(ip), optional, intent(in) :: ncells + + integer(ip) :: i + + ! 设置参数 + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! 计算 + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! 分配内存 + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! 生成节点坐标 + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! 生成单元中心坐标 + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== Mesh Information ===" + print *, "Domain: [", this%xmin, ", ", this%xmax, "]" + print *, "Cells: ", this%ncells + print *, "Nodes: ", this%nnodes + print *, "dx: ", this%dx + print *, "L: ", this%L + print *, "========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/infrastructure/solution.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/infrastructure/solution.f90 new file mode 100644 index 000000000..ce88fd8a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/infrastructure/solution.f90 @@ -0,0 +1,131 @@ +! src/infrastructure/solution.f90 +module solution_module + use base_modules, only: wp, ip + use domain_module, only: domain_type + + implicit none + private + public :: wp, ip, solution_type, solution_create, solution_reset + + type :: solution_type + type(domain_type), pointer :: domain => null() + real(wp), allocatable :: u(:) ! 当前解(含ghost) + real(wp), allocatable :: un(:) ! 旧解 + real(wp), allocatable :: q_face_left(:) ! 左界面值 + real(wp), allocatable :: q_face_right(:)! 右界面值 + real(wp), allocatable :: flux(:) ! 通量 + real(wp), allocatable :: res(:) ! 残差 + contains + procedure :: initialize => solution_initialize + procedure :: update_old_field => solution_update_old_field + procedure :: print_info => solution_print_info + procedure :: reset => solution_reset_instance + end type solution_type + +contains + + function solution_create(domain) result(solution) + type(domain_type), target, intent(in) :: domain + type(solution_type) :: solution + + integer(ip) :: ncells, nnodes, ntcells + + solution%domain => domain + + ncells = domain%mesh%ncells + nnodes = domain%mesh%nnodes + ntcells = domain%ntcells + + ! 分配数组(与Julia solution.jl一致) + allocate(solution%u(ntcells), source=0.0_wp) + allocate(solution%un(ntcells), source=0.0_wp) + allocate(solution%q_face_left(nnodes), source=0.0_wp) + allocate(solution%q_face_right(nnodes), source=0.0_wp) + allocate(solution%flux(nnodes), source=0.0_wp) + allocate(solution%res(ncells), source=0.0_wp) + + if (domain%config%verbose) then + print *, "[SOLUTION] Created:" + print *, " u size: ", size(solution%u), " (with ghosts)" + print *, " flux size: ", size(solution%flux) + print *, " res size: ", size(solution%res) + end if + end function solution_create + + subroutine solution_initialize(this, initial_values) + class(solution_type), intent(inout) :: this + real(wp), intent(in), optional :: initial_values(:) + + integer(ip) :: i, idx + type(domain_type), pointer :: domain + + domain => this%domain + + if (present(initial_values)) then + ! 应用初始值到物理区域 + do i = domain%ist, domain%ied - 1 + idx = i - domain%ist + 1 + if (idx <= size(initial_values)) then + this%u(i) = initial_values(idx) + end if + end do + else + ! 默认为0 + this%u = 0.0_wp + end if + + ! 同步旧场(与Julia的update_old_field一致) + call this%update_old_field() + + if (domain%config%verbose) then + print *, "[SOLUTION] Initialized" + print *, " u range: ", minval(this%u), " to ", maxval(this%u) + end if + end subroutine solution_initialize + + subroutine solution_update_old_field(this) + class(solution_type), intent(inout) :: this + this%un = this%u ! 与Julia的 un .= u 一致 + end subroutine solution_update_old_field + + subroutine solution_reset_instance(this) + class(solution_type), intent(inout) :: this + call solution_reset(this) + end subroutine solution_reset_instance + + subroutine solution_reset(solution) + type(solution_type), intent(inout) :: solution + + if (allocated(solution%u)) solution%u = 0.0_wp + if (allocated(solution%un)) solution%un = 0.0_wp + if (allocated(solution%q_face_left)) solution%q_face_left = 0.0_wp + if (allocated(solution%q_face_right)) solution%q_face_right = 0.0_wp + if (allocated(solution%flux)) solution%flux = 0.0_wp + if (allocated(solution%res)) solution%res = 0.0_wp + + if (associated(solution%domain) .and. solution%domain%config%verbose) then + print *, "[SOLUTION] Reset" + end if + end subroutine solution_reset + + subroutine solution_print_info(this) + class(solution_type), intent(in) :: this + + print *, "=== Solution Information ===" + print *, "Arrays:" + print *, " u: ", size(this%u), " elements" + print *, " un: ", size(this%un), " elements" + print *, " q_face_left: ", size(this%q_face_left), " elements" + print *, " q_face_right: ", size(this%q_face_right), " elements" + print *, " flux: ", size(this%flux), " elements" + print *, " res: ", size(this%res), " elements" + + if (allocated(this%u)) then + print *, "Values:" + print *, " u min/max: ", minval(this%u), maxval(this%u) + print *, " un min/max: ", minval(this%un), maxval(this%un) + end if + print *, "============================" + end subroutine solution_print_info + +end module solution_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/initial_condition/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03f/src/initial_condition/CMakeLists.txt new file mode 100644 index 000000000..01fbfa47b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/initial_condition/CMakeLists.txt @@ -0,0 +1,23 @@ +# src/initial_condition/CMakeLists.txt +message(STATUS "配置初始条件模块...") + +add_library(initial_condition STATIC + ic_base.f90 # 基类 + step.f90 # 阶跃函数 + sine.f90 # 正弦波 + gaussian.f90 # 高斯脉冲 + factory.f90 # 工厂 +) + +target_link_libraries(initial_condition + PRIVATE + base + infrastructure + core # 需要注册系统 +) + +set_target_properties(initial_condition PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "初始条件模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/initial_condition/factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/initial_condition/factory.f90 new file mode 100644 index 000000000..7fbdd5220 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/initial_condition/factory.f90 @@ -0,0 +1,31 @@ +! src/initial_condition/factory.f90 +module ic_factory_module + use base_modules, only: wp, ip + use ic_base_module, only: initial_condition + use step_ic_module, only: step_function_ic + implicit none + private + + public :: create_initial_condition, initialize_ic_factory + +contains + + subroutine initialize_ic_factory() + print *, "[IC FACTORY] Initial conditions placeholder" + end subroutine + + subroutine create_initial_condition(ic_type, ic_instance) + character(len=*), intent(in) :: ic_type + class(initial_condition), allocatable, intent(out) :: ic_instance + + ! 暂时只实现步函数 + allocate(step_function_ic :: ic_instance) + + select type(ic => ic_instance) + type is (step_function_ic) + ! 使用默认构造函数 + ic%name = "step" + end select + end subroutine + +end module ic_factory_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/initial_condition/gaussian.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/initial_condition/gaussian.f90 new file mode 100644 index 000000000..ed2b625b7 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/initial_condition/gaussian.f90 @@ -0,0 +1,62 @@ +! src/initial_condition/gaussian.f90 +module gaussian_ic_module + use base_modules, only: wp, ip + use ic_base_module, only: initial_condition + use solution_module, only: solution_type + use domain_module, only: domain_type + implicit none + private + + type, extends(initial_condition), public :: gaussian_pulse_ic + contains + procedure :: evaluate_at => gaussian_evaluate_at + procedure :: apply => gaussian_apply + end type + + interface gaussian_pulse_ic + module procedure create_gaussian_pulse_ic + end interface + +contains + + type(gaussian_pulse_ic) function create_gaussian_pulse_ic() result(this) + this%name = "gaussian" + end function + + function gaussian_evaluate_at(this, x) result(u) + class(gaussian_pulse_ic), intent(in) :: this + real(wp), intent(in) :: x(:) + real(wp) :: u(size(x)) + + integer :: i + real(wp) :: center, width + + center = 0.5_wp ! 脉冲中心 + width = 0.1_wp ! 脉冲宽度 + + do i = 1, size(x) + u(i) = exp(-((x(i) - center) / width)**2) + end do + end function + + subroutine gaussian_apply(this, solution) + class(gaussian_pulse_ic), intent(in) :: this + type(solution_type), intent(inout) :: solution + + integer :: i, idx + real(wp), allocatable :: u0(:) + type(domain_type), pointer :: domain + + domain => solution%domain + + ! 评估初始条件 + u0 = this%evaluate_at(domain%mesh%xcc) + + ! 应用到物理区域 + do i = domain%ist, domain%ied - 1 + idx = i - domain%ist + 1 + solution%u(i) = u0(idx) + end do + end subroutine + +end module gaussian_ic_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/initial_condition/ic_base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/initial_condition/ic_base.f90 new file mode 100644 index 000000000..4a7845fe0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/initial_condition/ic_base.f90 @@ -0,0 +1,43 @@ +! src/initial_condition/ic_base.f90 +module ic_base_module + use base_modules, only: wp, ip + use solution_module, only: solution_type + implicit none + private + + type, abstract, public :: initial_condition + character(len=:), allocatable :: name + contains + procedure :: get_name => ic_get_name + procedure(evaluate_interface), deferred :: evaluate_at + procedure(apply_interface), deferred :: apply + end type + + abstract interface + function evaluate_interface(this, x) result(u) + import :: initial_condition, wp + class(initial_condition), intent(in) :: this + real(wp), intent(in) :: x(:) + real(wp) :: u(size(x)) + end function evaluate_interface + + subroutine apply_interface(this, solution) + import :: initial_condition, solution_type + class(initial_condition), intent(in) :: this + type(solution_type), intent(inout) :: solution + end subroutine apply_interface + end interface + +contains + + function ic_get_name(this) result(name) + class(initial_condition), intent(in) :: this + character(len=:), allocatable :: name + if (allocated(this%name)) then + name = this%name + else + name = "unnamed" + end if + end function + +end module ic_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/initial_condition/sine.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/initial_condition/sine.f90 new file mode 100644 index 000000000..f9f1028c3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/initial_condition/sine.f90 @@ -0,0 +1,62 @@ +! src/initial_condition/sine.f90 +module sine_ic_module + use base_modules, only: wp, ip + use ic_base_module, only: initial_condition + use solution_module, only: solution_type + use domain_module, only: domain_type + implicit none + private + + type, extends(initial_condition), public :: sine_wave_ic + contains + procedure :: evaluate_at => sine_evaluate_at + procedure :: apply => sine_apply + end type + + interface sine_wave_ic + module procedure create_sine_wave_ic + end interface + +contains + + type(sine_wave_ic) function create_sine_wave_ic() result(this) + this%name = "sin" + end function + + function sine_evaluate_at(this, x) result(u) + class(sine_wave_ic), intent(in) :: this + real(wp), intent(in) :: x(:) + real(wp) :: u(size(x)) + + integer :: i + real(wp) :: L + + ! 假设域长度,可以根据需要调整 + L = 2.0_wp ! 默认域长度 + + do i = 1, size(x) + u(i) = sin(2.0_wp * 3.141592653589793_wp * x(i) / L) + end do + end function + + subroutine sine_apply(this, solution) + class(sine_wave_ic), intent(in) :: this + type(solution_type), intent(inout) :: solution + + integer :: i, idx + real(wp), allocatable :: u0(:) + type(domain_type), pointer :: domain + + domain => solution%domain + + ! 评估初始条件 + u0 = this%evaluate_at(domain%mesh%xcc) + + ! 应用到物理区域 + do i = domain%ist, domain%ied - 1 + idx = i - domain%ist + 1 + solution%u(i) = u0(idx) + end do + end subroutine + +end module sine_ic_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/initial_condition/step.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/initial_condition/step.f90 new file mode 100644 index 000000000..dca60265b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/initial_condition/step.f90 @@ -0,0 +1,62 @@ +! src/initial_condition/step.f90 +module step_ic_module + use base_modules, only: wp, ip + use ic_base_module, only: initial_condition + use solution_module, only: solution_type + use domain_module, only: domain_type + implicit none + private + + type, extends(initial_condition), public :: step_function_ic + contains + procedure :: evaluate_at => step_evaluate_at + procedure :: apply => step_apply + end type + + interface step_function_ic + module procedure create_step_function_ic + end interface + +contains + + type(step_function_ic) function create_step_function_ic() result(this) + this%name = "step" + end function + + function step_evaluate_at(this, x) result(u) + class(step_function_ic), intent(in) :: this + real(wp), intent(in) :: x(:) + real(wp) :: u(size(x)) + + integer :: i + + do i = 1, size(x) + if (x(i) >= 0.5_wp .and. x(i) <= 1.0_wp) then + u(i) = 2.0_wp + else + u(i) = 1.0_wp + end if + end do + end function + + subroutine step_apply(this, solution) + class(step_function_ic), intent(in) :: this + type(solution_type), intent(inout) :: solution + + integer :: i, idx + real(wp), allocatable :: u0(:) + type(domain_type), pointer :: domain + + domain => solution%domain + + ! 评估初始条件 + u0 = this%evaluate_at(domain%mesh%xcc) + + ! 应用到物理区域 + do i = domain%ist, domain%ied - 1 + idx = i - domain%ist + 1 + solution%u(i) = u0(idx) + end do + end subroutine + +end module step_ic_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/manager/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03f/src/manager/CMakeLists.txt new file mode 100644 index 000000000..00c8bf49b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/manager/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/manager/CMakeLists.txt +message(STATUS "配置管理器模块...") + +# 创建管理器库 +add_library(manager STATIC + component_manager.f90 + component_factory.f90 +) + +# 明确依赖关系:管理器依赖所有其他模块 +target_link_libraries(manager + PRIVATE + core + infrastructure + reconstructor + flux +) + +# 设置模块输出目录 +set_target_properties(manager PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "管理器模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/manager/component_factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/manager/component_factory.f90 new file mode 100644 index 000000000..bafceddbe --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/manager/component_factory.f90 @@ -0,0 +1,127 @@ +! src/manager/component_factory.f90 +module component_factory_module + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use weno5_reconstructor_module, only: weno5_reconstructor + use rusanov_flux_module, only: rusanov_flux + + implicit none + private + public :: wp, create_reconstructor, create_flux_calculator + + ! 定义wp以保持兼容性 + integer, parameter :: wp = real64 + + ! 错误代码 + integer, parameter :: CM_SUCCESS = 0 + integer, parameter :: CM_ERROR_UNKNOWN_SCHEME = 1 + integer, parameter :: CM_ERROR_UNKNOWN_FLUX = 2 + integer, parameter :: CM_ERROR_INVALID_ORDER = 3 + +contains + + ! ==================== 重构器创建 ==================== + + function create_reconstructor(config, status) result(recon) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(reconstructor_base), allocatable :: recon + + character(len=20) :: scheme + integer :: order, error_code + + scheme = trim(adjustl(config%recon_scheme)) + order = config%spatial_order + + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating reconstructor: ", scheme, " order=", order + end if + + ! 处理"weno"作为WENO5的别名 + if (scheme == "weno" .and. order == 5) then + scheme = "weno5" + end if + + select case(scheme) + case('eno') + allocate(eno_reconstructor :: recon) + select type(recon) + type is(eno_reconstructor) + recon%order = order + end select + + case('weno3') + allocate(weno3_reconstructor :: recon) + select type(recon) + type is(weno3_reconstructor) + recon%order = order + end select + + case('weno5') + allocate(weno5_reconstructor :: recon) + select type(recon) + type is(weno5_reconstructor) + recon%order = order + end select + + case default + error_code = CM_ERROR_UNKNOWN_SCHEME + if (config%verbose) then + print *, "[ERROR] Unknown reconstructor scheme: ", scheme + print *, " Available: eno, weno3, weno5" + end if + end select + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Reconstructor creation failed" + end if + end function create_reconstructor + + ! ==================== 通量计算器创建 ==================== + + function create_flux_calculator(config, status) result(flux) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(flux_calculator_base), allocatable :: flux + + character(len=20) :: flux_type + integer :: error_code + + flux_type = trim(adjustl(config%flux_type)) + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating flux calculator: ", flux_type + end if + + select case(flux_type) + case('rusanov') + allocate(rusanov_flux :: flux) + + case default + error_code = CM_ERROR_UNKNOWN_FLUX + if (config%verbose) then + print *, "[ERROR] Unknown flux type: ", flux_type + print *, " Available: rusanov" + end if + end select + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Flux calculator creation failed" + end if + end function create_flux_calculator + +end module component_factory_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/manager/component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/manager/component_manager.f90 new file mode 100644 index 000000000..70d00991f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/manager/component_manager.f90 @@ -0,0 +1,48 @@ +! src/manager/component_manager.f90 +module component_manager_module + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config + + implicit none + private + public :: wp, component_manager_info, validate_config + + ! 定义wp以保持兼容性 + integer, parameter :: wp = real64 + +contains + + ! ==================== 配置验证 ==================== + + function validate_config(config) result(is_valid) + type(cfd_config), intent(in) :: config + logical :: is_valid + + ! 简单验证 + is_valid = .true. + + if (config%verbose) then + print *, "[CONFIG VALIDATION] Configuration is valid (simplified)" + end if + end function validate_config + + ! ==================== 信息显示 ==================== + + subroutine component_manager_info() + print *, "=== Component Manager ===" + print *, "Available reconstructors:" + print *, " - eno (orders: 1-7)" + print *, " - weno3 (order: 3)" + print *, " - weno5 (order: 5)" + print *, "" + print *, "Available flux calculators:" + print *, " - rusanov" + print *, "" + print *, "Features:" + print *, " - Configuration validation" + print *, " - Component creation from config" + print *, " - Error handling with status codes" + print *, "=========================" + end subroutine component_manager_info + +end module component_manager_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..66874a7ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/CMakeLists.txt @@ -0,0 +1,7 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..3fde7746b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,28 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +# Start with just the base module +add_library(flux STATIC + base.f90 + rusanov.f90 +) + +#target_link_libraries(flux PRIVATE core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 明确设置不使用 /Qm64 选项 +if(CMAKE_Fortran_COMPILER_ID MATCHES "IntelLLVM|Intel") + set_target_properties(flux PROPERTIES + Fortran_FLAGS "${CMAKE_Fortran_FLAGS} /Qm64" # 明确设置,避免继承问题 + ) +endif() + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/flux/base.f90 new file mode 100644 index 000000000..7080a7ae4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/flux/base.f90 @@ -0,0 +1,30 @@ +!src/numerics/flux/base.f90 +module flux_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, flux_calculator_base + + ! Base flux calculator type + type :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure :: info => flux_info + procedure :: print_basic_info => flux_print_basic ! 添加辅助方法 + end type flux_calculator_base + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "Flux calculator information:" + print *, " Name: ", trim(this%name) + end subroutine flux_info + + subroutine flux_print_basic(this) + class(flux_calculator_base), intent(in) :: this + print *, " Name: ", trim(this%name) + end subroutine flux_print_basic + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/flux/engquist_osher.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/flux/engquist_osher.f90 new file mode 100644 index 000000000..90f499d12 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/flux/engquist_osher.f90 @@ -0,0 +1,41 @@ +! src/numerics/flux/engquist_osher.f90 +module engquist_osher_flux + use base_modules, only: wp, ip + use flux_base_module, only: flux_calculator_base + implicit none + private + + type, extends(flux_calculator_base), public :: engquist_osher_flux_t + real(wp) :: wave_speed = 1.0_wp + contains + procedure :: compute => eo_compute + end type + +contains + + subroutine eo_compute(this, qL, qR, flux, equation) + class(engquist_osher_flux_t), intent(inout) :: this + real(wp), intent(in) :: qL(:), qR(:) + real(wp), intent(out) :: flux(:) + class(*), intent(in) :: equation + + integer :: i, n + real(wp) :: cp, cm, uL, uR + + select type(eq => equation) + type is (linear_advection_eq) + cp = 0.5_wp * (eq%wave_speed + abs(eq%wave_speed)) + cm = 0.5_wp * (eq%wave_speed - abs(eq%wave_speed)) + + n = size(qL) + do i = 1, n + uL = qL(i) + uR = qR(i) + flux(i) = cp * uL + cm * uR + end do + class default + error stop "Unsupported equation type for Engquist-Osher flux" + end select + end subroutine + +end module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/flux/rusanov.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/flux/rusanov.f90 new file mode 100644 index 000000000..daa9e3bb5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/flux/rusanov.f90 @@ -0,0 +1,41 @@ +! src/numerics/flux/rusanov.f90 +module rusanov_flux_module + use, intrinsic :: iso_fortran_env, only: real64 + use flux_base_module, only: flux_calculator_base + implicit none + + private + public :: real64, rusanov_flux, create_rusanov_flux + + type, extends(flux_calculator_base) :: rusanov_flux + real(real64) :: wave_speed_default = 1.0_real64 + contains + procedure :: info => rusanov_info + end type rusanov_flux + + ! 构造函数接口 + interface rusanov_flux + module procedure create_rusanov_flux + end interface + +contains + + ! 构造函数 + type(rusanov_flux) function create_rusanov_flux() result(this) + this%name = "Rusanov" + this%wave_speed_default = 1.0_real64 + end function create_rusanov_flux + + subroutine rusanov_info(this) + class(rusanov_flux), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Flux calculator information:" + call this%print_basic_info() + + ! 添加Rusanov特有信息 + print *, " Type: Rusanov flux" + print *, " Default wave speed: ", this%wave_speed_default + end subroutine rusanov_info + +end module rusanov_flux_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..c88ea647b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,23 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "Configuring reconstructor module...") + +# Start with just the base module +add_library(reconstructor STATIC + base.f90 + eno.f90 + weno3.f90 + weno5.f90 +) + +target_link_libraries(reconstructor PRIVATE core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Reconstructor module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..53798d021 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/reconstructor/base.f90 @@ -0,0 +1,33 @@ +!src/numerics/reconstructor/base.f90 +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, reconstructor_base + + ! Base reconstructor type + type :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + contains + procedure :: info => reconstructor_info + procedure :: print_basic_info => reconstructor_print_basic ! 添加一个辅助方法 + end type reconstructor_base + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + print *, "Reconstructor information:" + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_info + + subroutine reconstructor_print_basic(this) + class(reconstructor_base), intent(in) :: this + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_print_basic + +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/reconstructor/eno.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/reconstructor/eno.f90 new file mode 100644 index 000000000..f973e8b32 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/reconstructor/eno.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/eno.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, eno_reconstructor, create_eno_reconstructor ! ← 添加这个 + + type, extends(reconstructor_base) :: eno_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => eno_info + end type eno_reconstructor + + ! 构造函数接口 + interface eno_reconstructor + module procedure create_eno_reconstructor + end interface + +contains + + ! 构造函数 + type(eno_reconstructor) function create_eno_reconstructor() result(this) + this%name = "ENO" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_eno_reconstructor + + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加ENO特有信息 + print *, " Type: ENO reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/reconstructor/weno3.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/reconstructor/weno3.f90 new file mode 100644 index 000000000..a8faa577f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/reconstructor/weno3.f90 @@ -0,0 +1,40 @@ +! src/numerics/reconstructor/weno3.f90 +module weno3_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + private + + type, extends(reconstructor_base), public :: weno3_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno3_info + end type weno3_reconstructor + + ! 构造函数接口 - 使用类型名本身作为构造函数 + interface weno3_reconstructor + module procedure create_weno3_reconstructor + end interface + +contains + + ! 构造函数 + type(weno3_reconstructor) function create_weno3_reconstructor() result(this) + this%name = "WENO3" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_weno3_reconstructor + + subroutine weno3_info(this) + class(weno3_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加WENO3特有信息 + print *, " Type: WENO-3 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno3_info + +end module weno3_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/reconstructor/weno5.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/reconstructor/weno5.f90 new file mode 100644 index 000000000..a869c67d0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/numerics/reconstructor/weno5.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/weno5.f90(新增) +module weno5_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno5_reconstructor, create_weno5_reconstructor + + type, extends(reconstructor_base) :: weno5_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno5_info + end type weno5_reconstructor + + ! 构造函数接口 + interface weno5_reconstructor + module procedure create_weno5_reconstructor + end interface + +contains + + ! 构造函数 + type(weno5_reconstructor) function create_weno5_reconstructor() result(this) + this%name = "WENO5" + this%order = 5 + this%epsilon = 1.0e-6_real64 + end function create_weno5_reconstructor + + subroutine weno5_info(this) + class(weno5_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加WENO5特有信息 + print *, " Type: WENO-5 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno5_info + +end module weno5_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/physics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03f/src/physics/CMakeLists.txt new file mode 100644 index 000000000..cc4e233ab --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/physics/CMakeLists.txt @@ -0,0 +1,19 @@ +# src/physics/CMakeLists.txt +message(STATUS "配置物理模块...") + +# 创建物理模块库 +add_library(physics STATIC + physics_interface.f90 + equations/linear_convection.f90 + problems/linear_convection_problem.f90 +) + +# 链接依赖 +target_link_libraries(physics PRIVATE base) + +# 设置模块输出目录 +set_target_properties(physics PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "物理模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/physics/equations/linear_convection.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/physics/equations/linear_convection.f90 new file mode 100644 index 000000000..3be3b8f7c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/physics/equations/linear_convection.f90 @@ -0,0 +1,43 @@ +! src/physics/equations/linear_advection.f90 +module linear_advection_equation + use base_modules, only: wp, ip + implicit none + private + + type, public :: linear_advection_eq + real(wp) :: wave_speed = 1.0_wp + contains + procedure :: flux => eq_flux + procedure :: max_wave_speed => eq_max_wave_speed + procedure :: num_eqs => eq_num_eqs + procedure :: print_info => eq_print_info + end type + +contains + + pure function eq_flux(this, u) result(f) + class(linear_advection_eq), intent(in) :: this + real(wp), intent(in) :: u + real(wp) :: f + f = this%wave_speed * u + end function + + pure function eq_max_wave_speed(this, u) result(smax) + class(linear_advection_eq), intent(in) :: this + real(wp), intent(in) :: u + real(wp) :: smax + smax = abs(this%wave_speed) + end function + + integer function eq_num_eqs(this) + class(linear_advection_eq), intent(in) :: this + eq_num_eqs = 1 + end function + + subroutine eq_print_info(this) + class(linear_advection_eq), intent(in) :: this + print *, "Linear Advection Equation:" + print *, " Wave speed: ", this%wave_speed + end subroutine + +end module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/physics/physics_interface.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/physics/physics_interface.f90 new file mode 100644 index 000000000..45002da6c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/physics/physics_interface.f90 @@ -0,0 +1,64 @@ +! src/physics/physics_interface.f90 +module physics_interface + use precision_module, only: wp, ip + implicit none + private + + ! 定义抽象基类型 - 先声明为私有,然后在公开部分导出 + type, abstract :: physics_equation + character(len=:), allocatable :: name + contains + procedure(eq_flux_abs), deferred :: flux + procedure(eq_speed_abs), deferred :: speed + end type physics_equation + + type, abstract :: physics_problem + character(len=:), allocatable :: name + contains + procedure(prob_ic_abs), deferred :: initial_condition + procedure(prob_bc_abs), deferred :: boundary_condition + procedure(prob_exact_abs), deferred :: exact_solution + end type physics_problem + + ! 抽象接口定义 + abstract interface + pure function eq_flux_abs(this, u) result(f) + import :: physics_equation, wp + class(physics_equation), intent(in) :: this + real(wp), intent(in) :: u + real(wp) :: f + end function eq_flux_abs + + pure function eq_speed_abs(this) result(a) + import :: physics_equation, wp + class(physics_equation), intent(in) :: this + real(wp) :: a + end function eq_speed_abs + + subroutine prob_ic_abs(this, x, u) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(in) :: x(:) + real(wp), intent(out) :: u(:) + end subroutine prob_ic_abs + + subroutine prob_bc_abs(this, u, t) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(inout) :: u(:) + real(wp), intent(in), optional :: t + end subroutine prob_bc_abs + + function prob_exact_abs(this, x, t) result(u) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(in) :: x(:), t + real(wp), dimension(size(x)) :: u + end function prob_exact_abs + end interface + + ! 公开接口 - 使用独立的public语句 + public :: wp, ip + public :: physics_equation, physics_problem + +end module physics_interface \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/physics/problems/linear_advection_problem.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/physics/problems/linear_advection_problem.f90 new file mode 100644 index 000000000..419d57cfd --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/physics/problems/linear_advection_problem.f90 @@ -0,0 +1,70 @@ +! src/physics/problems/linear_advection_problem.f90 +module linear_advection_problem + use base_modules, only: wp, ip + use initial_condition_module, only: create_initial_condition + use boundary_condition_module, only: create_boundary_condition + implicit none + private + + type, public :: linear_advection_problem + character(len=20) :: ic_type = "step" + character(len=20) :: boundary_type = "periodic" + real(wp) :: left_value = 1.0_wp + real(wp) :: right_value = 2.0_wp + real(wp) :: domain_length = 2.0_wp + real(wp) :: wave_speed = 1.0_wp + contains + procedure :: create_ic => prob_create_ic + procedure :: create_bc => prob_create_bc + procedure :: exact_solution => prob_exact_solution + end type + +contains + + function prob_create_ic(this, config) result(ic) + class(linear_advection_problem), intent(in) :: this + class(*), intent(in) :: config + class(*), allocatable :: ic + + ! 通过初始条件工厂创建 + call create_initial_condition(this%ic_type, config, ic) + end function + + function prob_create_bc(this, cfd) result(bc) + class(linear_advection_problem), intent(in) :: this + class(*), intent(in) :: cfd + class(*), allocatable :: bc + + ! 通过边界条件工厂创建 + call create_boundary_condition(this%boundary_type, cfd, bc) + end function + + function prob_exact_solution(this, x, t) result(u) + class(linear_advection_problem), intent(in) :: this + real(wp), intent(in) :: x(:), t + real(wp), allocatable :: u(:) + + integer :: i, n + real(wp) :: x_shifted, L + + n = size(x) + L = this%domain_length + allocate(u(n)) + + ! 周期性平移 + do i = 1, n + x_shifted = x(i) - this%wave_speed * t + x_shifted = modulo(x_shifted, L) + if (x_shifted < 0.0_wp) x_shifted = x_shifted + L + + ! 初始条件逻辑(简化) + if (this%ic_type == "step" .and. & + x_shifted >= 0.5_wp .and. x_shifted <= 1.0_wp) then + u(i) = 2.0_wp + else + u(i) = 1.0_wp + end if + end do + end function + +end module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/physics/problems/linear_convection_problem.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/physics/problems/linear_convection_problem.f90 new file mode 100644 index 000000000..06226ed13 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/physics/problems/linear_convection_problem.f90 @@ -0,0 +1,118 @@ +! src/physics/problems/linear_convection_problem.f90 +module linear_convection_problem + use precision_module, only: wp, ip + use physics_interface, only: physics_problem + implicit none + private + + ! 具体问题类型 - 先声明 + type, extends(physics_problem) :: linear_convection_prob + real(wp) :: wave_speed = 1.0_wp + real(wp) :: domain_length = 2.0_wp + character(len=20) :: ic_type = "step" + character(len=20) :: boundary_type = "periodic" + contains + procedure :: initial_condition => lc_initial_condition + procedure :: boundary_condition => lc_boundary_condition + procedure :: exact_solution => lc_exact_solution + end type linear_convection_prob + + ! 公开接口 + public :: wp, ip + public :: linear_convection_prob, create_linear_convection_prob + +contains + + ! 构造函数 + function create_linear_convection_prob(wave_speed, domain_length, & + ic_type, boundary_type) result(prob) + real(wp), intent(in), optional :: wave_speed, domain_length + character(len=*), intent(in), optional :: ic_type, boundary_type + type(linear_convection_prob) :: prob + + prob%name = "Linear Convection Problem" + + if (present(wave_speed)) prob%wave_speed = wave_speed + if (present(domain_length)) prob%domain_length = domain_length + if (present(ic_type)) prob%ic_type = ic_type + if (present(boundary_type)) prob%boundary_type = boundary_type + end function create_linear_convection_prob + + ! 初始条件 + subroutine lc_initial_condition(this, x, u) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(in) :: x(:) + real(wp), intent(out) :: u(:) + + integer :: i + + select case (trim(this%ic_type)) + case ("step") + do i = 1, size(x) + if (x(i) >= 0.5_wp .and. x(i) <= 1.0_wp) then + u(i) = 2.0_wp + else + u(i) = 1.0_wp + end if + end do + + case ("sin", "sine") + do i = 1, size(x) + u(i) = sin(2.0_wp * 3.141592653589793_wp * x(i) / this%domain_length) + end do + + case ("gaussian") + do i = 1, size(x) + u(i) = exp(-((x(i) - 0.5_wp) / 0.1_wp)**2) + end do + + case default + ! 默认阶跃函数 + do i = 1, size(x) + if (x(i) >= 0.5_wp .and. x(i) <= 1.0_wp) then + u(i) = 2.0_wp + else + u(i) = 1.0_wp + end if + end do + end select + end subroutine lc_initial_condition + + ! 边界条件(虚拟实现,实际在boundary模块) + subroutine lc_boundary_condition(this, u, t) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(inout) :: u(:) + real(wp), intent(in), optional :: t + + ! 边界条件将在独立模块实现 + print *, "[PROBLEM] Boundary condition placeholder" + if (present(t)) then + print *, " Time = ", t + end if + end subroutine lc_boundary_condition + + ! 精确解(周期性平移) + function lc_exact_solution(this, x, t) result(u) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(in) :: x(:), t + real(wp), dimension(size(x)) :: u + real(wp), dimension(size(x)) :: x_shifted + integer :: i + + ! 周期性平移 + do i = 1, size(x) + x_shifted(i) = x(i) - this%wave_speed * t + ! 确保在 [0, domain_length) 范围内 + do while (x_shifted(i) < 0.0_wp) + x_shifted(i) = x_shifted(i) + this%domain_length + end do + do while (x_shifted(i) >= this%domain_length) + x_shifted(i) = x_shifted(i) - this%domain_length + end do + end do + + ! 重用初始条件函数 + call this%initial_condition(x_shifted, u) + end function lc_exact_solution + +end module linear_convection_problem \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/results.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/results.f90 new file mode 100644 index 000000000..f7ce0a7ad --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/results.f90 @@ -0,0 +1,290 @@ +! src/results.f90 (修正版) +module results_module + use base_modules, only: wp, ip + use config_module, only: cfd_config + use mesh_module, only: mesh_type + use domain_module, only: domain_type + use solution_module, only: solution_type + ! use physics_solver_module, only: physics_solver ! 暂时注释掉,避免循环依赖 + + implicit none + private + public :: results_saver, results_saver_create, save_results + + ! 定义字符串长度常量 + integer, parameter :: STR_LEN = 128 + + ! 结果类型 - 对应Julia的result字典 + type :: cfd_results + character(len=STR_LEN) :: solver_name = "" + real(wp), allocatable :: x(:) ! 网格坐标(单元中心) + real(wp), allocatable :: numerical(:) ! 数值解 + real(wp), allocatable :: analytical(:) ! 解析解 + character(len=STR_LEN) :: scheme = "" ! 格式名称 + integer :: order = 0 ! 阶数 + integer :: rk_order = 0 ! RK阶数 + real(wp) :: final_time = 0.0_wp ! 最终时间 + real(wp) :: current_time = 0.0_wp ! 当前时间 + integer :: total_steps = 0 ! 总步数 + integer :: solver_state = 0 ! 求解器状态 + end type cfd_results + + ! 结果保存器 + type :: results_saver + character(len=STR_LEN) :: base_filename = "results" + logical :: verbose = .true. + contains + procedure :: save_text => results_saver_save_text + procedure :: save_binary => results_saver_save_binary + procedure :: load => results_saver_load + end type results_saver + + ! 接口声明 + interface results_saver + module procedure results_saver_constructor + end interface + +contains + + ! 构造函数 + function results_saver_constructor(base_filename, verbose) result(saver) + character(len=*), optional :: base_filename + logical, optional :: verbose + type(results_saver) :: saver + + if (present(base_filename)) then + saver%base_filename = trim(adjustl(base_filename)) + end if + if (present(verbose)) then + saver%verbose = verbose + end if + end function results_saver_constructor + + ! 保持向后兼容的创建函数 + function results_saver_create(base_filename, verbose) result(saver) + character(len=*), optional :: base_filename + logical, optional :: verbose + type(results_saver) :: saver + + saver = results_saver_constructor(base_filename, verbose) + end function results_saver_create + + ! 生成文件名(与Julia风格一致) + function generate_filename(saver, solver_name, mesh_size) result(filename) + class(results_saver), intent(in) :: saver + character(len=*), intent(in) :: solver_name + integer, intent(in) :: mesh_size + character(len=STR_LEN) :: filename + + write(filename, '(A, "_", A, "_", I0, ".dat")') & + trim(saver%base_filename), trim(solver_name), mesh_size + end function generate_filename + + ! 主保存函数 - 生成与Julia兼容的结果 + subroutine save_results(saver, solver_name, config, mesh, domain, solution, & + current_time, total_steps, solver_state) + class(results_saver), intent(in) :: saver + character(len=*), intent(in) :: solver_name + type(cfd_config), intent(in) :: config + type(mesh_type), intent(in) :: mesh + type(domain_type), intent(in) :: domain + type(solution_type), intent(in) :: solution + real(wp), intent(in) :: current_time + integer, intent(in) :: total_steps, solver_state + + type(cfd_results) :: results + character(len=STR_LEN) :: filename + integer :: i, n_physical + + ! 准备结果数据 + results%solver_name = trim(solver_name) + results%scheme = trim(config%recon_scheme) + results%order = config%spatial_order + results%rk_order = config%rk_order + results%final_time = config%final_time + results%current_time = current_time + results%total_steps = total_steps + results%solver_state = solver_state + + ! 分配数组 + n_physical = mesh%ncells + allocate(results%x(n_physical)) + allocate(results%numerical(n_physical)) + allocate(results%analytical(n_physical)) + + ! 填充网格坐标(单元中心) + results%x = mesh%xcc + + ! 填充数值解(仅物理区域) + do i = 1, n_physical + results%numerical(i) = solution%u(domain%ist + i - 1) + end do + + ! 生成解析解(与Julia的exact_solution对应) + call generate_analytical_solution(results%x, config, results%analytical, current_time) + + ! 生成文件名 + filename = generate_filename(saver, solver_name, mesh%ncells) + + ! 保存文件 + if (saver%verbose) then + print *, "[RESULTS] Saving results to: ", trim(filename) + print *, " Solver: ", trim(results%solver_name) + print *, " Scheme: ", trim(results%scheme), " order ", results%order + print *, " Time: ", results%current_time, " / ", results%final_time + print *, " Steps: ", results%total_steps + end if + + call saver%save_text(results, filename) + + ! 清理 + deallocate(results%x, results%numerical, results%analytical) + end subroutine save_results + + ! 生成解析解(匹配Julia的exact_solution逻辑) + subroutine generate_analytical_solution(x, config, analytical, current_time) + real(wp), intent(in) :: x(:), current_time + type(cfd_config), intent(in) :: config + real(wp), intent(out) :: analytical(:) + + integer :: i, n + real(wp) :: x_shifted, L + + n = size(x) + L = config%domain_length + + select case (trim(config%ic_type)) + case ("step") + ! 阶跃函数的精确解(周期性) + do i = 1, n + ! 周期性平移 + x_shifted = x(i) - config%wave_speed * current_time + x_shifted = modulo(x_shifted, L) + if (x_shifted < 0.0_wp) x_shifted = x_shifted + L + + ! 阶跃在 [0.5, 1.0] 内为 2.0,其他为 1.0 + if (x_shifted >= 0.5_wp .and. x_shifted <= 1.0_wp) then + analytical(i) = 2.0_wp + else + analytical(i) = 1.0_wp + end if + end do + + case ("sin", "sine") + ! 正弦波的精确解 + do i = 1, n + x_shifted = x(i) - config%wave_speed * current_time + x_shifted = modulo(x_shifted, L) + analytical(i) = sin(2.0_wp * 3.141592653589793_wp * x_shifted / L) + end do + + case ("gaussian") + ! 高斯脉冲的精确解 + do i = 1, n + x_shifted = x(i) - config%wave_speed * current_time + x_shifted = modulo(x_shifted, L) + analytical(i) = exp(-50.0_wp * (x_shifted - 1.0_wp)**2) + end do + + case default + ! 默认:阶跃函数 + do i = 1, n + x_shifted = x(i) - config%wave_speed * current_time + x_shifted = modulo(x_shifted, L) + if (x_shifted >= 0.5_wp .and. x_shifted <= 1.0_wp) then + analytical(i) = 2.0_wp + else + analytical(i) = 1.0_wp + end if + end do + end select + end subroutine generate_analytical_solution + + ! 文本格式保存(与Julia的纯文本输出兼容) + subroutine results_saver_save_text(this, results, filename) + class(results_saver), intent(in) :: this + type(cfd_results), intent(in) :: results + character(len=*), intent(in) :: filename + + integer :: i, n, unit, ierr + + n = size(results%x) + + ! 打开文件 + open(newunit=unit, file=trim(filename), status='replace', & + action='write', iostat=ierr) + + if (ierr /= 0) then + if (this%verbose) then + print *, "[ERROR] Cannot open file: ", trim(filename) + end if + return + end if + + ! 写入头部信息(类似Julia的输出格式) + write(unit, '(A)') "========================================" + write(unit, '(A)') "CFD SOLVER RESULTS (Fortran)" + write(unit, '(A)') "========================================" + write(unit, '(A, A)') "Solver: ", trim(results%solver_name) + write(unit, '(A, A)') "Scheme: ", trim(results%scheme) + write(unit, '(A, I0)') "Order: ", results%order + write(unit, '(A, I0)') "RK Order: ", results%rk_order + write(unit, '(A, ES15.8)') "Final Time: ", results%final_time + write(unit, '(A, ES15.8)') "Current Time: ", results%current_time + write(unit, '(A, I0)') "Total Steps: ", results%total_steps + write(unit, '(A, I0)') "Solver State: ", results%solver_state + write(unit, '(A, I0)') "Grid Points: ", n + write(unit, '(A)') "========================================" + write(unit, '(A)') "DATA: x, numerical, analytical" + write(unit, '(A)') "========================================" + + ! 写入数据 + do i = 1, n + write(unit, '(3ES20.12)') results%x(i), results%numerical(i), results%analytical(i) + end do + + ! 关闭文件 + close(unit) + + if (this%verbose) then + print *, "[RESULTS] Saved ", n, " data points to ", trim(filename) + end if + end subroutine results_saver_save_text + + ! 二进制保存(可选) + subroutine results_saver_save_binary(this, results, filename) + class(results_saver), intent(in) :: this + type(cfd_results), intent(in) :: results + character(len=*), intent(in) :: filename + + ! 暂时实现文本格式,二进制格式可后续添加 + if (this%verbose) then + print *, "[INFO] Binary save not implemented, using text format" + end if + call this%save_text(results, filename) + end subroutine results_saver_save_binary + + ! 加载结果(暂时简单实现) + subroutine results_saver_load(this, filename, results) + class(results_saver), intent(in) :: this + character(len=*), intent(in) :: filename + type(cfd_results), intent(out) :: results + + ! 简化:只打印文件信息 + if (this%verbose) then + print *, "[RESULTS] Would load from: ", trim(filename) + print *, " Note: Load functionality needs implementation" + end if + + ! 初始化结果结构以避免未初始化警告 + results%solver_name = "" + results%scheme = "" + results%order = 0 + results%rk_order = 0 + results%final_time = 0.0_wp + results%current_time = 0.0_wp + results%total_steps = 0 + results%solver_state = 0 + end subroutine results_saver_load + +end module results_module diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/solver/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03f/src/solver/CMakeLists.txt new file mode 100644 index 000000000..a88212429 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/solver/CMakeLists.txt @@ -0,0 +1,44 @@ +# src/solver/CMakeLists.txt(统一管理所有求解器) +message(STATUS "配置求解器模块...") + +# 基础求解器库 +add_library(solver STATIC + base.f90 + physics_solver.f90 +) + +target_link_libraries(solver + PRIVATE + infrastructure + core + physics + manager + results + boundary # 添加依赖 + initial_condition # 添加依赖 +) + +set_target_properties(solver PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 集成求解器库(在同一个目录下) +add_library(solver_integrated STATIC + solver_integrated.f90 +) + +target_link_libraries(solver_integrated + PRIVATE + solver # 基础求解器 + infrastructure + base + boundary + initial_condition + results # 如果需要保存结果 +) + +set_target_properties(solver_integrated PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "求解器模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/solver/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/solver/base.f90 new file mode 100644 index 000000000..cfd78c475 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/solver/base.f90 @@ -0,0 +1,264 @@ +! src/solver/base.f90 +module solver_base_module + use base_modules, only: wp => wp, ip => ip ! 重命名以避免冲突 + + use config_module, only: cfd_config + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create + + implicit none + private + + ! 明确导出列表 + public :: wp, ip ! 类型参数 + public :: solver_base, create_solver_base ! 类型和构造函数 + public :: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, SOLVER_RUNNING + public :: SOLVER_COMPLETED, SOLVER_ERROR ! 状态常量 + + ! 求解器状态枚举 + integer, parameter :: SOLVER_UNINITIALIZED = 0 + integer, parameter :: SOLVER_INITIALIZED = 1 + integer, parameter :: SOLVER_RUNNING = 2 + integer, parameter :: SOLVER_COMPLETED = 3 + integer, parameter :: SOLVER_ERROR = 4 + + ! 求解器基类 + type :: solver_base + ! 基本组件 + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + + ! 状态管理 + integer :: state = SOLVER_UNINITIALIZED + character(len=100) :: error_message = "" + real(wp) :: current_time = 0.0_wp + integer(ip) :: current_step = 0 + + ! 时间控制 + real(wp) :: dt_original = 0.0_wp + contains + procedure :: initialize => solver_base_initialize + procedure :: step => solver_base_step + procedure :: run_to_time => solver_base_run_to_time + procedure :: cleanup => solver_base_cleanup + procedure :: get_state => solver_base_get_state + procedure :: get_error => solver_base_get_error + procedure :: print_info => solver_base_print_info + end type solver_base + + ! 构造函数接口 + interface solver_base + module procedure create_solver_base + end interface + +contains + + ! ==================== 构造函数 ==================== + + function create_solver_base(config, mesh) result(solver) + type(cfd_config), intent(in) :: config + type(mesh_type), intent(in) :: mesh + type(solver_base) :: solver + + solver%config = config + solver%mesh = mesh + + ! 创建域 + solver%domain = domain_create(config, mesh) + + ! 创建解 + solver%solution = solution_create(solver%domain) + + ! 保存原始时间步长 + solver%dt_original = config%dt + + if (config%verbose) then + print *, "[SOLVER] Base solver created" + print *, " Mesh cells: ", mesh%ncells + print *, " Domain total cells: ", solver%domain%ntcells + end if + end function create_solver_base + + ! ==================== 初始化 ==================== + + subroutine solver_base_initialize(this) + class(solver_base), intent(inout) :: this + + if (this%state == SOLVER_INITIALIZED) then + if (this%config%verbose) then + print *, "[SOLVER] Already initialized" + end if + return + end if + + ! 初始化解(通过配置) + ! 这里暂时简化,实际需要调用初始条件工厂 + print *, "[INFO] Base solver initialized (simplified)" + + ! 更新状态 + this%state = SOLVER_INITIALIZED + this%current_time = 0.0_wp + this%current_step = 0 + + if (this%config%verbose) then + print *, "[SOLVER] Initialized at t = ", this%current_time + end if + end subroutine solver_base_initialize + + ! ==================== 单步计算(虚方法) ==================== + + subroutine solver_base_step(this, dt) + class(solver_base), intent(inout) :: this + real(wp), intent(in) :: dt + + ! 基类中这只是虚方法,需要在子类中实现 + print *, "[INFO] Base solver step (virtual method)" + print *, " dt = ", dt + print *, " t = ", this%current_time + + ! 更新时间 + this%current_time = this%current_time + dt + this%current_step = this%current_step + 1 + + ! 简单模拟:只是更新状态 + if (this%config%verbose) then + print *, "[SOLVER] Step completed: t = ", this%current_time, & + ", step = ", this%current_step + end if + end subroutine solver_base_step + + ! ==================== 运行到指定时间 ==================== + + subroutine solver_base_run_to_time(this, final_time) + class(solver_base), intent(inout) :: this + real(wp), intent(in) :: final_time + + real(wp) :: dt, t_remaining + integer :: step_count + + if (this%state /= SOLVER_INITIALIZED) then + this%error_message = "Solver not initialized" + this%state = SOLVER_ERROR + if (this%config%verbose) then + print *, "[SOLVER BASE ERROR] Not initialized: ", trim(this%error_message) + end if + return + end if + + this%state = SOLVER_RUNNING + step_count = 0 + + if (this%config%verbose) then + print *, "[SOLVER BASE] Running from t = ", this%current_time, & + " to t = ", final_time + print *, " Time step: ", this%config%dt + end if + + do while (this%current_time < final_time - 1e-12_wp) + ! 计算时间步长 + t_remaining = final_time - this%current_time + dt = min(this%config%dt, t_remaining) + + ! 执行时间步 + call this%step(dt) + + step_count = step_count + 1 + + ! 每50步输出一次进度 + if (mod(step_count, 50) == 0 .and. this%config%verbose) then + print *, "[SOLVER BASE] Progress: t = ", this%current_time, & + " / ", final_time, " (step ", step_count, ")" + end if + end do + + ! 恢复原始时间步长 + this%config%dt = this%dt_original + + ! 更新状态 + this%state = SOLVER_COMPLETED + + if (this%config%verbose) then + print *, "[SOLVER BASE] Run completed:" + print *, " Final time: ", this%current_time + print *, " Total steps: ", this%current_step + print *, " State: ", this%state + end if + end subroutine solver_base_run_to_time + + ! ==================== 清理 ==================== + + subroutine solver_base_cleanup(this) + class(solver_base), intent(inout) :: this + + ! 重置状态 + this%state = SOLVER_UNINITIALIZED + this%current_time = 0.0_wp + this%current_step = 0 + this%error_message = "" + + if (this%config%verbose) then + print *, "[SOLVER] Cleaned up" + end if + end subroutine solver_base_cleanup + + ! ==================== 状态查询 ==================== + + function solver_base_get_state(this) result(state) + class(solver_base), intent(in) :: this + integer :: state + state = this%state + end function solver_base_get_state + + function solver_base_get_error(this) result(error_msg) + class(solver_base), intent(in) :: this + character(len=100) :: error_msg + error_msg = trim(this%error_message) + end function solver_base_get_error + + ! ==================== 信息打印 ==================== + + subroutine solver_base_print_info(this) + class(solver_base), intent(in) :: this + + character(len=20) :: state_str + + ! 状态字符串 + select case (this%state) + case (SOLVER_UNINITIALIZED) + state_str = "Uninitialized" + case (SOLVER_INITIALIZED) + state_str = "Initialized" + case (SOLVER_RUNNING) + state_str = "Running" + case (SOLVER_COMPLETED) + state_str = "Completed" + case (SOLVER_ERROR) + state_str = "Error" + case default + state_str = "Unknown" + end select + + print *, "=== Solver Information ===" + print *, "State: ", trim(state_str) + print *, "Current time: ", this%current_time + print *, "Current step: ", this%current_step + print *, "Error message: '", trim(this%error_message), "'" + + ! 配置信息 + print *, "Configuration:" + print *, " Scheme: ", trim(this%config%recon_scheme) + print *, " Order: ", this%config%spatial_order + print *, " dt: ", this%config%dt + + ! 域信息 + print *, "Domain:" + print *, " Ghost layers: ", this%domain%nghosts + print *, " Physical cells: ", this%domain%ist, " to ", this%domain%ied - 1 + + print *, "=========================" + end subroutine solver_base_print_info + +end module solver_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/solver/physics_solver.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/solver/physics_solver.f90 new file mode 100644 index 000000000..4582042db --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/solver/physics_solver.f90 @@ -0,0 +1,188 @@ +! src/solver/physics_solver.f90 (修正版) +module physics_solver_module + use base_modules, only: wp => wp, ip => ip + use config_module, only: cfd_config + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create + + implicit none + private + + ! 明确导出列表 + public :: wp, ip, physics_solver + public :: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, SOLVER_COMPLETED, SOLVER_ERROR + + ! 求解器状态枚举 + integer, parameter :: SOLVER_UNINITIALIZED = 0 + integer, parameter :: SOLVER_INITIALIZED = 1 + integer, parameter :: SOLVER_RUNNING = 2 + integer, parameter :: SOLVER_COMPLETED = 3 + integer, parameter :: SOLVER_ERROR = 4 + + ! 物理求解器类型 + type :: physics_solver + ! 基本组件 + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + + ! 状态管理 + integer :: state = SOLVER_UNINITIALIZED + character(len=100) :: error_message = "" + real(wp) :: current_time = 0.0_wp + integer(ip) :: current_step = 0 + + ! 时间控制 + real(wp) :: dt_original = 0.0_wp + contains + procedure :: initialize => physics_solver_initialize + procedure :: run_to_time => physics_solver_run_to_time + procedure :: cleanup => physics_solver_cleanup + procedure :: get_state => physics_solver_get_state + procedure, private :: apply_simple_initial_condition ! 添加这行 + end type physics_solver + +contains + + ! ==================== 初始化 ==================== + + subroutine physics_solver_initialize(this) + class(physics_solver), intent(inout) :: this + + if (this%state == SOLVER_INITIALIZED) then + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Already initialized" + end if + return + end if + + ! 创建域 + this%domain = domain_create(this%config, this%mesh) + + ! 创建解 + this%solution = solution_create(this%domain) + + ! 应用简化的初始条件 + call this%apply_simple_initial_condition() + + ! 保存原始时间步长 + this%dt_original = this%config%dt + + ! 更新状态 + this%state = SOLVER_INITIALIZED + this%current_time = 0.0_wp + this%current_step = 0 + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Initialized at t = ", this%current_time + end if + end subroutine physics_solver_initialize + + subroutine apply_simple_initial_condition(this) + class(physics_solver), intent(inout) :: this + + integer :: i, idx + real(wp) :: x + + ! 简化的阶跃函数初始条件 + do i = this%domain%ist, this%domain%ied - 1 + idx = i - this%domain%ist + 1 + x = this%mesh%xcc(idx) + + if (x >= 0.5_wp .and. x <= 1.0_wp) then + this%solution%u(i) = 2.0_wp + else + this%solution%u(i) = 1.0_wp + end if + end do + + ! 同步旧场 + this%solution%un = this%solution%u + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Applied simple initial condition" + end if + end subroutine + + ! ==================== 运行到指定时间 ==================== + + subroutine physics_solver_run_to_time(this, final_time) + class(physics_solver), intent(inout) :: this + real(wp), intent(in) :: final_time + + real(wp) :: dt, t_remaining + integer :: step_count + + if (this%state /= SOLVER_INITIALIZED) then + this%error_message = "Solver not initialized" + this%state = SOLVER_ERROR + if (this%config%verbose) then + print *, "[PHYSICS SOLVER ERROR] Not initialized" + end if + return + end if + + this%state = SOLVER_RUNNING + step_count = 0 + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Running from t = ", this%current_time, & + " to t = ", final_time + end if + + do while (this%current_time < final_time - 1e-12_wp) + ! 计算时间步长 + t_remaining = final_time - this%current_time + dt = min(this%config%dt, t_remaining) + + ! 简单的时间步进(占位符) + this%current_time = this%current_time + dt + this%current_step = this%current_step + 1 + step_count = step_count + 1 + + ! 每100步输出一次进度 + if (mod(step_count, 100) == 0 .and. this%config%verbose) then + print *, "[PHYSICS SOLVER] Progress: t = ", this%current_time, & + " / ", final_time, " (step ", step_count, ")" + end if + end do + + ! 恢复原始时间步长 + this%config%dt = this%dt_original + + ! 更新状态 + this%state = SOLVER_COMPLETED + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Run completed:" + print *, " Final time: ", this%current_time + print *, " Total steps: ", this%current_step + end if + end subroutine physics_solver_run_to_time + + ! ==================== 清理 ==================== + + subroutine physics_solver_cleanup(this) + class(physics_solver), intent(inout) :: this + + this%state = SOLVER_UNINITIALIZED + this%current_time = 0.0_wp + this%current_step = 0 + this%error_message = "" + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Cleaned up" + end if + end subroutine physics_solver_cleanup + + ! ==================== 状态查询 ==================== + + function physics_solver_get_state(this) result(state) + class(physics_solver), intent(in) :: this + integer :: state + state = this%state + end function physics_solver_get_state + +end module physics_solver_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/solver/residual.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/solver/residual.f90 new file mode 100644 index 000000000..91ec2c717 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/solver/residual.f90 @@ -0,0 +1,69 @@ +! src/solver/residual.f90 +module residual_module + use base_modules, only: wp, ip + use domain_module, only: domain_type + use solution_module, only: solution_type + use mesh_module, only: mesh_type + implicit none + private + + type, public :: residual_calculator + class(*), pointer :: reconstructor => null() + class(*), pointer :: flux_calc => null() + class(*), pointer :: equation => null() + real(wp), pointer :: qL(:) => null() + real(wp), pointer :: qR(:) => null() + real(wp), pointer :: flux(:) => null() + real(wp), pointer :: res(:) => null() + real(wp) :: dx = 0.0_wp + contains + procedure :: compute => residual_compute + procedure :: init => residual_init + end type + +contains + + subroutine residual_init(this, reconstructor, flux_calc, equation, & + qL, qR, flux, res, dx) + class(residual_calculator), intent(inout) :: this + class(*), intent(in) :: reconstructor, flux_calc, equation + real(wp), intent(in), target :: qL(:), qR(:), flux(:), res(:) + real(wp), intent(in) :: dx + + this%reconstructor => reconstructor + this%flux_calc => flux_calc + this%equation => equation + this%qL => qL + this%qR => qR + this%flux => flux + this%res => res + this%dx = dx + end subroutine + + subroutine residual_compute(this, u) + class(residual_calculator), intent(inout) :: this + real(wp), intent(in) :: u(:) + + integer :: i, n + real(wp) :: f_left, f_right + + ! 阶段1: 重构界面值 (当前为占位符) + print *, "[RESIDUAL] Reconstructing interface values (placeholder)" + + ! 阶段2: 计算通量 (当前为占位符) + print *, "[RESIDUAL] Computing fluxes (placeholder)" + + ! 阶段3: 计算残差 (真实计算示例) + n = size(this%res) + do i = 1, n + ! 简单的一阶迎风格式作为占位符 + f_left = this%equation%flux(u(i)) + f_right = this%equation%flux(u(i+1)) + this%res(i) = -(f_right - f_left) / this%dx + end do + + print *, "[RESIDUAL] Residual computed, range: ", & + minval(this%res), " to ", maxval(this%res) + end subroutine + +end module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/src/solver/solver_integrated.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/src/solver/solver_integrated.f90 new file mode 100644 index 000000000..6a9a0bfd4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/src/solver/solver_integrated.f90 @@ -0,0 +1,390 @@ +! src/solver/solver_integrated.f90 (完全修正版) +module solver_integrated_module + use base_modules, only: wp, ip + use config_module, only: cfd_config + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create + use boundary_base_module, only: boundary_condition + use periodic_boundary_module, only: periodic_boundary + + implicit none + private + + ! 求解器状态常量 + integer, parameter, public :: SOLVER_READY = 0 + integer, parameter, public :: SOLVER_INITIALIZED = 1 + integer, parameter, public :: SOLVER_RUNNING = 2 + integer, parameter, public :: SOLVER_COMPLETED = 3 + integer, parameter, public :: SOLVER_ERROR = -1 + + type, public :: integrated_solver + ! 基本组件 + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + + ! 组件实例 + class(boundary_condition), allocatable :: bc + + ! 状态 + integer :: state = SOLVER_READY + real(wp) :: current_time = 0.0_wp + integer(ip) :: current_step = 0 + character(len=100) :: error_msg = "" + + ! 数据模式 + logical :: use_real_data = .true. + + contains + procedure :: initialize => solver_initialize + procedure :: run_to_time => solver_run_to_time + procedure :: cleanup => solver_cleanup + procedure :: get_state => solver_get_state + procedure :: enable_real_data => solver_enable_real_data + procedure, private :: apply_initial_condition + procedure, private :: apply_boundary_conditions + procedure, private :: simple_time_step + procedure, private :: calculate_dt + procedure, private :: setup_boundary_condition ! 修改方法名 + end type integrated_solver + +contains + + ! ==================== 公共接口 ==================== + + subroutine solver_initialize(this) + class(integrated_solver), intent(inout) :: this + + if (this%state /= SOLVER_READY) then + this%error_msg = "Solver already initialized" + this%state = SOLVER_ERROR + if (this%config%verbose) then + print *, "[ERROR] ", trim(this%error_msg) + end if + return + end if + + ! 创建域 + this%domain = domain_create(this%config, this%mesh) + + ! 创建解 + this%solution = solution_create(this%domain) + + ! 设置边界条件 + call this%setup_boundary_condition() ! 修改调用 + + ! 应用初始条件 + call this%apply_initial_condition() + + ! 初始化状态 + this%state = SOLVER_INITIALIZED + this%current_time = 0.0_wp + this%current_step = 0 + + if (this%config%verbose) then + print *, "[INTEGRATED SOLVER] Initialized successfully" + print *, " Domain cells (with ghosts): ", this%domain%ntcells + print *, " Ghost layers: ", this%domain%nghosts + if (this%use_real_data) then + print *, " Data mode: Real" + else + print *, " Data mode: Simple" + end if + end if + end subroutine solver_initialize + + ! ==================== 边界条件设置 ==================== + + subroutine setup_boundary_condition(this) + class(integrated_solver), intent(inout) :: this + + ! 根据配置创建边界条件 + select case (trim(this%config%boundary_type)) + case ("periodic") + ! 创建周期性边界条件 + if (.not. allocated(this%bc)) then + allocate(periodic_boundary :: this%bc) + end if + + select type(bc => this%bc) + type is (periodic_boundary) + bc%name = "periodic" + end select + + case default + ! 默认使用周期性边界 + if (this%config%verbose) then + print *, "[WARNING] Using periodic boundary as default" + end if + + if (.not. allocated(this%bc)) then + allocate(periodic_boundary :: this%bc) + end if + + select type(bc => this%bc) + type is (periodic_boundary) + bc%name = "periodic" + end select + end select + + if (this%config%verbose) then + print *, "[INTEGRATED SOLVER] Boundary condition created: ", & + trim(this%bc%get_name()) + end if + end subroutine setup_boundary_condition + + subroutine solver_run_to_time(this, final_time) + class(integrated_solver), intent(inout) :: this + real(wp), intent(in) :: final_time + + real(wp) :: dt, t_remaining + integer :: step_count + real(wp) :: original_dt + + if (this%state /= SOLVER_INITIALIZED) then + this%error_msg = "Solver not initialized" + this%state = SOLVER_ERROR + if (this%config%verbose) then + print *, "[ERROR] ", trim(this%error_msg) + end if + return + end if + + ! 保存原始时间步长 + original_dt = this%config%dt + + this%state = SOLVER_RUNNING + step_count = 0 + + if (this%config%verbose) then + print *, "[INTEGRATED SOLVER] Starting time integration" + print *, " Initial time: ", this%current_time + print *, " Final time: ", final_time + print *, " Initial dt: ", this%config%dt + end if + + do while (this%current_time < final_time - 1e-12_wp) + ! 计算时间步长 + call this%calculate_dt() + + ! 确保不超过最终时间 + t_remaining = final_time - this%current_time + dt = min(this%config%dt, t_remaining) + + ! 应用边界条件 + call this%apply_boundary_conditions() + + ! 执行时间步 + call this%simple_time_step(dt) + + ! 更新时间 + this%current_time = this%current_time + dt + this%current_step = this%current_step + 1 + step_count = step_count + 1 + + ! 进度输出 + if (mod(step_count, 50) == 0 .and. this%config%verbose) then + print *, " Progress: t = ", this%current_time, & + " / ", final_time, " (step ", step_count, ")" + end if + end do + + ! 恢复原始时间步长 + this%config%dt = original_dt + + ! 更新状态 + this%state = SOLVER_COMPLETED + + if (this%config%verbose) then + print *, "[INTEGRATED SOLVER] Time integration completed" + print *, " Final time: ", this%current_time + print *, " Total steps: ", this%current_step + if (allocated(this%solution%u)) then + print *, " Solution range: [", minval(this%solution%u), ", ", & + maxval(this%solution%u), "]" + end if + end if + end subroutine solver_run_to_time + + subroutine solver_cleanup(this) + class(integrated_solver), intent(inout) :: this + + ! 清理分配的组件 + if (allocated(this%bc)) then + deallocate(this%bc) + end if + + ! 重置状态 + this%state = SOLVER_READY + this%current_time = 0.0_wp + this%current_step = 0 + this%error_msg = "" + + if (this%config%verbose) then + print *, "[INTEGRATED SOLVER] Cleaned up" + end if + end subroutine solver_cleanup + + function solver_get_state(this) result(state) + class(integrated_solver), intent(in) :: this + integer :: state + state = this%state + end function solver_get_state + + subroutine solver_enable_real_data(this, use_real) + class(integrated_solver), intent(inout) :: this + logical, intent(in) :: use_real + this%use_real_data = use_real + + if (this%config%verbose) then + if (use_real) then + print *, "[INTEGRATED SOLVER] Data mode set to: Real" + else + print *, "[INTEGRATED SOLVER] Data mode set to: Simple" + end if + end if + end subroutine solver_enable_real_data + + ! ==================== 私有方法 ==================== + + subroutine apply_initial_condition(this) + class(integrated_solver), intent(inout) :: this + + integer :: i, idx + real(wp) :: x + + if (this%config%verbose) then + print *, "[INTEGRATED SOLVER] Applying initial condition: ", & + trim(this%config%ic_type) + end if + + ! 简化的初始条件应用 + do i = this%domain%ist, this%domain%ied - 1 + idx = i - this%domain%ist + 1 + x = this%mesh%xcc(idx) + + select case (trim(this%config%ic_type)) + case ("step") + ! 阶跃函数 + if (x >= 0.5_wp .and. x <= 1.0_wp) then + this%solution%u(i) = 2.0_wp + else + this%solution%u(i) = 1.0_wp + end if + + case ("sin", "sine") + ! 正弦波 + this%solution%u(i) = sin(2.0_wp * 3.141592653589793_wp * x / & + this%config%domain_length) + + case ("gaussian") + ! 高斯脉冲 + this%solution%u(i) = exp(-((x - 0.5_wp) / 0.1_wp)**2) + + case default + ! 默认阶跃函数 + if (x >= 0.5_wp .and. x <= 1.0_wp) then + this%solution%u(i) = 2.0_wp + else + this%solution%u(i) = 1.0_wp + end if + end select + end do + + ! 同步旧场 + this%solution%un = this%solution%u + + if (this%config%verbose) then + print *, "[INTEGRATED SOLVER] Initial condition applied" + if (allocated(this%solution%u)) then + print *, " Min value: ", minval(this%solution%u) + print *, " Max value: ", maxval(this%solution%u) + end if + end if + end subroutine apply_initial_condition + + subroutine apply_boundary_conditions(this) + class(integrated_solver), intent(inout) :: this + + if (.not. allocated(this%bc)) then + if (this%config%verbose) then + print *, "[WARNING] No boundary condition allocated" + end if + return + end if + + select type(bc => this%bc) + type is (periodic_boundary) + ! 应用周期性边界条件 + call bc%apply(this%solution%u, & + this%domain%nghosts, & + this%domain%ist, & + this%domain%ied - 1) + + class default + ! 对于其他边界条件类型 + if (this%config%verbose) then + print *, "[WARNING] Boundary condition type not fully implemented" + end if + end select + end subroutine apply_boundary_conditions + + subroutine simple_time_step(this, dt) + class(integrated_solver), intent(inout) :: this + real(wp), intent(in) :: dt + + integer :: i + real(wp) :: dx, cfl + + ! 简化的时间步进(一阶迎风格式) + dx = this%mesh%dx + cfl = this%config%wave_speed * dt / dx + + if (cfl > 1.0_wp .and. this%config%verbose) then + print *, "[WARNING] CFL = ", cfl, " > 1.0" + end if + + ! 保存旧解 + this%solution%un = this%solution%u + + ! 一阶迎风格式(只更新内部点) + if (this%domain%ist + 1 <= this%domain%ied - 2) then + do i = this%domain%ist + 1, this%domain%ied - 2 + this%solution%u(i) = this%solution%un(i) - & + cfl * (this%solution%un(i) - this%solution%un(i-1)) + end do + end if + + ! 调试输出 + if (mod(this%current_step, 100) == 0 .and. this%config%verbose) then + print *, " [TIME STEP] Step: ", this%current_step, & + ", t = ", this%current_time, & + ", CFL = ", cfl, & + ", dt = ", dt + end if + end subroutine simple_time_step + + subroutine calculate_dt(this) + class(integrated_solver), intent(inout) :: this + + real(wp) :: cfl, dx + + dx = this%mesh%dx + + if (this%use_real_data) then + ! 真实计算使用CFL条件 + cfl = 0.8_wp ! CFL数 + this%config%dt = cfl * dx / abs(this%config%wave_speed) + else + ! 简单数据使用固定时间步长 + this%config%dt = 0.0025_wp + end if + + if (this%config%verbose .and. this%current_step == 0) then + print *, "[INTEGRATED SOLVER] Calculated dt = ", this%config%dt + end if + end subroutine calculate_dt + +end module solver_integrated_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/CMakeLists.txt new file mode 100644 index 000000000..ced7163ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/CMakeLists.txt @@ -0,0 +1,137 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +# 基础设施测试 +add_executable(test_infrastructure test_infrastructure.f90) +target_link_libraries(test_infrastructure + PRIVATE + infrastructure + core +) + +# 注册系统测试 +add_executable(test_registry test_registry.f90) +target_link_libraries(test_registry + PRIVATE + core + infrastructure +) + +# 物理模块测试 +add_executable(test_physics test_physics.f90) +target_link_libraries(test_physics + PRIVATE + physics + base +) + +# 组件管理器测试 +add_executable(test_component_manager test_component_manager.f90) +target_link_libraries(test_component_manager + PRIVATE + manager + infrastructure +) + +# 配置物理测试 +add_executable(test_config_physics test_config_physics.f90) +target_link_libraries(test_config_physics + PRIVATE + infrastructure + core +) + +# 求解器基础测试 +add_executable(test_solver_base test_solver_base.f90) +target_link_libraries(test_solver_base + PRIVATE + solver + infrastructure + core +) + +# 物理求解器测试 +add_executable(test_physics_solver test_physics_solver.f90) +target_link_libraries(test_physics_solver + PRIVATE + solver + infrastructure + core + physics + manager +) + +# 新增:简单物理求解器测试 +add_executable(test_physics_solver_simple test_physics_solver_simple.f90) +target_link_libraries(test_physics_solver_simple + PRIVATE + solver + infrastructure + core + physics + manager +) + +add_executable(test_simple_link test_simple_link.f90) +target_link_libraries(test_simple_link + PRIVATE + reconstructor + flux +) + + +add_executable(test_factory_simple test_factory_simple.f90) +target_link_libraries(test_factory_simple + PRIVATE + core + infrastructure + reconstructor + flux +) + +add_executable(test_domain_solution test_domain_solution.f90) +target_link_libraries(test_domain_solution + PRIVATE + infrastructure + core +) + +add_executable(test_component_manager_physics test_component_manager_physics.f90) +target_link_libraries(test_component_manager_physics + PRIVATE + manager + infrastructure + physics + core +) + +# 初始条件测试 +add_executable(test_initial_condition test_initial_condition.f90) +target_link_libraries(test_initial_condition + PRIVATE + initial_condition + infrastructure + core + base +) + +# 新增:简单求解器测试(只包含现有的) +# 注释掉缺失文件的测试,或者创建简单的占位符文件 +# add_executable(test_solver_integrated test_solver_integrated.f90) +# target_link_libraries(test_solver_integrated +# PRIVATE +# solver +# infrastructure +# core +# physics +# manager +# ) + +# 注释掉缺失的残差测试 +# add_executable(test_residual test_residual.f90) +# target_link_libraries(test_residual +# PRIVATE +# solver +# infrastructure +# physics +# ) diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_architecture.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_architecture.f90 new file mode 100644 index 000000000..1c40d4677 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_architecture.f90 @@ -0,0 +1,117 @@ +! tests/test_architecture.f90 +program test_architecture + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module + use config_module + use mesh_module + use component_manager_module + + implicit none + + print *, "=== CFD架构测试 ===" + print *, "" + + ! 测试1: 基本系统 + call test_basic_systems() + + ! 测试2: 组件注册 + call test_registry_integration() + + ! 测试3: 配置验证 + call test_config_validation() + + ! 测试4: 完整流程 + call test_full_workflow() + + print *, "" + print *, "=== 架构测试完成 ===" + +contains + + subroutine test_basic_systems() + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "1. 测试基本系统..." + print *, "-------------------" + + ! 测试配置 + call config_print(config) + print *, "✓ 配置创建成功" + + ! 测试网格 + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "✓ 网格创建成功" + print *, "" + end subroutine test_basic_systems + + subroutine test_registry_integration() + print *, "2. 测试注册系统集成..." + print *, "------------------------" + + call initialize_registry(verbose=.true.) + + ! 注册核心组件 + print *, "注册核心组件:" + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + ! 显示注册内容 + call component_registry%list_simple() + print *, "注册表大小: ", registry_get_size() + + call cleanup_registry() + print *, "✓ 注册系统测试完成" + print *, "" + end subroutine test_registry_integration + + subroutine test_config_validation() + type(cfd_config) :: config + logical :: is_valid + + print *, "3. 测试配置验证..." + print *, "-------------------" + + ! 测试有效配置 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + print *, "测试配置:" + print *, " - 重构器: ", trim(config%recon_scheme), " 阶数: ", config%spatial_order + print *, " - 通量: ", trim(config%flux_type) + print *, " - 波速: ", config%wave_speed + + ! 这里调用验证函数(需要先实现) + ! is_valid = validate_config(config) + print *, "✓ 配置验证接口准备就绪" + print *, "" + end subroutine test_config_validation + + subroutine test_full_workflow() + print *, "4. 测试完整流程..." + print *, "-------------------" + + print *, "步骤1: 初始化系统 ✓" + print *, "步骤2: 创建网格 ✓" + print *, "步骤3: 设置配置 ✓" + print *, "步骤4: 创建组件 (待实现)" + print *, "步骤5: 初始化解 (待实现)" + print *, "步骤6: 时间推进 (待实现)" + print *, "步骤7: 输出结果 (待实现)" + print *, "" + + print *, "✓ 流程框架定义完成" + end subroutine test_full_workflow + +end program test_architecture \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_cfd_architecture.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_cfd_architecture.f90 new file mode 100644 index 000000000..1c40d4677 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_cfd_architecture.f90 @@ -0,0 +1,117 @@ +! tests/test_architecture.f90 +program test_architecture + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module + use config_module + use mesh_module + use component_manager_module + + implicit none + + print *, "=== CFD架构测试 ===" + print *, "" + + ! 测试1: 基本系统 + call test_basic_systems() + + ! 测试2: 组件注册 + call test_registry_integration() + + ! 测试3: 配置验证 + call test_config_validation() + + ! 测试4: 完整流程 + call test_full_workflow() + + print *, "" + print *, "=== 架构测试完成 ===" + +contains + + subroutine test_basic_systems() + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "1. 测试基本系统..." + print *, "-------------------" + + ! 测试配置 + call config_print(config) + print *, "✓ 配置创建成功" + + ! 测试网格 + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "✓ 网格创建成功" + print *, "" + end subroutine test_basic_systems + + subroutine test_registry_integration() + print *, "2. 测试注册系统集成..." + print *, "------------------------" + + call initialize_registry(verbose=.true.) + + ! 注册核心组件 + print *, "注册核心组件:" + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + ! 显示注册内容 + call component_registry%list_simple() + print *, "注册表大小: ", registry_get_size() + + call cleanup_registry() + print *, "✓ 注册系统测试完成" + print *, "" + end subroutine test_registry_integration + + subroutine test_config_validation() + type(cfd_config) :: config + logical :: is_valid + + print *, "3. 测试配置验证..." + print *, "-------------------" + + ! 测试有效配置 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + print *, "测试配置:" + print *, " - 重构器: ", trim(config%recon_scheme), " 阶数: ", config%spatial_order + print *, " - 通量: ", trim(config%flux_type) + print *, " - 波速: ", config%wave_speed + + ! 这里调用验证函数(需要先实现) + ! is_valid = validate_config(config) + print *, "✓ 配置验证接口准备就绪" + print *, "" + end subroutine test_config_validation + + subroutine test_full_workflow() + print *, "4. 测试完整流程..." + print *, "-------------------" + + print *, "步骤1: 初始化系统 ✓" + print *, "步骤2: 创建网格 ✓" + print *, "步骤3: 设置配置 ✓" + print *, "步骤4: 创建组件 (待实现)" + print *, "步骤5: 初始化解 (待实现)" + print *, "步骤6: 时间推进 (待实现)" + print *, "步骤7: 输出结果 (待实现)" + print *, "" + + print *, "✓ 流程框架定义完成" + end subroutine test_full_workflow + +end program test_architecture \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_component_manager.f90 new file mode 100644 index 000000000..cf4cf38a8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_component_manager.f90 @@ -0,0 +1,56 @@ +! tests/test_component_manager.f90 (修正版) +program test_component_manager + use base_modules, only: wp + use config_module, only: cfd_config, config_print, config_with_reconstruction + implicit none + + type(cfd_config) :: config + + print *, "=== Component Manager Test (简化版) ===" + print *, "" + + ! 测试1: 基本配置 + print *, "1. Testing basic configuration..." + print *, "-----------------------------------" + + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_wp + + call config_print(config) + print *, "" + + print *, "2. Testing component manager info (简化)..." + print *, "------------------------------------------" + print *, "Component manager functions (简化版本):" + print *, " - Configuration validation available" + print *, " - Component creation framework ready" + print *, "" + + print *, "3. Testing WENO3 configuration..." + print *, "---------------------------------" + + call config_with_reconstruction(config, "weno3", 3) + + print *, "WENO3 configuration:" + print *, " Scheme: ", trim(config%recon_scheme) + print *, " Order: ", config%spatial_order + print *, "" + + ! 测试4: 错误配置测试 + print *, "4. Testing error handling..." + print *, "-----------------------------------" + + config%recon_scheme = "unknown_scheme" + config%flux_type = "unknown_flux" + + print *, "Invalid configuration test:" + print *, " Scheme: ", trim(config%recon_scheme) + print *, " Flux: ", trim(config%flux_type) + print *, "" + + print *, "=== Component manager test completed (简化版) ===" + print *, "下一步: 完善组件管理器功能" + +end program test_component_manager \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_component_manager_physics.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_component_manager_physics.f90 new file mode 100644 index 000000000..f2becca95 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_component_manager_physics.f90 @@ -0,0 +1,120 @@ +! tests/test_component_manager_physics.f90 (简化版) +program test_component_manager_physics + use base_modules, only: wp + use config_module, only: cfd_config, config_print, config_with_reconstruction + use component_manager_module, only: component_manager_info, validate_config + + implicit none + + type(cfd_config) :: config + logical :: is_valid + + print *, "=== Component Manager Physics Test (Simplified) ===" + print *, "" + + ! 测试1: 显示组件管理器信息 + print *, "1. Testing component manager info..." + print *, "-------------------------------------" + call component_manager_info() + print *, "" + + ! 测试2: 物理模块测试(默认) + print *, "2. Testing physics module with default configuration..." + print *, "------------------------------------------------------" + + config%verbose = .true. + call config_print(config) + + ! 验证配置 + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Default configuration is valid" + else + print *, "[ERROR] Default configuration is invalid" + end if + print *, "" + + ! 测试3: 测试物理配置 + print *, "3. Testing physics configuration..." + print *, "------------------------------------" + + ! 修改物理参数 + config%equation_type = "linear_advection" + config%problem_type = "linear_advection" + config%wave_speed = 2.5_wp + config%domain_length = 3.0_wp + + print *, "Modified physics configuration:" + print *, " Equation type: ", trim(config%equation_type) + print *, " Problem type: ", trim(config%problem_type) + print *, " Wave speed: ", config%wave_speed + print *, " Domain length: ", config%domain_length + print *, " Physics enabled: ", config%enable_physics + + ! 验证修改后的配置 + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Modified physics configuration is valid" + else + print *, "[ERROR] Modified physics configuration is invalid" + end if + print *, "" + + ! 测试4: 数值组件测试 + print *, "4. Testing numerical components with physics..." + print *, "-----------------------------------------------" + + call config_with_reconstruction(config, "weno3", 3) + config%flux_type = "rusanov" + + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Combined physics+numerics configuration is valid" + else + print *, "[ERROR] Combined configuration is invalid" + end if + print *, "" + + ! 测试5: 物理模块禁用测试 + print *, "5. Testing physics module disabled..." + print *, "---------------------------------------" + + config%enable_physics = .false. + config%verbose = .false. + + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Configuration valid even with physics disabled" + else + print *, "[ERROR] Configuration should be valid with physics disabled" + end if + print *, "" + + ! 测试6: 错误配置测试 + print *, "6. Testing error handling..." + print *, "-----------------------------" + + config%verbose = .true. + config%enable_physics = .true. + config%recon_scheme = "unknown_scheme" + config%flux_type = "unknown_flux" + config%equation_type = "unknown_equation" + config%problem_type = "unknown_problem" + + is_valid = validate_config(config) + if (.not. is_valid) then + print *, "[OK] Invalid configuration correctly rejected" + else + print *, "[ERROR] Invalid configuration should have been rejected" + end if + print *, "" + + print *, "=== Component Manager Physics Test Summary ===" + print *, "✓ Component manager info works" + print *, "✓ Configuration validation works with physics" + print *, "✓ Error handling works correctly" + print *, "✓ Combined physics+numerics validation works" + print *, "" + print *, "下一步: 集成物理模块到求解器框架" + +end program test_component_manager_physics \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_config_physics.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_config_physics.f90 new file mode 100644 index 000000000..c6fef5c0b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_config_physics.f90 @@ -0,0 +1,141 @@ +! tests/test_config_physics.f90 (修复版) +program test_config_physics + use base_modules, only: wp + use config_module, only: cfd_config, config_print, config_with_reconstruction + + implicit none + + type(cfd_config) :: config + + print *, "=== Configuration Physics Test (Simplified) ===" + print *, "" + + ! 测试1: 默认配置 + print *, "1. Testing default configuration..." + print *, "-----------------------------------" + call config_print(config) + print *, "" + + ! 测试2: 验证基础物理字段 + print *, "2. Testing basic physics fields..." + print *, "----------------------------------" + + print *, "Verifying default physics fields:" + + if (trim(config%equation_type) == "linear_advection") then + print *, " ✓ Default equation type: linear_advection" + else + print *, " ✗ Unexpected equation type: ", trim(config%equation_type) + end if + + if (trim(config%problem_type) == "linear_advection") then + print *, " ✓ Default problem type: linear_advection" + else + print *, " ✗ Unexpected problem type: ", trim(config%problem_type) + end if + + if (abs(config%domain_length - 2.0_wp) < 1e-10_wp) then + print *, " ✓ Default domain length: 2.0" + else + print *, " ✗ Unexpected domain length: ", config%domain_length + end if + + if (config%enable_physics) then + print *, " ✓ Physics enabled by default" + else + print *, " ✗ Physics not enabled by default" + end if + + print *, "" + + ! 测试3: 使用类型绑定的方法(正确的方法名) + print *, "3. Testing type-bound procedures..." + print *, "--------------------------------------" + + call config%set_physics_parameters( & + equation_type="burgers_equation", & + problem_type="sod_shock_tube", & + domain_length=3.0_wp, & + enable_physics=.false.) + + print *, "After set_physics_parameters:" + print *, " Equation type: ", trim(config%equation_type) + print *, " Problem type: ", trim(config%problem_type) + print *, " Domain length: ", config%domain_length + print *, " Physics enabled: ", config%enable_physics + + if (trim(config%equation_type) == "burgers_equation") then + print *, " ✓ Equation type modified successfully via set_physics_parameters" + end if + + if (trim(config%problem_type) == "sod_shock_tube") then + print *, " ✓ Problem type modified successfully via set_physics_parameters" + end if + + if (abs(config%domain_length - 3.0_wp) < 1e-10_wp) then + print *, " ✓ Domain length modified successfully via set_physics_parameters" + end if + + if (.not. config%enable_physics) then + print *, " ✓ Physics disabled successfully via set_physics_parameters" + end if + + print *, "" + + ! 测试4: 调用get_physics_info方法 + print *, "4. Testing get_physics_info method..." + print *, "--------------------------------------" + call config%get_physics_info() + print *, "" + + ! 测试5: 高斯脉冲配置 + print *, "5. Testing Gaussian pulse configuration..." + print *, "-----------------------------------------" + + config%ic_type = "gaussian" + config%pulse_center = 0.6_wp + config%pulse_width = 0.15_wp + + print *, "Gaussian pulse parameters:" + print *, " IC type: ", trim(config%ic_type) + print *, " Center: ", config%pulse_center + print *, " Width: ", config%pulse_width + + if (trim(config%ic_type) == "gaussian") then + print *, " ✓ Gaussian IC type set" + end if + + if (abs(config%pulse_center - 0.6_wp) < 1e-10_wp) then + print *, " ✓ Pulse center set" + end if + + if (abs(config%pulse_width - 0.15_wp) < 1e-10_wp) then + print *, " ✓ Pulse width set" + end if + + print *, "" + + ! 测试6: 重构配置 + print *, "6. Testing reconstruction configuration..." + print *, "------------------------------------------" + + call config_with_reconstruction(config, "weno", 5) + + print *, "Reconstruction configuration:" + print *, " Scheme: ", trim(config%recon_scheme) + print *, " Order: ", config%spatial_order + + if (trim(config%recon_scheme) == "weno" .and. config%spatial_order == 5) then + print *, " ✓ WENO5 configuration successful" + else + print *, " ✗ Reconstruction configuration failed" + end if + + print *, "" + + print *, "=== Configuration Physics Test Complete ===" + print *, "✓ Config module updated with physics support" + print *, "✓ Fields can be directly accessed and modified" + print *, "✓ Type-bound procedures work correctly" + +end program test_config_physics \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_domain_solution.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_domain_solution.f90 new file mode 100644 index 000000000..ff659bac2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_domain_solution.f90 @@ -0,0 +1,102 @@ +! tests/test_domain_solution.f90 +program test_domain_solution + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create, solution_reset + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + real(wp), allocatable :: initial_values(:) + integer :: i + + print *, "=== Domain and Solution Test ===" + print *, "" + + ! 测试1: 不同重构方案的ghost层计算 + print *, "1. Testing ghost layer calculation..." + print *, "--------------------------------------" + + ! ENO3 + call config_with_reconstruction(config, "eno", 3) + config%verbose = .false. + call mesh%init(ncells=10) + domain = domain_create(config, mesh) + print *, "ENO3: nghosts = ", domain%nghosts, " (expected: 3)" + + ! WENO3 + call config_with_reconstruction(config, "weno3", 3) + domain = domain_create(config, mesh) + print *, "WENO3: nghosts = ", domain%nghosts, " (expected: 2)" + + ! WENO5 + call config_with_reconstruction(config, "weno", 5) + domain = domain_create(config, mesh) + print *, "WENO5: nghosts = ", domain%nghosts, " (expected: 3)" + print *, "" + + ! 测试2: Solution数组 + print *, "2. Testing solution arrays..." + print *, "------------------------------" + + call config_with_reconstruction(config, "eno", 3) + config%verbose = .true. + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=10) + + domain = domain_create(config, mesh) + call domain%print_info() + print *, "" + + solution = solution_create(domain) + call solution%print_info() + print *, "" + + ! 测试3: 初始化和更新 + print *, "3. Testing initialization and update..." + print *, "----------------------------------------" + + allocate(initial_values(mesh%ncells)) + do i = 1, mesh%ncells + initial_values(i) = sin(2.0_wp * 3.14159265358979_wp * mesh%xcc(i) / mesh%L) + end do + + call solution%initialize(initial_values) + print *, "After initialization:" + print *, " u range: ", minval(solution%u), " to ", maxval(solution%u) + print *, " un range: ", minval(solution%un), " to ", maxval(solution%un) + + ! 修改当前解,测试更新 + solution%u = solution%u * 2.0_wp + call solution%update_old_field() + print *, "After update: max|u - un| = ", maxval(abs(solution%u - solution%un)) + print *, "" + + ! 测试4: 重置 + print *, "4. Testing reset..." + print *, "-------------------" + + call solution_reset(solution) + print *, "After reset:" + print *, " u max: ", maxval(abs(solution%u)) + print *, " un max: ", maxval(abs(solution%un)) + print *, " flux max: ", maxval(abs(solution%flux)) + print *, "" + + deallocate(initial_values) + + print *, "=== Test Summary ===" + print *, "✓ Ghost layer calculation works" + print *, "✓ Domain creation works" + print *, "✓ Solution arrays work" + print *, "✓ Initialization works" + print *, "✓ Field update works" + print *, "✓ Reset works" + print *, "" + print *, "Ready for next step: Implementing Physics modules" + +end program test_domain_solution \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_factory_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_factory_simple.f90 new file mode 100644 index 000000000..db65da7cf --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_factory_simple.f90 @@ -0,0 +1,58 @@ +! tests/test_factory_simple.f90 (修复版) +program test_factory_simple + use base_modules, only: wp ! ← 添加这行 + use config_module, only: cfd_config, config_print + use mesh_module, only: mesh_type + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(eno_reconstructor) :: eno + type(weno3_reconstructor) :: weno3 + type(rusanov_flux) :: rusanov + + print *, "=== Factory Pattern Simple Test ===" + print *, "" + + ! Test 1: Basic systems + print *, "1. Testing basic systems..." + print *, "-----------------------------" + call config_print(config) + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 2: Creating reconstructors + print *, "2. Testing reconstructors..." + print *, "------------------------------" + + ! 创建并测试ENO重构器 + print *, "Creating ENO reconstructor..." + eno = eno_reconstructor() ! 使用构造函数 + call eno%info() ! 必须调用info方法 + + print *, "" + print *, "Creating WENO3 reconstructor..." + weno3 = weno3_reconstructor() ! 使用构造函数 + call weno3%info() ! 必须调用info方法 + print *, "" + + ! Test 3: Creating flux calculator + print *, "3. Testing flux calculator..." + print *, "-------------------------------" + + print *, "Creating Rusanov flux calculator..." + rusanov = rusanov_flux() ! 使用构造函数 + call rusanov%info() ! 必须调用info方法 + print *, "" + + print *, "=== Factory pattern simple test completed successfully ===" + +end program test_factory_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_infrastructure.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_infrastructure.f90 new file mode 100644 index 000000000..22fa92d1a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_infrastructure.f90 @@ -0,0 +1,56 @@ +! tests/test_infrastructure.f90 (原test_basic_only.f90) +program test_infrastructure + use base_modules, only: wp + use config_module, only: cfd_config, config_print + use mesh_module, only: mesh_type + use registry_module, only: registry_init, registry_cleanup, & + register_component_simple, list_components + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "=== 基础设施测试 ===" + print *, "" + + ! 测试1: 配置 + print *, "1. 测试配置模块..." + print *, "-------------------" + call config_print(config) + print *, "" + + ! 测试2: 网格 + print *, "2. 测试网格模块..." + print *, "------------------" + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=5) + print *, "网格初始化:" + print *, " 单元数: ", mesh%ncells + print *, " 节点数: ", mesh%nnodes + print *, " 网格间距: ", mesh%dx + print *, "" + + ! 测试3: 注册系统 + print *, "3. 测试注册系统..." + print *, "------------------" + + call registry_init() + + ! 注册组件(使用简化版本) + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("flux", "rusanov") + + ! 列出组件 + call list_components() + print *, "" + + ! 清理 + call registry_cleanup() + + print *, "=== 基础设施测试通过 ===" + print *, "✓ 配置模块工作正常" + print *, "✓ 网格模块工作正常" + print *, "✓ 注册系统工作正常" + +end program test_infrastructure \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_initial_condition.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_initial_condition.f90 new file mode 100644 index 000000000..1c6064b60 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_initial_condition.f90 @@ -0,0 +1,96 @@ +! tests/test_initial_condition.f90 +program test_initial_condition + use base_modules, only: wp + use config_module, only: cfd_config + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create + use ic_factory_module, only: create_initial_condition + use ic_base_module, only: initial_condition + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + class(initial_condition), allocatable :: ic + integer :: i + + print *, "=== 初始条件模块测试 ===" + print *, "" + + ! 创建配置和网格 + config%verbose = .false. + config%recon_scheme = "eno" + config%spatial_order = 3 + + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=10) + domain = domain_create(config, mesh) + solution = solution_create(domain) + + ! 测试1: 阶跃函数初始条件 + print *, "1. 测试阶跃函数初始条件..." + call create_initial_condition("step", ic) + + if (allocated(ic)) then + call ic%apply(solution) + print *, " 成功应用阶跃函数初始条件" + print *, " 解范围: ", minval(solution%u), " 到 ", maxval(solution%u) + + ! 检查结果 + if (abs(maxval(solution%u) - 2.0_wp) < 1e-10_wp .and. & + abs(minval(solution%u) - 1.0_wp) < 1e-10_wp) then + print *, " ✓ 阶跃函数测试通过" + else + print *, " ✗ 阶跃函数测试失败" + end if + end if + + deallocate(ic) + print *, "" + + ! 测试2: 正弦波初始条件 + print *, "2. 测试正弦波初始条件..." + call create_initial_condition("sin", ic) + + if (allocated(ic)) then + call solution%reset() + call ic%apply(solution) + print *, " 成功应用正弦波初始条件" + print *, " 解范围: ", minval(solution%u), " 到 ", maxval(solution%u) + print *, " ✓ 正弦波测试通过" + end if + + deallocate(ic) + print *, "" + + ! 测试3: 高斯脉冲初始条件 + print *, "3. 测试高斯脉冲初始条件..." + call create_initial_condition("gaussian", ic) + + if (allocated(ic)) then + call solution%reset() + call ic%apply(solution) + print *, " 成功应用高斯脉冲初始条件" + print *, " 解范围: ", minval(solution%u), " 到 ", maxval(solution%u) + print *, " ✓ 高斯脉冲测试通过" + end if + + deallocate(ic) + print *, "" + + ! 测试4: 错误处理 + print *, "4. 测试错误处理..." + call create_initial_condition("unknown", ic) + + if (allocated(ic)) then + print *, " ✓ 错误处理测试通过(使用了默认初始条件)" + else + print *, " ✗ 错误处理测试失败" + end if + + print *, "" + print *, "=== 初始条件模块测试完成 ===" + +end program test_initial_condition \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_physics.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_physics.f90 new file mode 100644 index 000000000..784a3ffc4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_physics.f90 @@ -0,0 +1,20 @@ +! tests/test_physics.f90 +program test_physics + use base_modules, only: wp, ip + implicit none + + print *, "=== 物理模块测试(简化版)===" + print *, "" + + print *, "1. 测试基本物理概念..." + print *, " ✓ 物理模块占位符" + print *, "" + + print *, "2. 测试阶跃函数逻辑..." + print *, " ✓ 阶跃函数逻辑占位符" + print *, "" + + print *, "=== 物理模块测试完成 ===" + print *, "注意: 这是简化测试,物理模块尚未完全集成" + +end program test_physics \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_physics_solver.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_physics_solver.f90 new file mode 100644 index 000000000..0c83641f8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_physics_solver.f90 @@ -0,0 +1,85 @@ +! tests/test_physics_solver.f90 (简化修正版) +program test_physics_solver + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction + use mesh_module, only: mesh_type + use physics_solver_module, only: physics_solver, SOLVER_COMPLETED + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(physics_solver) :: psolver + real(wp) :: final_time + integer :: state + + print *, "=== Physics Solver Test (简化版) ===" + print *, "" + + ! 测试1: 创建物理求解器 + print *, "1. Creating physics solver..." + print *, "-----------------------------" + + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%dt = 0.01_wp + config%enable_physics = .true. + + print *, "Configuration:" + print *, " Scheme: ", trim(config%recon_scheme) + print *, " dt: ", config%dt + print *, " Physics enabled: ", config%enable_physics + + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=10) + + ! 设置求解器配置和网格 + psolver%config = config + psolver%mesh = mesh + + print *, " Solver created successfully" + print *, "" + + ! 测试2: 初始化 + print *, "2. Initializing physics solver..." + print *, "---------------------------------" + + call psolver%initialize() + state = psolver%get_state() + + print *, " State after initialization: ", state + print *, " Expected: initialized (1)" + print *, "" + + ! 测试3: 运行一小段时间 + print *, "3. Running physics solver (short time)..." + print *, "------------------------------------------" + + call psolver%run_to_time(0.02_wp) + state = psolver%get_state() + + print *, " State after run: ", state + print *, " Expected: completed (3)" + print *, " Current time: ", psolver%current_time + print *, " Current step: ", psolver%current_step + print *, "" + + ! 测试4: 清理 + print *, "4. Testing cleanup..." + print *, "----------------------" + + call psolver%cleanup() + state = psolver%get_state() + + print *, " State after cleanup: ", state + print *, " Expected: uninitialized (0)" + print *, "" + + ! 结果验证 + print *, "=== Test Summary ===" + if (state == 0) then + print *, "✓ All basic tests passed" + else + print *, "✗ Some tests failed" + end if + +end program test_physics_solver \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_physics_solver_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_physics_solver_simple.f90 new file mode 100644 index 000000000..13312efde --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_physics_solver_simple.f90 @@ -0,0 +1,161 @@ +! tests/test_physics_solver_simple.f90 +program test_physics_solver_simple + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction + use mesh_module, only: mesh_type + use physics_solver_module, only: physics_solver, SOLVER_COMPLETED + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(physics_solver) :: solver + real(wp) :: final_time, final_step + integer :: state + + print *, "=========================================" + print *, " 简单物理求解器测试" + print *, "=========================================" + print *, "" + + ! 步骤1: 配置 + print *, "[步骤1] 配置求解器..." + print *, "---------------------" + + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%dt = 0.01_wp + config%final_time = 0.1_wp + config%wave_speed = 1.0_wp + config%ic_type = "step" + config%boundary_type = "periodic" + config%equation_type = "linear_advection" + config%problem_type = "linear_advection" + config%enable_physics = .true. + config%domain_length = 1.0_wp + + print *, "配置参数:" + print *, " 重构格式: ", trim(config%recon_scheme) + print *, " 时间步长: ", config%dt + print *, " 最终时间: ", config%final_time + print *, " 波速: ", config%wave_speed + print *, "" + + ! 步骤2: 创建网格 + print *, "[步骤2] 创建网格..." + print *, "-------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + + print *, "网格信息:" + print *, " 单元数: ", mesh%ncells + print *, " 节点数: ", mesh%nnodes + print *, " 网格间距: ", mesh%dx + print *, "" + + ! 步骤3: 创建求解器 + print *, "[步骤3] 创建求解器..." + print *, "---------------------" + + solver = physics_solver(config, mesh) + + print *, "求解器创建成功" + print *, " 初始状态: ", solver%get_state() + print *, "" + + ! 步骤4: 初始化 + print *, "[步骤4] 初始化求解器..." + print *, "-----------------------" + + call solver%initialize() + + state = solver%get_state() + print *, "初始化完成" + print *, " 状态: ", state + print *, " 当前时间: ", solver%current_time + print *, " 当前步数: ", solver%current_step + print *, "" + + ! 步骤5: 运行求解器 + print *, "[步骤5] 运行求解器..." + print *, "---------------------" + + call solver%run_to_time(config%final_time) + + state = solver%get_state() + print *, "运行完成" + print *, " 状态: ", state + print *, " 最终时间: ", solver%current_time + print *, " 总步数: ", solver%current_step + print *, "" + + ! 步骤6: 保存结果 + print *, "[步骤6] 保存结果..." + print *, "-------------------" + + final_time = solver%current_time + final_step = real(solver%current_step, wp) + state = solver%get_state() + + print *, "保存的结果:" + print *, " 状态: ", state + print *, " 时间: ", final_time + print *, " 步数: ", final_step + print *, "" + + ! 步骤7: 清理求解器 + print *, "[步骤7] 清理求解器..." + print *, "---------------------" + + call solver%cleanup() + + print *, "清理后状态:" + print *, " 状态: ", solver%get_state() + print *, " 时间: ", solver%current_time + print *, " 步数: ", solver%current_step + print *, "" + + ! 步骤8: 验证结果 + print *, "[步骤8] 验证结果..." + print *, "-------------------" + + print *, "验证标准:" + print *, " 1. 运行后状态应为 COMPLETED (", SOLVER_COMPLETED, ")" + print *, " 2. 最终时间应接近 ", config%final_time + print *, " 3. 步数应大于 0" + print *, "" + + if (state == SOLVER_COMPLETED) then + print *, "✓ 状态验证通过: COMPLETED" + else + print *, "✗ 状态验证失败: 期望 ", SOLVER_COMPLETED, ", 实际 ", state + end if + + if (abs(final_time - config%final_time) < 1e-5_wp) then + print *, "✓ 时间验证通过: ", final_time, " ≈ ", config%final_time + else + print *, "✗ 时间验证失败: ", final_time, " ≠ ", config%final_time + end if + + if (final_step > 0) then + print *, "✓ 步数验证通过: ", final_step, " > 0" + else + print *, "✗ 步数验证失败: ", final_step, " ≤ 0" + end if + + print *, "" + + ! 最终判断 + if (state == SOLVER_COMPLETED .and. & + abs(final_time - config%final_time) < 1e-5_wp .and. & + final_step > 0) then + print *, "=========================================" + print *, " 所有测试通过! ✓" + print *, "=========================================" + else + print *, "=========================================" + print *, " 测试失败 ✗" + print *, "=========================================" + end if + +end program test_physics_solver_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_registry.f90 new file mode 100644 index 000000000..e82651ffb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_registry.f90 @@ -0,0 +1,87 @@ +! tests/test_registry.f90 (原test_minimal_simple.f90) +program test_registry + use base_modules, only: wp + use registry_module + use config_module + use mesh_module + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== 注册系统功能测试 ===" + print *, "" + + ! 测试1: 配置系统 + print *, "1. 测试配置系统" + print *, "--------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! 测试2: 网格系统 + print *, "2. 测试网格系统" + print *, "--------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! 测试3: 注册系统 + print *, "3. 测试注册系统" + print *, "--------------" + + call registry_init() + + ! 注册组件 + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call list_components() + print *, "注册表大小: ", registry_get_size() + print *, "" + + ! 测试组件查找 + print *, "4. 测试组件查找" + print *, "--------------" + + if (has_component_simple("reconstructor", "eno")) then + print *, "找到: reconstructor.eno" + else + print *, "未找到: reconstructor.eno" + end if + + if (has_component_simple("reconstructor", "unknown")) then + print *, "找到: reconstructor.unknown" + else + print *, "未找到: reconstructor.unknown" + end if + print *, "" + + ! 测试获取可用组件 + print *, "5. 测试注册系统功能" + print *, "------------------" + print *, "注册表已初始化: ", registry_is_initialized() + print *, "组件数量: ", registry_get_size() + print *, "" + + ! 清理 + call registry_cleanup() + + print *, "=== 注册系统测试完成 ===" + +end program test_registry \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_simple_link.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_simple_link.f90 new file mode 100644 index 000000000..71cc614e8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_simple_link.f90 @@ -0,0 +1,78 @@ +! tests/test_simple_link.f90 +program test_simple_link + use base_modules, only: wp ! ← 添加这行 + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call registry_init() + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call list_components() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component_simple("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component_simple("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Cleanup + call registry_cleanup() + + print *, "=== Minimal test completed successfully ===" + +end program test_simple_link \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_solver_base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_solver_base.f90 new file mode 100644 index 000000000..6cfe47e41 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_solver_base.f90 @@ -0,0 +1,99 @@ +! tests/test_solver_base.f90 (修复版) +program test_solver_base + ! 所有 USE 语句必须在程序开始处 + use base_modules, only: wp + use config_module, only: cfd_config, config_print, config_with_reconstruction + use mesh_module, only: mesh_type + use solver_base_module, only: solver_base, SOLVER_UNINITIALIZED, & + SOLVER_INITIALIZED, SOLVER_COMPLETED, SOLVER_ERROR + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(solver_base) :: solver + integer :: state + + print *, "=== Solver Base Test ===" + print *, "" + + ! 测试1: 创建求解器 + print *, "1. Creating solver..." + print *, "----------------------" + + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%dt = 0.01_wp + + call config_print(config) + + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=10) + + solver = solver_base(config, mesh) + call solver%print_info() + print *, "" + + ! 测试2: 初始化 + print *, "2. Initializing solver..." + print *, "-------------------------" + + call solver%initialize() + state = solver%get_state() + print *, "State after initialization: ", state + print *, "Expected: ", SOLVER_INITIALIZED + print *, "Match? ", state == SOLVER_INITIALIZED + print *, "Error message: '", trim(solver%get_error()), "'" + print *, "" + + ! 测试3: 运行求解器 + print *, "3. Running solver..." + print *, "--------------------" + + call solver%run_to_time(0.05_wp) + state = solver%get_state() + print *, "State after run: ", state + print *, "Expected: ", SOLVER_COMPLETED + print *, "Match? ", state == SOLVER_COMPLETED + print *, "Current time: ", solver%current_time + print *, "Current step: ", solver%current_step + print *, "" + + ! 测试4: 再次运行(从已完成状态) + print *, "4. Running again from completed state..." + print *, "----------------------------------------" + + ! 需要先清理才能重新运行 + call solver%cleanup() + call solver%initialize() + call solver%run_to_time(0.1_wp) + + call solver%print_info() + print *, "" + + ! 测试5: 错误处理 + print *, "5. Testing error states..." + print *, "--------------------------" + + ! 创建一个未初始化的求解器 + call solver%cleanup() + state = solver%get_state() + print *, "Uninitialized state: ", state + print *, "Expected: ", SOLVER_UNINITIALIZED + print *, "Match? ", state == SOLVER_UNINITIALIZED + + ! 尝试运行未初始化的求解器 + call solver%run_to_time(0.01_wp) + state = solver%get_state() + print *, "State after error: ", state + print *, "Expected: ", SOLVER_ERROR + print *, "Match? ", state == SOLVER_ERROR + print *, "Error message: '", trim(solver%get_error()), "'" + print *, "" + + print *, "=== Solver Base Test Complete ===" + print *, "✓ Solver base class works" + print *, "✓ State management works" + print *, "✓ Time stepping framework works" + print *, "✓ Error handling works" + +end program test_solver_base \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_solver_framework.f90 b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_solver_framework.f90 new file mode 100644 index 000000000..6754323d0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03f/tests/test_solver_framework.f90 @@ -0,0 +1,91 @@ +! tests/test_solver_framework.f90 +program test_solver_framework + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use mesh_module, only: mesh_type + use solver_module, only: cfd_solver, solver_create, solver_run, solver_cleanup + use solver_module, only: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, & + SOLVER_COMPLETED, SOLVER_ERROR + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(cfd_solver) :: solver + + print *, "=== 求解器框架测试 ===" + print *, "" + + ! 测试1: 基本创建 + print *, "1. 测试求解器创建..." + print *, "----------------------" + + ! 创建配置 + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.0_real64 + config%dt = 0.01_real64 + + call config_print(config) + print *, "" + + ! 创建网格 + call mesh%init(xmin=0.0_real64, xmax=2.0_real64, ncells=20) + call mesh%print_info() + print *, "" + + ! 创建求解器 + solver = solver_create(config, mesh) + print *, "✓ 求解器创建成功" + print *, " 状态: ", solver%get_state() + print *, "" + + ! 测试2: 求解器初始化 + print *, "2. 测试求解器初始化..." + print *, "------------------------" + + call solver%initialize() + print *, "✓ 求解器初始化完成" + print *, " 状态: ", solver%get_state() + print *, " 错误信息: '", trim(solver%get_error()), "'" + print *, "" + + ! 测试3: 简单运行 + print *, "3. 测试求解器运行..." + print *, "----------------------" + + call solver_run(solver, 0.05_real64) ! 运行到0.05秒 + print *, "✓ 求解器运行完成" + print *, " 最终状态: ", solver%get_state() + print *, "" + + ! 测试4: 清理 + print *, "4. 测试求解器清理..." + print *, "----------------------" + + call solver_cleanup(solver) + print *, "✓ 求解器清理完成" + print *, " 状态: ", solver%get_state() + print *, "" + + ! 测试5: 错误处理 + print *, "5. 测试错误处理..." + print *, "-------------------" + + ! 尝试重复初始化 + call solver%initialize() + print *, " 重复初始化状态: ", solver%get_state() + print *, " 错误信息: '", trim(solver%get_error()), "'" + + call solver_cleanup(solver) + print *, "" + + print *, "=== 框架测试总结 ===" + print *, "✓ 求解器创建/初始化/运行/清理流程验证完成" + print *, "✓ 状态管理正常工作" + print *, "✓ 错误处理机制就绪" + print *, "" + print *, "下一步: 添加实际数值计算功能" + +end program test_solver_framework \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03g/CMakeLists.txt new file mode 100644 index 000000000..55859dc2a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 4.2.1) +project(FortranCFD VERSION 1.0.0 LANGUAGES Fortran) + +set(CMAKE_Fortran_STANDARD 2008) +set(CMAKE_Fortran_STANDARD_REQUIRED ON) + +message(STATUS "Project: ${PROJECT_NAME} Version: ${PROJECT_VERSION}") +message(STATUS "Compiler: ${CMAKE_Fortran_COMPILER}") +message(STATUS "Compiler ID: ${CMAKE_Fortran_COMPILER_ID}") +message(STATUS "Compiler Version: ${CMAKE_Fortran_COMPILER_VERSION}") + + +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 共享库(.so/.dylib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +add_subdirectory(src) +add_subdirectory(tests) +add_subdirectory(examples) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/README.md b/example/1d-linear-convection/weno3/fortran/registry/03g/README.md new file mode 100644 index 000000000..c4889757b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/README.md @@ -0,0 +1,221 @@ +# Fortran CFD Project + +基于工厂模式和注册系统的CFD求解器框架。 + +## 项目结构 + +``` +fortran_cfd/ +├── CMakeLists.txt # 根CMake配置 +├── scripts/ # 构建脚本 +│ ├── build.py # Python构建脚本(推荐) +│ ├── build.bat # Batch包装脚本 +│ ├── build.ps1 # PowerShell包装脚本 +│ └── requirements.txt # Python依赖 +├── src/ # 源代码 +│ ├── CMakeLists.txt +│ ├── core/ # 核心模块(注册系统、工厂接口) +│ ├── infrastructure/ # 基础设施(配置、网格) +│ └── numerics/ # 数值方法 +├── tests/ # 测试 +│ ├── CMakeLists.txt +│ ├── test_minimal_simple.f90 # 简单测试 +│ └── test_factory.f90 # 工厂模式测试 +└── README.md # 项目说明(本文件) +``` + +## 快速开始 + +### 使用Python脚本(推荐) + +```bash +# 进入脚本目录 +cd scripts + +# 基本构建 +python build.py + +# 清理并构建 +python build.py --clean + +# 发布构建 +python build.py --build-type Release --clean + +# 并行构建(使用所有核心) +python build.py -j + +# 不运行测试 +python build.py --no-tests + +# 查看帮助 +python build.py --help +``` + +### 使用批处理脚本 + +```bash +cd scripts +.\build.bat +``` + +### 使用PowerShell脚本 + +```powershell +cd scripts +.\build.ps1 +``` + +## 手动构建 + +```bash +# 设置Intel环境 +cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && powershell' + +# 配置项目 +mkdir build +cd build +cmake -G "Visual Studio 17 2022" -A x64 -T fortran=ifx .. + +# 构建项目 +cmake --build . --config Debug + +# 运行测试 +Debug\test_simple.exe +Debug\test_factory.exe +``` + +## 功能特性 + +✅ 组件注册系统 +✅ 工厂模式 +✅ ENO/WENO重构器 +✅ Rusanov通量计算器 +✅ 可扩展架构 +✅ 自动化测试 + +## 依赖 + +- Intel oneAPI(包含ifx编译器) +- CMake 3.12+ +- Python 3.6+(仅用于构建脚本) + +## 源代码文件 + +### 核心模块 +- `src/core/registry.f90` - 组件注册系统 +- `src/core/factory_interfaces.f90` - 工厂接口 + +### 基础设施 +- `src/infrastructure/config.f90` - 配置管理 +- `src/infrastructure/mesh.f90` - 网格生成 + +### 数值方法 +- `src/numerics/reconstructor/base.f90` - 重构器基类 +- `src/numerics/reconstructor/eno.f90` - ENO重构器 +- `src/numerics/reconstructor/weno3.f90` - WENO-3重构器 +- `src/numerics/flux/base.f90` - 通量计算器基类 +- `src/numerics/flux/rusanov.f90` - Rusanov通量 + +### 测试 +- `tests/test_minimal_simple.f90` - 简单功能测试 +- `tests/test_factory.f90` - 工厂模式测试 + +## 构建脚本 + +### Python脚本 (build.py) +主构建脚本,支持: +- 自动设置Intel oneAPI环境 +- 参数化构建选项 +- 并行编译 +- 自动化测试 +- 彩色输出 + +### Batch脚本 (build.bat) +Windows批处理包装器,调用Python脚本。 + +### PowerShell脚本 (build.ps1) +PowerShell包装器,调用Python脚本。 + +## 示例输出 + +成功构建后,会看到类似输出: + +``` +============================================================ + Fortran CFD Project Builder +============================================================ + +[1/4] Setting up Intel Fortran compiler... +✓ Intel oneAPI environment configured + +[2/4] Preparing build directory... +✓ Cleaned build directory + +[3/4] Configuring project... + $ cmake -G Visual Studio 17 2022 -A x64 -T fortran=ifx -DCMAKE_BUILD_TYPE=Debug .. +✓ CMake configuration successful + +[4/4] Building project... + $ cmake --build . --config Debug +✓ Build completed in 12.3 seconds + +============================================================ + Running Tests +============================================================ + +[TEST] Simple functionality test... +✓ Simple test passed + +[TEST] Factory pattern test... +✓ Factory test passed + +============================================================ + Build Summary +============================================================ +✓ Build directory: D:\path\to\build +✓ Build type: Debug +✓ Total time: 15.2s +``` + +## 开发指南 + +### 添加新组件 + +1. 在相应模块中创建Fortran源文件 +2. 实现工厂函数 +3. 使用`register_component_with_factory`注册组件 +4. 添加测试 + +### 扩展架构 + +项目采用模块化设计: +1. **注册系统** - 管理所有可用的组件 +2. **工厂模式** - 动态创建组件实例 +3. **抽象接口** - 确保组件兼容性 +4. **配置驱动** - 通过配置文件控制行为 + +## 故障排除 + +### 常见问题 + +1. **Intel环境未找到** + - 检查Intel oneAPI安装路径 + - 手动运行`setvars.bat` + +2. **CMake配置失败** + - 确保使用正确的生成器:`Visual Studio 17 2022` + - 检查Fortran编译器是否可用 + +3. **构建失败** + - 检查依赖项是否完整 + - 查看详细的错误信息 + +### 调试建议 + +- 使用`--verbose`参数获取详细输出 +- 检查`build/CMakeCache.txt`了解配置详情 +- 查看编译器错误日志 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/examples/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03g/examples/CMakeLists.txt new file mode 100644 index 000000000..cff652147 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/examples/CMakeLists.txt @@ -0,0 +1,39 @@ +# examples/CMakeLists.txt (修正版) +message(STATUS "配置示例程序...") + +# 主示例程序:ENO/WENO对比(当前版本) +add_executable(run_eno_weno + run_eno_weno.f90 +) + +target_link_libraries(run_eno_weno + PRIVATE + solver + infrastructure + core + physics + manager + results + boundary + initial_condition +) + +# 集成版本示例 +add_executable(run_eno_weno_integrated + run_eno_weno_integrated.f90 +) + +target_link_libraries(run_eno_weno_integrated + PRIVATE + solver_integrated # 主要模块 + infrastructure + base + boundary # 集成求解器需要的边界条件 + initial_condition # 集成求解器需要的初始条件 +) + +install(TARGETS run_eno_weno run_eno_weno_integrated + RUNTIME DESTINATION bin/examples +) + +message(STATUS "示例程序配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/examples/run_eno_weno.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/examples/run_eno_weno.f90 new file mode 100644 index 000000000..ffa6ff9d0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/examples/run_eno_weno.f90 @@ -0,0 +1,327 @@ +! examples/run_eno_weno.f90 (修正版) +program run_eno_weno + ! Example program: ENO/WENO comparison analysis + + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction, config_print + use mesh_module, only: mesh_type + use registry_module, only: registry_init, registry_cleanup, initialize_default_components + use physics_solver_module, only: physics_solver, SOLVER_COMPLETED + use results_module, only: results_saver, save_results ! 修改:只导入需要的内容 + + implicit none + + type(cfd_config) :: config_eno3, config_weno3, config_weno5 + type(mesh_type) :: mesh + type(physics_solver) :: solver_eno3, solver_weno3, solver_weno5 + + ! 结果保存器 + type(results_saver) :: saver ! 直接声明类型 + + ! Variables to save results + real(wp) :: time_eno3, time_weno3, time_weno5 + integer :: steps_eno3, steps_weno3, steps_weno5 + integer :: state_eno3, state_weno3, state_weno5 + logical :: all_success + + ! Debug: print start marker + print *, "==========================================" + print *, "START: ENO/WENO Comparison Analysis" + print *, "==========================================" + print *, "" + + ! Step 0: Initialize system + print *, "[STEP 0] Initializing system..." + print *, "--------------------------------" + + call registry_init(verbose=.true.) + print *, "Registry initialized" + + call initialize_default_components() + print *, "Default components registered" + print *, "" + + ! Step 1: Create mesh + print *, "[STEP 1] Creating computational mesh..." + print *, "----------------------------------------" + + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=40) + call mesh%print_info() + print *, "" + + ! 创建结果保存器 - 使用构造函数而不是赋值 + saver = results_saver("results", .true.) ! 直接使用构造函数 + + ! ========== ENO3 Solver ========== + print *, "[STEP 2] Configuring and running ENO3 solver..." + print *, "-----------------------------------------------" + + ! Configure ENO3 + config_eno3%verbose = .true. + config_eno3%ic_type = "step" + config_eno3%wave_speed = 1.0_wp + config_eno3%final_time = 0.625_wp + config_eno3%dt = 0.0025_wp + config_eno3%rk_order = 2 + config_eno3%boundary_type = "periodic" + config_eno3%equation_type = "linear_advection" + config_eno3%problem_type = "linear_advection" + config_eno3%enable_physics = .true. + config_eno3%domain_length = 2.0_wp + + call config_with_reconstruction(config_eno3, "eno", 3) + + print *, "ENO3 configuration:" + call config_print(config_eno3) + print *, "" + + ! Create and run ENO3 solver + print *, "Creating ENO3 solver instance..." + ! 替换这三行: + ! call physics_solver_constructor(solver_eno3, config_eno3, mesh) + + ! 改为: + solver_eno3 = physics_solver(config_eno3, mesh) + + print *, "Initializing ENO3 solver..." + call solver_eno3%initialize() + + print *, "Running ENO3 solver..." + call solver_eno3%run_to_time(config_eno3%final_time) + + ! Immediately save ENO3 results + time_eno3 = solver_eno3%current_time + steps_eno3 = solver_eno3%current_step + state_eno3 = solver_eno3%get_state() + + print *, "ENO3 solver completed" + print *, " Final time: ", time_eno3 + print *, " Total steps: ", steps_eno3 + print *, " State: ", state_eno3 + print *, "" + + ! 保存ENO3结果到文件 + call save_results(saver, "ENO3", & + solver_eno3%config, solver_eno3%mesh, solver_eno3%domain, & + solver_eno3%solution, & + solver_eno3%current_time, solver_eno3%current_step, solver_eno3%get_state()) + + ! ========== WENO3 Solver ========== + print *, "[STEP 3] Configuring and running WENO3 solver..." + print *, "------------------------------------------------" + + ! Configure WENO3 + config_weno3%verbose = .true. + config_weno3%ic_type = "step" + config_weno3%wave_speed = 1.0_wp + config_weno3%final_time = 0.625_wp + config_weno3%dt = 0.0025_wp + config_weno3%rk_order = 2 + config_weno3%boundary_type = "periodic" + config_weno3%equation_type = "linear_advection" + config_weno3%problem_type = "linear_advection" + config_weno3%enable_physics = .true. + config_weno3%domain_length = 2.0_wp + + call config_with_reconstruction(config_weno3, "weno3", 3) + + print *, "WENO3 configuration:" + call config_print(config_weno3) + print *, "" + + ! Create and run WENO3 solver + print *, "Creating WENO3 solver instance..." + call physics_solver_constructor(solver_weno3, config_weno3, mesh) + + print *, "Initializing WENO3 solver..." + call solver_weno3%initialize() + + print *, "Running WENO3 solver..." + call solver_weno3%run_to_time(config_weno3%final_time) + + ! Immediately save WENO3 results + time_weno3 = solver_weno3%current_time + steps_weno3 = solver_weno3%current_step + state_weno3 = solver_weno3%get_state() + + print *, "WENO3 solver completed" + print *, " Final time: ", time_weno3 + print *, " Total steps: ", steps_weno3 + print *, " State: ", state_weno3 + print *, "" + + ! 保存WENO3结果到文件 + call save_results(saver, "WENO3", & + solver_weno3%config, solver_weno3%mesh, solver_weno3%domain, & + solver_weno3%solution, time_weno3, steps_weno3, state_weno3) + + ! ========== WENO5 Solver ========== + print *, "[STEP 4] Configuring and running WENO5 solver..." + print *, "------------------------------------------------" + + ! Configure WENO5 + config_weno5%verbose = .true. + config_weno5%ic_type = "step" + config_weno5%wave_speed = 1.0_wp + config_weno5%final_time = 0.625_wp + config_weno5%dt = 0.0025_wp + config_weno5%rk_order = 2 + config_weno5%boundary_type = "periodic" + config_weno5%equation_type = "linear_advection" + config_weno5%problem_type = "linear_advection" + config_weno5%enable_physics = .true. + config_weno5%domain_length = 2.0_wp + + call config_with_reconstruction(config_weno5, "weno", 5) + + print *, "WENO5 configuration:" + call config_print(config_weno5) + print *, "" + + ! Create and run WENO5 solver + print *, "Creating WENO5 solver instance..." + call physics_solver_constructor(solver_weno5, config_weno5, mesh) + + print *, "Initializing WENO5 solver..." + call solver_weno5%initialize() + + print *, "Running WENO5 solver..." + call solver_weno5%run_to_time(config_weno5%final_time) + + ! Immediately save WENO5 results + time_weno5 = solver_weno5%current_time + steps_weno5 = solver_weno5%current_step + state_weno5 = solver_weno5%get_state() + + print *, "WENO5 solver completed" + print *, " Final time: ", time_weno5 + print *, " Total steps: ", steps_weno5 + print *, " State: ", state_weno5 + print *, "" + + ! 保存WENO5结果到文件 + call save_results(saver, "WENO5", & + solver_weno5%config, solver_weno5%mesh, solver_weno5%domain, & + solver_weno5%solution, time_weno5, steps_weno5, state_weno5) + + ! ========== Results Summary ========== + print *, "==========================================" + print *, " RESULTS SUMMARY" + print *, "==========================================" + print *, "" + + print *, "Solver Performance Comparison:" + print *, "------------------------------" + + print *, "ENO3:" + print *, " Final time: ", time_eno3 + print *, " Total steps: ", steps_eno3 + print *, " State: ", state_eno3 + print *, " Results saved to: results_ENO3_40.dat" + print *, "" + + print *, "WENO3:" + print *, " Final time: ", time_weno3 + print *, " Total steps: ", steps_weno3 + print *, " State: ", state_weno3 + print *, " Results saved to: results_WENO3_40.dat" + print *, "" + + print *, "WENO5:" + print *, " Final time: ", time_weno5 + print *, " Total steps: ", steps_weno5 + print *, " State: ", state_weno5 + print *, " Results saved to: results_WENO5_40.dat" + print *, "" + + ! ========== Final Judgment ========== + print *, "==========================================" + print *, " FINAL JUDGMENT" + print *, "==========================================" + print *, "" + + all_success = (state_eno3 == SOLVER_COMPLETED) .and. & + (state_weno3 == SOLVER_COMPLETED) .and. & + (state_weno5 == SOLVER_COMPLETED) + + if (all_success) then + print *, "✓ ALL SOLVERS SUCCESSFULLY COMPLETED!" + print *, "" + print *, "Parameter Summary:" + print *, " Grid cells: ", mesh%ncells + print *, " Time step: ", config_eno3%dt + print *, " Final time: ", config_eno3%final_time + print *, " Wave speed: ", config_eno3%wave_speed + print *, " IC type: ", trim(config_eno3%ic_type) + print *, "" + print *, "Performance Comparison:" + print *, " ENO3: ", steps_eno3, " steps" + print *, " WENO3: ", steps_weno3, " steps" + print *, " WENO5: ", steps_weno5, " steps" + print *, "" + print *, "To visualize results:" + print *, " python ../python/plot_results.py --auto" + else + print *, "✗ SOME SOLVERS FAILED" + print *, "" + print *, "Failure Analysis:" + if (state_eno3 /= SOLVER_COMPLETED) then + print *, " • ENO3 failed with state: ", state_eno3 + end if + if (state_weno3 /= SOLVER_COMPLETED) then + print *, " • WENO3 failed with state: ", state_weno3 + end if + if (state_weno5 /= SOLVER_COMPLETED) then + print *, " • WENO5 failed with state: ", state_weno5 + end if + end if + + print *, "" + print *, "==========================================" + print *, " ANALYSIS COMPLETE" + print *, "==========================================" + + ! ========== Cleanup ========== + print *, "" + print *, "[STEP 5] Cleaning up system..." + print *, "--------------------------------" + + call solver_eno3%cleanup() + call solver_weno3%cleanup() + call solver_weno5%cleanup() + call registry_cleanup() + + print *, "All solvers cleaned up" + print *, "Registry cleaned up" + print *, "" + + ! ========== Wait for user input before exit ========== + print *, "==========================================" + print *, "Press ENTER to exit..." + print *, "==========================================" + + ! Wait for user input (uncomment if needed) + ! read(*,*) + + ! Alternative: add a small delay + print *, "Program will exit in 3 seconds..." + call sleep(3) + + print *, "" + print *, "==========================================" + print *, " PROGRAM END" + print *, "==========================================" + +contains + + ! 辅助函数:显式创建physics_solver(如果原始代码使用函数而不是子程序) + subroutine physics_solver_constructor(solver, config, mesh) + type(physics_solver), intent(out) :: solver + type(cfd_config), intent(in) :: config + type(mesh_type), intent(in) :: mesh + + ! 使用赋值构造函数(如果physics_solver_module中有相应的接口) + solver = physics_solver(config, mesh) + end subroutine physics_solver_constructor + +end program run_eno_weno \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/examples/run_eno_weno_integrated.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/examples/run_eno_weno_integrated.f90 new file mode 100644 index 000000000..776293051 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/examples/run_eno_weno_integrated.f90 @@ -0,0 +1,176 @@ +! examples/run_eno_weno_integrated.f90 (完整修复版) +program run_eno_weno_integrated + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction, config_print + use mesh_module, only: mesh_type + use solver_integrated_module, only: integrated_solver, SOLVER_INITIALIZED, SOLVER_COMPLETED + + implicit none + + type(cfd_config) :: config_eno3, config_weno3, config_weno5 + type(mesh_type) :: mesh + type(integrated_solver) :: solver_eno3, solver_weno3, solver_weno5 + + ! 结果变量 + real(wp) :: time_eno3, time_weno3, time_weno5 + integer :: steps_eno3, steps_weno3, steps_weno5 + integer :: state_eno3, state_weno3, state_weno5 + logical :: all_success + + ! 定义常量(避免导入冲突) + integer, parameter :: SOLVER_READY = 0 + integer, parameter :: SOLVER_RUNNING = 2 + integer, parameter :: SOLVER_ERROR = -1 + + print *, "==========================================" + print *, "ENO/WENO 对比分析 (集成版本)" + print *, "==========================================" + print *, "" + + ! 步骤1: 创建网格 + print *, "[STEP 1] 创建计算网格..." + print *, "-----------------------------------" + + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=40) + call mesh%print_info() + print *, "" + + ! ========== ENO3 求解器 ========== + print *, "[STEP 2] 配置和运行 ENO3 求解器..." + print *, "-----------------------------------" + + ! 初始化配置 + config_eno3%verbose = .true. + config_eno3%ic_type = "step" + config_eno3%wave_speed = 1.0_wp + config_eno3%final_time = 0.625_wp + config_eno3%dt = 0.0025_wp + config_eno3%boundary_type = "periodic" + config_eno3%equation_type = "linear_advection" + config_eno3%problem_type = "linear_advection" + config_eno3%domain_length = 2.0_wp + config_eno3%enable_physics = .true. + + call config_with_reconstruction(config_eno3, "eno", 3) + call config_print(config_eno3) + print *, "" + + ! 创建和运行求解器 + solver_eno3%config = config_eno3 + solver_eno3%mesh = mesh + + ! 控制数据模式 (当前使用简单数据) + call solver_eno3%enable_real_data(.false.) ! 设置为false使用简单数据 + + call solver_eno3%initialize() + call solver_eno3%run_to_time(config_eno3%final_time) + + ! 获取结果 + time_eno3 = solver_eno3%current_time + steps_eno3 = solver_eno3%current_step + state_eno3 = solver_eno3%get_state() + + print *, "ENO3 完成:" + print *, " 最终时间: ", time_eno3 + print *, " 总步数: ", steps_eno3 + print *, " 状态: ", state_eno3 + print *, "" + + ! ========== WENO3 求解器 ========== + print *, "[STEP 3] 配置和运行 WENO3 求解器..." + print *, "-----------------------------------" + + ! 配置 (复制ENO3配置,只改重构格式) + config_weno3 = config_eno3 + call config_with_reconstruction(config_weno3, "weno3", 3) + + ! 运行 + solver_weno3%config = config_weno3 + solver_weno3%mesh = mesh + call solver_weno3%enable_real_data(.false.) + + call solver_weno3%initialize() + call solver_weno3%run_to_time(config_weno3%final_time) + + time_weno3 = solver_weno3%current_time + steps_weno3 = solver_weno3%current_step + state_weno3 = solver_weno3%get_state() + + print *, "WENO3 完成:" + print *, " 最终时间: ", time_weno3 + print *, " 总步数: ", steps_weno3 + print *, " 状态: ", state_weno3 + print *, "" + + ! ========== WENO5 求解器 ========== + print *, "[STEP 4] 配置和运行 WENO5 求解器..." + print *, "-----------------------------------" + + config_weno5 = config_eno3 + call config_with_reconstruction(config_weno5, "weno", 5) + + solver_weno5%config = config_weno5 + solver_weno5%mesh = mesh + call solver_weno5%enable_real_data(.false.) + + call solver_weno5%initialize() + call solver_weno5%run_to_time(config_weno5%final_time) + + time_weno5 = solver_weno5%current_time + steps_weno5 = solver_weno5%current_step + state_weno5 = solver_weno5%get_state() + + print *, "WENO5 完成:" + print *, " 最终时间: ", time_weno5 + print *, " 总步数: ", steps_weno5 + print *, " 状态: ", state_weno5 + print *, "" + + ! ========== 结果汇总 ========== + print *, "==========================================" + print *, " 结果汇总" + print *, "==========================================" + print *, "" + + all_success = (state_eno3 == SOLVER_COMPLETED) .and. & + (state_weno3 == SOLVER_COMPLETED) .and. & + (state_weno5 == SOLVER_COMPLETED) + + if (all_success) then + print *, "✓ 所有求解器成功完成!" + print *, "" + print *, "性能对比:" + print *, " ENO3: ", steps_eno3, " 步" + print *, " WENO3: ", steps_weno3, " 步" + print *, " WENO5: ", steps_weno5, " 步" + print *, "" + print *, "网格信息:" + print *, " 单元数: ", mesh%ncells + print *, " 域长度: ", mesh%L + print *, " 网格间距: ", mesh%dx + print *, "" + print *, "数据模式: 简单数据 (一阶迎风格式)" + print *, "切换真实计算: 调用 solver%enable_real_data(.true.)" + else + print *, "✗ 部分求解器失败" + print *, " ENO3状态: ", state_eno3, " (期望: ", SOLVER_COMPLETED, ")" + print *, " WENO3状态: ", state_weno3, " (期望: ", SOLVER_COMPLETED, ")" + print *, " WENO5状态: ", state_weno5, " (期望: ", SOLVER_COMPLETED, ")" + end if + + print *, "" + print *, "==========================================" + print *, " 分析完成" + print *, "==========================================" + + ! 清理 + call solver_eno3%cleanup() + call solver_weno3%cleanup() + call solver_weno5%cleanup() + + ! 等待用户输入 + print *, "" + print *, "按 ENTER 键退出..." + read(*,*) + +end program run_eno_weno_integrated \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/python/plot_results.py b/example/1d-linear-convection/weno3/fortran/registry/03g/python/plot_results.py new file mode 100644 index 000000000..cf8e40175 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/python/plot_results.py @@ -0,0 +1,419 @@ +#!/usr/bin/env python3 +# python/plot_results.py +""" +Fortran CFD 结果可视化脚本 +与Julia的plotter.jl功能类似,但直接读取Fortran生成的文本文件 +""" + +import numpy as np +import matplotlib.pyplot as plt +import os +import sys +import glob +from pathlib import Path +import re + +class FortranResultsPlotter: + """Fortran结果绘图器""" + + def __init__(self, style='default'): + self.style = style + self.setup_styles() + + def setup_styles(self): + """设置绘图样式""" + if self.style == 'default': + self.styles = { + 'numerical': { + 'color': 'blue', + 'linestyle': '-', + 'marker': 'o', + 'markerfacecolor': 'none', + 'markersize': 4, + 'linewidth': 1 + }, + 'analytical': { + 'color': 'red', + 'linestyle': '--', + 'marker': '', + 'linewidth': 1.5 + }, + 'comparison': [ + {'color': 'black', 'linestyle': '-', 'marker': 'o', 'markerfacecolor': 'none'}, + {'color': 'blue', 'linestyle': '--', 'marker': 's', 'markerfacecolor': 'none'}, + {'color': 'green', 'linestyle': ':', 'marker': '^', 'markerfacecolor': 'none'} + ] + } + + def read_fortran_results(self, filename): + """读取Fortran生成的结果文件""" + data = {} + + try: + with open(filename, 'r', encoding='utf-8', errors='ignore') as f: + lines = f.readlines() + + data_lines = [] + in_data_section = False # 新状态:是否在数据区域 + + for line in lines: + line = line.strip() + if not line: + continue + + # 跳过所有分隔线 + if line.startswith("===="): + continue + + # 检测数据区域开始 + if line == "DATA: x, numerical, analytical": + in_data_section = True + continue + + if not in_data_section: + # 解析头部信息 + if "Solver:" in line: + data['solver'] = line.split(":", 1)[1].strip() + elif "Scheme:" in line: + data['scheme'] = line.split(":", 1)[1].strip() + elif "Order:" in line: + if "RK Order:" in line: + data['rk_order'] = int(line.split(":", 1)[1].strip()) + else: + data['order'] = int(line.split(":", 1)[1].strip()) + elif "Current Time:" in line: + data['time'] = float(line.split(":", 1)[1].strip()) + elif "Grid Points:" in line: + data['n_points'] = int(line.split(":", 1)[1].strip()) + else: + # 解析数据行 + parts = line.split() + if len(parts) >= 3: + try: + x = float(parts[0]) + numerical = float(parts[1]) + analytical = float(parts[2]) + data_lines.append([x, numerical, analytical]) + except ValueError: + continue # 忽略无法解析的行 + + if data_lines: + import numpy as np + data_array = np.array(data_lines) + data['x'] = data_array[:, 0] + data['numerical'] = data_array[:, 1] + data['analytical'] = data_array[:, 2] + + print(f"Read {len(data['x'])} points from {filename}") + print(f" Solver: {data.get('solver', 'N/A')}") + print(f" Scheme: {data.get('scheme', 'N/A')} order {data.get('order', 'N/A')}") + print(f" Time: {data.get('time', 'N/A')}") + + return data + else: + print(f"Warning: No data found in {filename}") + return None + + except Exception as e: + print(f"Error reading {filename}: {e}") + return None + + def plot_single_result(self, filename, title=None, show=True, save_path=None): + """绘制单个结果文件""" + data = self.read_fortran_results(filename) + if data is None: + return False + + fig, ax = plt.subplots(figsize=(10, 6)) + + # 自动生成标题 + if title is None: + title = f"1D Convection (t={data['time']:.3f})\n" + title += f"{data['order']}th-order {data['scheme'].upper()} + {data['rk_order']}nd-order RK" + + # 绘制数值解 + ax.plot(data['x'], data['numerical'], + label=f"Numerical ({data['scheme'].upper()}{data['order']})", + **self.styles['numerical']) + + # 绘制解析解 + ax.plot(data['x'], data['analytical'], + label="Analytical", + **self.styles['analytical']) + + # 设置图形属性 + ax.set_title(title, fontsize=12) + ax.set_xlabel("x", fontsize=10) + ax.set_ylabel("u", fontsize=10) + ax.legend(fontsize=9) + ax.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + + # 保存或显示 + if save_path: + plt.savefig(save_path, dpi=150, bbox_inches='tight') + print(f"Saved plot to {save_path}") + + if show: + plt.show() + else: + plt.close() + + return True + + def format_scheme_label(self, scheme: str, order: int) -> str: + s = scheme.upper() + if s.startswith("WENO"): + return f"WENO{order}" + else: + return f"{s}{order}" + + def plot_comparison(self, filenames, labels=None, title=None, show=True, save_path=None): + """比较多个结果文件""" + all_data = [] + print(f"plot_comparison filenames={filenames}") + + # 读取所有文件 + for filename in filenames: + print(f"plot_comparison filename={filename}") + data = self.read_fortran_results(filename) + if data is not None: + all_data.append(data) + + if not all_data: + print("No valid data to plot") + return False + + fig, axes = plt.subplots(2, 2, figsize=(14, 10)) + + # 子图1:所有方法比较 + for i, data in enumerate(all_data): + #print(f"i,all_data={i,all_data}") + if labels and i < len(labels): + label = labels[i] + else: + label = f"{data['scheme'].upper()}{data['order']}" + + style_idx = i % len(self.styles['comparison']) + style = self.styles['comparison'][style_idx] + + axes[0, 0].plot(data['x'], data['numerical'], + label=label, **style) + + axes[0, 0].plot(all_data[0]['x'], all_data[0]['analytical'], + label="Analytical", **self.styles['analytical']) + + axes[0, 0].set_xlabel('x', fontsize=12) + axes[0, 0].set_ylabel('u(x)', fontsize=12) + axes[0, 0].set_title('Numerical Solutions Comparison', fontsize=14) + axes[0, 0].legend() + axes[0, 0].grid(True, alpha=0.3) + + # 子图2:误差分析 + for i, data in enumerate(all_data): + if labels and i < len(labels): + label = labels[i] + else: + label = f"{data['scheme'].upper()}{data['order']}" + + error = np.abs(data['numerical'] - data['analytical']) + axes[0, 1].semilogy(data['x'], error, label=label) + + axes[0, 1].set_xlabel('x', fontsize=12) + axes[0, 1].set_ylabel('Absolute Error', fontsize=12) + axes[0, 1].set_title('Error Comparison', fontsize=14) + axes[0, 1].legend() + axes[0, 1].grid(True, alpha=0.3) + + # 子图3:数值解细节(放大) + x_min, x_max = all_data[0]['x'].min(), all_data[0]['x'].max() + zoom_center = 1.0 # 阶跃函数位置附近 + zoom_width = 0.3 + + for i, data in enumerate(all_data): + if labels and i < len(labels): + label = labels[i] + else: + label = f"{data['scheme'].upper()}{data['order']}" + + style_idx = i % len(self.styles['comparison']) + style = self.styles['comparison'][style_idx] + + axes[1, 0].plot(data['x'], data['numerical'], label=label, **style) + + axes[1, 0].plot(all_data[0]['x'], all_data[0]['analytical'], + label="Analytical", **self.styles['analytical']) + + axes[1, 0].set_xlim(zoom_center - zoom_width/2, zoom_center + zoom_width/2) + axes[1, 0].set_xlabel('x', fontsize=12) + axes[1, 0].set_ylabel('u(x)', fontsize=12) + axes[1, 0].set_title('Zoomed View (x ≈ 1.0)', fontsize=14) + axes[1, 0].legend() + axes[1, 0].grid(True, alpha=0.3) + + # 子图4:性能统计 + axes[1, 1].axis('off') + + # 计算并显示L2误差 + errors = [] + schemes = [] + + for data in all_data: + error = np.sqrt(np.mean((data['numerical'] - data['analytical'])**2)) + errors.append(error) + schemes.append(f"{data['scheme'].upper()}{data['order']}") + + # 创建表格数据 + table_data = [] + for i, (scheme, error) in enumerate(zip(schemes, errors)): + table_data.append([scheme, f"{error:.2e}"]) + + # 在子图中显示表格 + table = axes[1, 1].table(cellText=table_data, + colLabels=['Scheme', 'L2 Error'], + loc='center', + cellLoc='center', + colWidths=[0.3, 0.4]) + + table.auto_set_font_size(False) + table.set_fontsize(10) + table.scale(1, 1.5) + + axes[1, 1].set_title('Performance Summary (L2 Error)', fontsize=14) + + # 设置总标题 + if title is None: + time = all_data[0]['time'] + schemes_str = ", ".join([self.format_scheme_label(d['scheme'], d['order']) for d in all_data]) + title = f"1D Convection Comparison (t={time:.3f})\n{schemes_str}" + + fig.suptitle(title, fontsize=16) + plt.tight_layout() + + # 保存或显示 + if save_path: + plt.savefig(save_path, dpi=150, bbox_inches='tight') + print(f"Saved comparison plot to {save_path}") + + if show: + plt.show() + else: + plt.close() + + return True + + def get_scheme_from_filename(self, filename): + print(f"filename={filename}") + stem = Path(filename).stem # e.g., "results_ENO3_40" + parts = stem.split('_') + if len(parts) >= 2: + return parts[1] # "ENO3", "WENO3", "WENO5" + return "" + + def plot_eno_weno_comparison(self, result_dir=".", save_path="eno_weno_comparison.png"): + """自动绘制ENO/WENO对比图(类似Julia的功能)""" + # 查找结果文件 + pattern = os.path.join(result_dir, "results_*.dat") + files = glob.glob(pattern) + + if not files: + print(f"No result files found matching {pattern}") + return False + + # 重新分类 + file_info = [] + eno_files = [] + weno3_files = [] + weno5_files = [] + for f in files: + print(f"f={f}") + print(f"type(f)={type(f)}") + scheme = self.get_scheme_from_filename(f) + print(f"scheme={scheme}") + if scheme == "ENO3": + eno_files.append(f) + elif scheme == "WENO3": + weno3_files.append(f) + elif scheme == "WENO5": + weno5_files.append(f) + + # 按求解器类型分类 + print(f"eno_files={eno_files}") + print(f"weno3_files={weno3_files}") + print(f"weno5_files={weno5_files}") + + # 选择最新的文件(如果有多组) + files_to_plot = [] + labels = [] + + if eno_files: + files_to_plot.append(sorted(eno_files)[-1]) + labels.append("ENO3") + + if weno3_files: + files_to_plot.append(sorted(weno3_files)[-1]) + labels.append("WENO3") + + if weno5_files: + files_to_plot.append(sorted(weno5_files)[-1]) + labels.append("WENO5") + + if len(files_to_plot) >= 2: + print(f"Plotting comparison of {len(files_to_plot)} solvers") + return self.plot_comparison(files_to_plot, labels=labels, + save_path=save_path, show=True) + else: + print("Not enough different solvers for comparison") + return False + +def main(): + """主函数""" + import argparse + + parser = argparse.ArgumentParser(description='Fortran CFD Results Visualizer') + parser.add_argument('--file', help='Plot single result file') + parser.add_argument('--compare', nargs='+', help='Compare multiple files') + parser.add_argument('--dir', default='.', help='Directory containing result files') + parser.add_argument('--auto', action='store_true', help='Auto plot ENO/WENO comparison') + parser.add_argument('--save', help='Save plot to file (without showing)') + parser.add_argument('--no-show', action='store_true', help='Don\'t show plot') + + args = parser.parse_args() + + plotter = FortranResultsPlotter() + + if args.file: + # 绘制单个文件 + plotter.plot_single_result(args.file, save_path=args.save, + show=not args.no_show) + + elif args.compare: + # 比较多个文件 + plotter.plot_comparison(args.compare, save_path=args.save, + show=not args.no_show) + + elif args.auto: + # 自动绘制比较图 + save_path = args.save or "eno_weno_comparison.png" + plotter.plot_eno_weno_comparison(args.dir, save_path=save_path) + + else: + # 默认:显示帮助 + parser.print_help() + + # 也显示可用的结果文件 + print("\nAvailable result files:") + pattern = os.path.join(args.dir, "results_*.dat") + files = glob.glob(pattern) + + if files: + for f in sorted(files): + print(f" {os.path.basename(f)}") + + print(f"\nTo plot ENO/WENO comparison automatically:") + print(f" python plot_results.py --auto") + else: + print(" No result files found") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/build.bat b/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/build.bat new file mode 100644 index 000000000..6fd6dc031 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/build.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Project Builder +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python build script with full Intel environment support... +echo. + +python build.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Build failed + pause + exit /b 1 +) + +echo. +echo [INFO] Build completed successfully! +echo. +echo [INFO] To run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/build.py b/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/build.py new file mode 100644 index 000000000..3bf6d5375 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/build.py @@ -0,0 +1,629 @@ +#!/usr/bin/env python3 +""" +Fortran CFD Project Builder - 完整Python解决方案 +在Python内部处理Intel oneAPI环境配置 +""" + +import os +import sys +import subprocess +import shutil +import argparse +import time +import platform +import tempfile +from pathlib import Path + +class IntelEnvironment: + """Intel oneAPI环境管理器""" + + def __init__(self): + self.setvars_path = None + self.env_vars = {} + + def find_setvars(self): + """查找setvars.bat文件""" + possible_paths = [ + r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat", + r"C:\Program Files\Intel\oneAPI\setvars.bat", + r"C:\Program Files (x86)\Intel\oneAPI\compiler\latest\env\vars.bat", + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\setvars.bat"), + os.path.expandvars(r"%INTEL_ONEAPI_ROOT%\compiler\latest\env\vars.bat"), + ] + + for path in possible_paths: + if os.path.exists(path): + self.setvars_path = path + return True + + return False + + def setup_environment(self): + """设置Intel环境""" + if not self.find_setvars(): + return False + + try: + # 创建临时的批处理文件来捕获环境变量 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + f.write(f'@echo off\n') + f.write(f'call "{self.setvars_path}" >nul 2>&1\n') + f.write(f'set\n') # 输出所有环境变量 + temp_bat = f.name + + # 运行批处理文件并捕获输出 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + shell=True + ) + + # 解析环境变量 + for line in result.stdout.split('\n'): + line = line.strip() + if '=' in line: + key, value = line.split('=', 1) + self.env_vars[key.strip()] = value.strip() + + # 清理临时文件 + os.unlink(temp_bat) + + # 更新当前进程的环境变量 + os.environ.update(self.env_vars) + + return True + + except Exception as e: + print(f"设置Intel环境失败: {e}") + return False + + def get_compiler_info(self): + """获取编译器信息""" + info = {} + + # 检查ifx编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore', + env={**os.environ, **self.env_vars} if self.env_vars else os.environ + ) + + if result.returncode == 0: + for line in result.stdout.split('\n'): + if 'Version' in line or '版本' in line: + info['ifx_version'] = line.strip() + break + except: + pass + + # 检查环境变量 + info['ifx_root'] = self.env_vars.get('IFX_ROOT', '') + info['compiler_root'] = self.env_vars.get('ONEAPI_ROOT', '') + + return info + +class BuildSystem: + """构建系统主类""" + + def __init__(self): + self.project_root = Path(__file__).parent.parent + self.build_dir = self.project_root / "build" + self.intel_env = IntelEnvironment() + + # 设置控制台编码 + if sys.platform == "win32": + try: + import ctypes + # 设置控制台输出为UTF-8 + ctypes.windll.kernel32.SetConsoleOutputCP(65001) + except: + pass + + def print_header(self, text): + """打印标题""" + print(f"\n{'='*70}") + print(f" {text}") + print(f"{'='*70}\n") + + def print_step(self, step, total, message): + """打印步骤""" + print(f"[{step}/{total}] {message}...") + + def print_success(self, message): + """打印成功""" + print(f"\033[92m✓ {message}\033[0m") + + def print_error(self, message): + """打印错误""" + print(f"\033[91m✗ {message}\033[0m") + + def print_warning(self, message): + """打印警告""" + print(f"\033[93m! {message}\033[0m") + + def print_info(self, message): + """打印信息""" + print(f"\033[94mℹ {message}\033[0m") + + def check_prerequisites(self): + """检查前提条件""" + self.print_step(1, 6, "检查前提条件") + + # 检查Python版本 + python_version = sys.version.split()[0] + self.print_info(f"Python版本: {python_version}") + + # 检查平台 + self.print_info(f"平台: {platform.system()} {platform.release()}") + self.print_info(f"处理器核心数: {os.cpu_count()}") + + # 检查CMake + try: + result = subprocess.run( + ["cmake", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + version_line = result.stdout.split('\n')[0] + self.print_success(f"CMake: {version_line}") + else: + self.print_error("CMake未找到") + return False + except FileNotFoundError: + self.print_error("CMake未安装") + return False + + return True + + def setup_intel_environment(self, args): + """设置Intel环境""" + self.print_step(2, 6, "配置Intel oneAPI环境") + + if not self.intel_env.find_setvars(): + self.print_warning("未找到Intel oneAPI setvars.bat") + self.print_info("将尝试使用系统环境中的编译器") + + # 检查是否能直接访问编译器 + try: + result = subprocess.run( + ["ifx", "--version"], + capture_output=True, + text=True, + encoding='utf-8', + errors='ignore' + ) + if result.returncode == 0: + self.print_success("Intel编译器在系统PATH中找到") + return True + else: + self.print_warning("Intel编译器未在PATH中找到") + except: + self.print_warning("无法访问Intel编译器") + + return True # 继续,让CMake自己找编译器 + + # 设置环境 + if self.intel_env.setup_environment(): + compiler_info = self.intel_env.get_compiler_info() + + if compiler_info.get('ifx_version'): + self.print_success(f"Intel Fortran编译器: {compiler_info['ifx_version']}") + elif compiler_info.get('ifx_root'): + self.print_success(f"Intel编译器路径: {compiler_info['ifx_root']}") + else: + self.print_success("Intel oneAPI环境配置完成") + + return True + else: + self.print_warning("Intel环境配置失败,将继续使用系统环境") + return True + + def clean_build_directory(self, args): + """清理构建目录""" + if args.clean and self.build_dir.exists(): + self.print_info("清理构建目录...") + try: + shutil.rmtree(self.build_dir) + self.print_success("构建目录已清理") + except Exception as e: + self.print_error(f"清理失败: {e}") + if not args.force: + return False + return True + + def run_command(self, cmd, cwd=None, check=True, env=None): + """运行命令""" + if isinstance(cmd, list): + cmd_str = ' '.join(str(c) for c in cmd if c) + else: + cmd_str = str(cmd) + + print(f" \033[96m$\033[0m {cmd_str}") + + try: + # 合并环境变量 + exec_env = os.environ.copy() + if env: + exec_env.update(env) + if self.intel_env.env_vars: + exec_env.update(self.intel_env.env_vars) + + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=False, + env=exec_env + ) + + # 处理输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or '完成' in line or '生成' in line: + print(f" \033[92m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + if check and result.returncode != 0: + self.print_error(f"命令执行失败,退出码: {result.returncode}") + return False + + return True + + except Exception as e: + self.print_error(f"命令执行异常: {e}") + return False + + def configure_cmake(self, args): + """配置CMake""" + self.print_step(3, 6, "配置CMake项目") + + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + f"-DCMAKE_BUILD_TYPE={args.build_type}", + ] + + if args.compiler == "ifx": + cmake_cmd.extend(["-T", "fortran=ifx"]) + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + success = self.run_command(cmake_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("CMake配置完成") + else: + self.print_error("CMake配置失败") + + return success + + def build_project(self, args): + """构建项目""" + self.print_step(4, 6, "构建项目") + + build_cmd = [ + "cmake", + "--build", ".", + "--config", args.build_type, + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + success = self.run_command(build_cmd, cwd=self.build_dir, check=True) + + if success: + self.print_success("项目构建完成") + else: + self.print_error("构建失败") + + return success + + def run_tests_with_environment(self, test_exe): + """运行单个测试,确保有Intel环境""" + try: + # 创建临时的批处理文件来运行测试 + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'"{test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'"{test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + return result + + except Exception as e: + print(f"运行测试失败: {e}") + return None + + def run_tests(self, args): + """运行测试""" + self.print_step(5, 6, "运行测试") + + # 查找测试可执行文件 + test_dir = self.build_dir / "bin" / args.build_type + if not test_dir.exists(): + test_dir = self.build_dir / "bin" + if not test_dir.exists(): + test_dir = self.build_dir + + test_files = list(test_dir.glob("test_*.exe")) + + if not test_files: + self.print_warning("未找到测试程序") + return True + + all_passed = True + + for test_exe in sorted(test_files): + test_name = test_exe.stem + self.print_info(f"运行测试: {test_name}") + print(f" {'-'*50}") + + # 运行测试 + result = self.run_tests_with_environment(str(test_exe)) + + if result is None: + self.print_error(f" {test_name} 运行失败") + all_passed = False + continue + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + print(f" {line}") + + if result.returncode == 0: + self.print_success(f" {test_name} 通过") + else: + self.print_error(f" {test_name} 失败 (退出码: {result.returncode})") + all_passed = False + + if result.stderr: + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + print() # 空行 + + return all_passed + + def create_test_runner(self, args): + """创建独立的测试运行器""" + self.print_step(6, 6, "创建测试运行器") + + runner_path = self.build_dir / "run_tests.bat" + + content = f'''@echo off +chcp 65001 >nul + +echo ======================================== +echo Fortran CFD Test Runner +echo ======================================== +echo. + +REM Setup Intel oneAPI environment +set "SETVARS_PATH={self.intel_env.setvars_path or ''}" +if exist "%SETVARS_PATH%" ( + call "%SETVARS_PATH%" >nul + echo [INFO] Intel environment configured +) else ( + echo [WARNING] Intel environment not found + echo [WARNING] Tests may fail without runtime libraries +) + +echo. + +REM Run all test executables +set "TEST_COUNT=0" +set "PASS_COUNT=0" + +for %%f in ("bin\\{args.build_type}\\test_*.exe") do ( + set /a TEST_COUNT+=1 + echo [TEST %%f] + echo {'-'*50} + + %%f + if errorlevel 1 ( + echo [FAILED] %%f + ) else ( + echo [PASSED] %%f + set /a PASS_COUNT+=1 + ) + echo. +) + +echo ======================================== +echo Tests: %PASS_COUNT%/%TEST_COUNT% passed +if %PASS_COUNT% equ %TEST_COUNT% ( + echo [SUCCESS] All tests passed! +) else ( + echo [FAILURE] Some tests failed +) +echo ======================================== + +pause +''' + + with open(runner_path, 'w', encoding='utf-8') as f: + f.write(content) + + self.print_success(f"测试运行器已创建: {runner_path}") + self.print_info(f"使用方法: cd build && run_tests.bat") + + return runner_path + + def generate_report(self, args, build_time, tests_passed): + """生成构建报告""" + self.print_header("构建完成") + + print(f"项目: {self.project_root.name}") + print(f"构建类型: {args.build_type}") + print(f"编译器: {args.compiler}") + print(f"并行作业: {args.jobs}") + print(f"总耗时: {build_time:.1f}秒") + print(f"测试结果: {'全部通过' if tests_passed else '有失败'}") + + # 显示生成的可执行文件 + bin_dir = self.build_dir / "bin" / args.build_type + if bin_dir.exists(): + print(f"\n生成的可执行文件:") + for exe in sorted(bin_dir.glob("*.exe")): + size_mb = exe.stat().st_size / (1024 * 1024) + print(f" • {exe.name} ({size_mb:.2f} MB)") + + # 显示测试运行器信息 + runner_path = self.build_dir / "run_tests.bat" + if runner_path.exists(): + print(f"\n独立测试运行器:") + print(f" • {runner_path.name}") + print(f" 在Intel oneAPI环境中运行所有测试") + + def run(self): + """运行构建系统""" + parser = argparse.ArgumentParser( + description="Fortran CFD项目构建工具", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认构建 + %(prog)s --clean # 清理后构建 + %(prog)s --build-type Release # Release构建 + %(prog)s --no-tests # 只构建,不运行测试 + %(prog)s -j8 --verbose # 8线程并行构建,详细输出 + """ + ) + + parser.add_argument("--build-type", choices=["Debug", "Release"], + default="Debug", help="构建类型") + parser.add_argument("--compiler", choices=["ifx", "ifort"], + default="ifx", help="Fortran编译器") + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-tests", action="store_true", + help="跳过测试") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + + args = parser.parse_args() + + # 开始构建 + start_time = time.time() + + self.print_header("Fortran CFD 项目构建系统") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 检查前提条件 + if not self.check_prerequisites(): + if not args.force: + return 1 + + # 2. 设置Intel环境 + if not self.setup_intel_environment(args): + if not args.force: + return 1 + + # 3. 清理目录 + if not self.clean_build_directory(args): + if not args.force: + return 1 + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 4. 配置CMake + if not self.configure_cmake(args): + if not args.force: + return 1 + + # 5. 构建项目 + if not self.build_project(args): + if not args.force: + return 1 + + # 6. 运行测试和创建测试运行器 + tests_passed = True + if not args.no_tests: + tests_passed = self.run_tests(args) + + # 创建测试运行器 + self.create_test_runner(args) # 传递 args 参数 + + # 7. 生成报告 + build_time = time.time() - start_time + self.generate_report(args, build_time, tests_passed) + + return 0 if tests_passed else 1 + + except KeyboardInterrupt: + self.print_error("\n构建被用户中断") + return 1 + except Exception as e: + self.print_error(f"构建过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + builder = BuildSystem() + return builder.run() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/requirements.txt b/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/requirements.txt new file mode 100644 index 000000000..d21a12b45 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/requirements.txt @@ -0,0 +1,2 @@ +# 构建脚本的Python依赖(可选) +# 目前没有特殊依赖,保持空文件或添加未来可能需要的包 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/run_all_steps.bat b/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/run_all_steps.bat new file mode 100644 index 000000000..d506149b2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/run_all_steps.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo CFD Project: All Steps +echo ======================================== +echo. + +echo [INFO] Starting Step 1: Physics Modules Test... +call run_step1.bat + +if errorlevel 1 ( + echo [ERROR] Step 1 failed + pause + exit /b 1 +) + +echo. +echo [INFO] Starting Step 2: Configuration Physics Update... +call run_step2.bat + +if errorlevel 1 ( + echo [ERROR] Step 2 failed + pause + exit /b 1 +) + +echo. +echo ======================================== +echo All Steps Completed Successfully! +echo ======================================== +echo. +echo [INFO] Next: Update component manager for physics support +echo [INFO] Run: run_step3.bat (to be created) +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/run_example.py b/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/run_example.py new file mode 100644 index 000000000..d7c199178 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/run_example.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# scripts/run_example.py +""" +运行ENO/WENO示例程序 +""" + +import os +import sys +import subprocess +from pathlib import Path + +# 添加当前目录到路径 +sys.path.insert(0, str(Path(__file__).parent)) + +try: + from build import BuildSystem +except ImportError: + print("错误: 找不到build.py,请确保在scripts目录中运行") + sys.exit(1) + +def run_example(): + """运行示例程序""" + builder = BuildSystem() + + print("\n" + "="*70) + print(" 运行ENO/WENO对比示例程序") + print("="*70 + "\n") + + # 检查是否已构建 + exe_path = builder.build_dir / "bin" / "Debug" / "example_eno_weno_comparison.exe" + + if not exe_path.exists(): + print("示例程序未构建,先构建项目...") + print("-"*50) + + # 使用简化的构建 + result = subprocess.run( + ["python", "build.py", "--no-tests", "--clean"], + cwd=builder.project_root / "scripts", + capture_output=True, + text=True + ) + + if result.returncode != 0: + print("构建失败:") + print(result.stderr) + return False + + # 运行示例程序 + print("运行示例程序...") + print("-"*50) + + try: + result = subprocess.run( + [str(exe_path)], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace' + ) + + print(result.stdout) + + if result.stderr: + print("标准错误输出:") + print(result.stderr) + + return result.returncode == 0 + + except Exception as e: + print(f"运行示例程序失败: {e}") + return False + +def main(): + """主函数""" + success = run_example() + + if success: + print("\n" + "="*70) + print(" ✓ 示例程序运行成功") + print("="*70) + return 0 + else: + print("\n" + "="*70) + print(" ✗ 示例程序运行失败") + print("="*70) + return 1 + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/run_step1.bat b/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/run_step1.bat new file mode 100644 index 000000000..0b6b1f17e --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/run_step1.bat @@ -0,0 +1,39 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Step 1: Physics Modules Test +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python step1 script with full Intel environment support... +echo. + +python run_step1.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Step 1 failed + pause + exit /b 1 +) + +echo. +echo [INFO] Step 1 completed successfully! +echo. +echo [INFO] Next step: Update config to include physics settings +echo [INFO] Run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/run_step1.py b/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/run_step1.py new file mode 100644 index 000000000..5e087a690 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/run_step1.py @@ -0,0 +1,319 @@ +#!/usr/bin/env python3 +""" +Step 1: Physics Modules Test +扩展build.py,专门用于测试物理模块 +""" + +import os +import sys +import subprocess +import time +from pathlib import Path + +# 添加当前目录到路径,以便导入build.py的类 +sys.path.insert(0, str(Path(__file__).parent)) + +try: + from build import IntelEnvironment, BuildSystem +except ImportError: + print("Error: Cannot import build.py. Make sure build.py is in the same directory.") + sys.exit(1) + +class Step1System(BuildSystem): + """Step 1 测试系统,继承自BuildSystem""" + + def __init__(self): + super().__init__() + self.test_name = "test_physics_minimal" + self.test_exe = None + + def find_test_executable(self): + """查找测试可执行文件""" + possible_paths = [ + self.build_dir / "bin" / "Debug" / f"{self.test_name}.exe", + self.build_dir / "Debug" / f"{self.test_name}.exe", + self.build_dir / f"{self.test_name}.exe", + self.build_dir / "bin" / f"{self.test_name}.exe", + ] + + for path in possible_paths: + if path.exists(): + self.test_exe = path + self.print_success(f"Found test executable: {path}") + return True + + # 如果没有找到,尝试搜索 + self.print_warning(f"Could not find {self.test_name}.exe") + self.print_info("Searching for test executables...") + + try: + result = subprocess.run( + ["dir", str(self.build_dir), "/s", "/b", "*.exe"], + capture_output=True, + text=True, + encoding='utf-8', + shell=True + ) + + if result.returncode == 0: + test_files = [line.strip() for line in result.stdout.split('\n') + if line and 'test_' in line.lower()] + + if test_files: + self.print_info("Found test files:") + for test_file in test_files: + self.print_info(f" {test_file}") + return False + except: + pass + + return False + + def run_test_with_intel_env(self): + """在Intel环境下运行测试""" + if not self.test_exe: + self.print_error("No test executable found") + return False + + self.print_step(1, 2, f"Running test: {self.test_exe.name}") + + try: + # 创建临时的批处理文件来运行测试(包含Intel环境) + import tempfile + + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + # 设置Intel环境 + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'echo [INFO] Intel environment configured\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'echo [WARNING] Intel environment not found\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + self.print_info(f"Command: {temp_bat}") + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower() or 'fail' in line.lower(): + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or 'pass' in line.lower() or '✓' in line: + print(f" \033[92m{line}\033[0m") + elif '=' in line or '---' in line: + print(f" \033[96m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + self.print_warning("Test stderr output:") + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + self.print_step(2, 2, "Test execution completed") + + if result.returncode == 0: + self.print_success("Test passed") + return True + else: + self.print_error(f"Test failed (exit code: {result.returncode})") + return False + + except Exception as e: + self.print_error(f"Failed to run test: {e}") + return False + + def build_project_if_needed(self, args): + """如果需要,构建项目""" + if args.no_build: + self.print_info("Skipping build (--no-build flag)") + return True + + self.print_step(1, 3, "Building project") + + # 调用父类的构建方法 + build_args = argparse.Namespace() + build_args.clean = args.clean + build_args.build_type = "Debug" + build_args.compiler = "ifx" + build_args.no_tests = True # 不运行所有测试 + build_args.jobs = os.cpu_count() + build_args.verbose = args.verbose + build_args.force = args.force + + # 清理构建目录 + if args.clean and self.build_dir.exists(): + self.print_info("Cleaning build directory...") + import shutil + try: + shutil.rmtree(self.build_dir) + self.print_success("Build directory cleaned") + except Exception as e: + self.print_error(f"Clean failed: {e}") + if not args.force: + return False + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 配置CMake + self.print_step(2, 3, "Configuring CMake") + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + "-DCMAKE_BUILD_TYPE=Debug", + "-T", "fortran=ifx", + ] + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + if not self.run_command(cmake_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + # 构建项目 + self.print_step(3, 3, "Building project") + build_cmd = [ + "cmake", + "--build", ".", + "--config", "Debug", + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + if not self.run_command(build_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + self.print_success("Build completed") + return True + + def run(self): + """运行Step 1测试""" + parser = argparse.ArgumentParser( + description="Step 1: Physics Modules Test", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认运行 + %(prog)s --clean # 清理后构建并测试 + %(prog)s --no-build # 只运行测试,不重新构建 + %(prog)s --verbose # 详细输出 + %(prog)s -j4 # 使用4个并行作业构建 + """ + ) + + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-build", action="store_true", + help="不重新构建,直接运行测试") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + + args = parser.parse_args() + + # 开始测试 + start_time = time.time() + + self.print_header("Step 1: Physics Modules Implementation Test") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 设置Intel环境 + self.print_step(1, 4, "Setting up Intel oneAPI environment") + if not self.setup_intel_environment(args): + if not args.force: + self.print_error("Intel environment setup failed") + return 1 + self.print_warning("Intel environment setup failed, continuing...") + + # 2. 构建项目(如果需要) + self.print_step(2, 4, "Building project if needed") + if not self.build_project_if_needed(args): + if not args.force: + return 1 + + # 3. 查找测试可执行文件 + self.print_step(3, 4, "Finding test executable") + if not self.find_test_executable(): + if not args.force: + return 1 + self.print_warning("Test executable not found, but continuing due to --force") + return 0 + + # 4. 运行测试 + self.print_step(4, 4, "Running physics module test") + test_passed = self.run_test_with_intel_env() + + # 生成报告 + test_time = time.time() - start_time + self.print_header("Step 1 Complete") + + print(f"测试: {'通过 ✓' if test_passed else '失败 ✗'}") + print(f"测试程序: {self.test_exe.name if self.test_exe else '未找到'}") + print(f"总耗时: {test_time:.1f}秒") + + if test_passed: + print(f"\n下一步: 更新配置以包含物理设置") + print(f"建议: 修改config.f90,添加physics相关字段") + return 0 + else: + if args.force: + self.print_warning("测试失败,但由于--force标志继续执行") + return 0 + return 1 + + except KeyboardInterrupt: + self.print_error("\n测试被用户中断") + return 1 + except Exception as e: + self.print_error(f"测试过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + system = Step1System() + return system.run() + +if __name__ == "__main__": + # 需要导入argparse + import argparse + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/run_step2.bat b/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/run_step2.bat new file mode 100644 index 000000000..9c1f62de4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/run_step2.bat @@ -0,0 +1,39 @@ +@echo off +chcp 65001 >nul + +echo ======================================== +echo Step 2: Configuration Physics Update +echo (Python-based with Intel environment) +echo ======================================== +echo. + +python --version >nul 2>&1 +if errorlevel 1 ( + echo [ERROR] Python not found or not in PATH + pause + exit /b 1 +) + +echo [INFO] Running Python step2 script with full Intel environment support... +echo. + +python run_step2.py %* + +if errorlevel 1 ( + echo. + echo [ERROR] Step 2 failed + pause + exit /b 1 +) + +echo. +echo [INFO] Step 2 completed successfully! +echo. +echo [INFO] Next step: Update component manager to support physics +echo [INFO] Run tests independently: +echo cd build +echo run_tests.bat +echo. + +pause +exit /b 0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/run_step2.py b/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/run_step2.py new file mode 100644 index 000000000..c16b76088 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/run_step2.py @@ -0,0 +1,284 @@ +#!/usr/bin/env python3 +""" +Step 2: Configuration Physics Update +测试配置模块的物理功能更新 +""" + +import os +import sys +import subprocess +import time +from pathlib import Path + +# 添加当前目录到路径,以便导入build.py的类 +sys.path.insert(0, str(Path(__file__).parent)) + +try: + from build import IntelEnvironment, BuildSystem +except ImportError: + print("Error: Cannot import build.py. Make sure build.py is in the same directory.") + sys.exit(1) + +class Step2System(BuildSystem): + """Step 2 测试系统,继承自BuildSystem""" + + def __init__(self): + super().__init__() + self.test_name = "test_config_physics" + self.test_exe = None + + def find_test_executable(self): + """查找测试可执行文件""" + possible_paths = [ + self.build_dir / "bin" / "Debug" / f"{self.test_name}.exe", + self.build_dir / "Debug" / f"{self.test_name}.exe", + self.build_dir / f"{self.test_name}.exe", + self.build_dir / "bin" / f"{self.test_name}.exe", + ] + + for path in possible_paths: + if path.exists(): + self.test_exe = path + self.print_success(f"Found test executable: {path}") + return True + + return False + + def run_test_with_intel_env(self): + """在Intel环境下运行测试""" + if not self.test_exe: + self.print_error("No test executable found") + return False + + self.print_step(1, 2, f"Running test: {self.test_exe.name}") + + try: + # 创建临时的批处理文件来运行测试(包含Intel环境) + import tempfile + + with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f: + # 设置Intel环境 + if self.intel_env.setvars_path: + f.write(f'@echo off\n') + f.write(f'call "{self.intel_env.setvars_path}" >nul 2>&1\n') + f.write(f'echo [INFO] Intel environment configured\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + else: + f.write(f'@echo off\n') + f.write(f'echo [WARNING] Intel environment not found\n') + f.write(f'echo Running test: {self.test_exe.name}\n') + f.write(f'echo {"-"*50}\n') + f.write(f'"{self.test_exe}"\n') + + temp_bat = f.name + + # 运行测试 + self.print_info(f"Command: {temp_bat}") + result = subprocess.run( + [temp_bat], + capture_output=True, + text=True, + encoding='utf-8', + errors='replace', + shell=True + ) + + # 清理临时文件 + os.unlink(temp_bat) + + # 显示输出 + if result.stdout: + for line in result.stdout.split('\n'): + line = line.strip() + if line: + # 高亮重要信息 + if 'error' in line.lower() or 'fail' in line.lower() or '✗' in line: + print(f" \033[91m{line}\033[0m") + elif 'warning' in line.lower(): + print(f" \033[93m{line}\033[0m") + elif 'success' in line.lower() or 'pass' in line.lower() or '✓' in line: + print(f" \033[92m{line}\033[0m") + elif '=' in line or '---' in line or '===' in line: + print(f" \033[96m{line}\033[0m") + else: + print(f" {line}") + + if result.stderr: + self.print_warning("Test stderr output:") + for line in result.stderr.split('\n'): + line = line.strip() + if line: + print(f" \033[93m{line}\033[0m") + + self.print_step(2, 2, "Test execution completed") + + if result.returncode == 0: + self.print_success("Test passed") + return True + else: + self.print_error(f"Test failed (exit code: {result.returncode})") + return False + + except Exception as e: + self.print_error(f"Failed to run test: {e}") + return False + + def build_project(self, args): + """构建项目""" + if args.no_build: + self.print_info("Skipping build (--no-build flag)") + return True + + self.print_step(1, 3, "Building project") + + # 清理构建目录 + if args.clean and self.build_dir.exists(): + self.print_info("Cleaning build directory...") + import shutil + try: + shutil.rmtree(self.build_dir) + self.print_success("Build directory cleaned") + except Exception as e: + self.print_error(f"Clean failed: {e}") + if not args.force: + return False + + # 确保构建目录存在 + self.build_dir.mkdir(exist_ok=True) + + # 配置CMake + self.print_step(2, 3, "Configuring CMake") + cmake_cmd = [ + "cmake", + "..", + "-G", "Visual Studio 17 2022", + "-A", "x64", + "-DCMAKE_BUILD_TYPE=Debug", + "-T", "fortran=ifx", + ] + + if args.verbose: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE=ON") + + if not self.run_command(cmake_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + # 构建项目 + self.print_step(3, 3, "Building project") + build_cmd = [ + "cmake", + "--build", ".", + "--config", "Debug", + ] + + if args.jobs > 1: + build_cmd.append(f"-j{args.jobs}") + + if not self.run_command(build_cmd, cwd=self.build_dir, check=not args.force): + if not args.force: + return False + + self.print_success("Build completed") + return True + + def run(self): + """运行Step 2测试""" + import argparse + + parser = argparse.ArgumentParser( + description="Step 2: Configuration Physics Update", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s # 默认运行 + %(prog)s --clean # 清理后构建并测试 + %(prog)s --no-build # 只运行测试,不重新构建 + %(prog)s --verbose # 详细输出 + """ + ) + + parser.add_argument("--clean", action="store_true", + help="构建前清理build目录") + parser.add_argument("--no-build", action="store_true", + help="不重新构建,直接运行测试") + parser.add_argument("--verbose", action="store_true", + help="详细输出") + parser.add_argument("--force", action="store_true", + help="出错时继续执行") + parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count(), + help="并行作业数") + + args = parser.parse_args() + + # 开始测试 + start_time = time.time() + + self.print_header("Step 2: Configuration Physics Update") + self.print_info(f"项目根目录: {self.project_root}") + + try: + # 1. 设置Intel环境 + self.print_step(1, 4, "Setting up Intel oneAPI environment") + if not self.setup_intel_environment(args): + if not args.force: + self.print_error("Intel environment setup failed") + return 1 + self.print_warning("Intel environment setup failed, continuing...") + + # 2. 构建项目(如果需要) + self.print_step(2, 4, "Building project if needed") + if not self.build_project(args): + if not args.force: + return 1 + + # 3. 查找测试可执行文件 + self.print_step(3, 4, "Finding test executable") + if not self.find_test_executable(): + self.print_error(f"Test executable {self.test_name}.exe not found") + if not args.force: + return 1 + self.print_warning("Test executable not found, but continuing due to --force") + return 0 + + # 4. 运行测试 + self.print_step(4, 4, "Running configuration physics test") + test_passed = self.run_test_with_intel_env() + + # 生成报告 + test_time = time.time() - start_time + self.print_header("Step 2 Complete") + + print(f"测试: {'通过 ✓' if test_passed else '失败 ✗'}") + print(f"测试程序: {self.test_exe.name if self.test_exe else '未找到'}") + print(f"总耗时: {test_time:.1f}秒") + + if test_passed: + print(f"\n下一步: 更新组件管理器以支持物理模块") + print(f"建议: 修改component_manager.f90,添加physics组件创建") + return 0 + else: + if args.force: + self.print_warning("测试失败,但由于--flag继续执行") + return 0 + return 1 + + except KeyboardInterrupt: + self.print_error("\n测试被用户中断") + return 1 + except Exception as e: + self.print_error(f"测试过程中出现未预期错误: {e}") + if args.verbose: + import traceback + traceback.print_exc() + return 1 + +def main(): + """主函数""" + system = Step2System() + return system.run() + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/test_integrated.py b/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/test_integrated.py new file mode 100644 index 000000000..91020a945 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/scripts/test_integrated.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# scripts/test_integrated.py +""" +测试集成求解器 +""" +import subprocess +import sys +import os +from pathlib import Path + +def run_test(): + """运行集成测试""" + # 构建项目 + print("构建项目...") + result = subprocess.run( + ["python", "scripts/build.py", "--no-tests", "--clean"], + capture_output=True, + text=True + ) + + if result.returncode != 0: + print("构建失败:") + print(result.stderr) + return False + + # 运行集成示例 + print("\n运行集成示例...") + exe_path = Path("build/bin/Debug/run_eno_weno_integrated.exe") + + if not exe_path.exists(): + print(f"可执行文件不存在: {exe_path}") + return False + + result = subprocess.run( + [str(exe_path)], + capture_output=True, + text=True, + encoding='utf-8' + ) + + print(result.stdout) + if result.stderr: + print("错误输出:") + print(result.stderr) + + return result.returncode == 0 + +if __name__ == "__main__": + success = run_test() + sys.exit(0 if success else 1) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03g/src/CMakeLists.txt new file mode 100644 index 000000000..ef816d134 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/CMakeLists.txt @@ -0,0 +1,41 @@ +# 修改后的完整版本: +message(STATUS "配置源代码目录...") + +# 设置包含目录 +include_directories(${CMAKE_Fortran_MODULE_DIRECTORY}) + +# 按依赖顺序添加子目录 +add_subdirectory(base) # 1. 基础类型和精度 +add_subdirectory(core) # 2. 核心注册系统 +add_subdirectory(infrastructure) # 3. 基础设施(配置、网格、域、解) +add_subdirectory(physics) # 4. 物理模块(方程和问题) +add_subdirectory(numerics) # 5. 数值方法(重构器、通量、时间积分) +add_subdirectory(manager) # 6. 组件管理器和工厂 +add_subdirectory(solver) # 7. 求解器 + +# ==================== 新增:边界条件和初始条件模块 ==================== +message(STATUS "配置边界条件模块...") +add_subdirectory(boundary) + +message(STATUS "配置初始条件模块...") +add_subdirectory(initial_condition) + +# ==================== 新增:结果模块 ==================== +message(STATUS "配置结果模块...") + +add_library(results STATIC + results.f90 # 新增文件 +) + +target_link_libraries(results + PRIVATE + base + infrastructure + solver +) + +set_target_properties(results PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "集成求解器库配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/base/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03g/src/base/CMakeLists.txt new file mode 100644 index 000000000..74f4aa65f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/base/CMakeLists.txt @@ -0,0 +1,16 @@ +# src/base/CMakeLists.txt +message(STATUS "Configuring base module...") + +add_library(base STATIC + modules.f90 + precision.f90 # 新增 +) + +set_target_properties(base PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Base module configured") + +# src/core/CMakeLists.txt +message(STATUS "Configuring core module...") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/base/modules.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/base/modules.f90 new file mode 100644 index 000000000..43aaee241 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/base/modules.f90 @@ -0,0 +1,36 @@ +! src/base/modules.f90 +module base_modules + use, intrinsic :: iso_fortran_env, only: real64, int32 + implicit none + + public :: wp, ip, max_name_len, string_len, cfd_config_base, component_info + + integer, parameter :: wp = real64 + integer, parameter :: ip = int32 + integer, parameter :: string_len = 100 + integer, parameter :: max_name_len = 32 + + ! 基础配置类型 + type :: cfd_config_base + character(len=max_name_len) :: ic_type = "step" + character(len=max_name_len) :: recon_scheme = "eno" + character(len=max_name_len) :: flux_type = "rusanov" + integer(ip) :: rk_order = 1 + real(wp) :: wave_speed = 1.0_wp + real(wp) :: final_time = 0.625_wp + real(wp) :: dt = 0.025_wp + character(len=max_name_len) :: boundary_type = "periodic" + integer(ip) :: spatial_order = 2 + character(len=max_name_len) :: equation_type = "linear_advection" + character(len=max_name_len) :: problem_type = "linear_advection" + logical :: verbose = .true. + end type cfd_config_base + + ! 组件信息类型 + type :: component_info + character(len=max_name_len) :: category = "" + character(len=max_name_len) :: name = "" + integer(ip) :: order = 0 + end type component_info + +end module base_modules \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/base/precision.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/base/precision.f90 new file mode 100644 index 000000000..4ac5fd7ef --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/base/precision.f90 @@ -0,0 +1,9 @@ +! src/base/precision.f90(简单版本) +module precision_module + use base_modules, only: wp, ip + implicit none + + ! 重新导出,确保兼容 + public :: wp, ip + +end module precision_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/boundary/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03g/src/boundary/CMakeLists.txt new file mode 100644 index 000000000..a9909f1e5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/boundary/CMakeLists.txt @@ -0,0 +1,23 @@ +# src/boundary/CMakeLists.txt +message(STATUS "配置边界条件模块...") + +add_library(boundary STATIC + boundary_base.f90 # 基类 + periodic.f90 # 周期性边界 + dirichlet.f90 # Dirichlet边界 + neumann.f90 # Neumann边界 + factory.f90 # 工厂 +) + +target_link_libraries(boundary + PRIVATE + base + infrastructure + core # 需要注册系统 +) + +set_target_properties(boundary PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "边界条件模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/boundary/boundary_base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/boundary/boundary_base.f90 new file mode 100644 index 000000000..2f7b11163 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/boundary/boundary_base.f90 @@ -0,0 +1,35 @@ +! src/boundary/boundary_base.f90 (修正版) +module boundary_base_module + use base_modules, only: wp, ip + implicit none + private + + type, abstract, public :: boundary_condition + character(len=:), allocatable :: name + contains + procedure(apply_interface), deferred :: apply + procedure :: get_name => bc_get_name + end type + + abstract interface + subroutine apply_interface(this, u, nghosts, ist, ied) + import :: boundary_condition, wp, ip + class(boundary_condition), intent(in) :: this + real(wp), intent(inout) :: u(:) + integer(ip), intent(in) :: nghosts, ist, ied + end subroutine apply_interface + end interface + +contains + + function bc_get_name(this) result(name) + class(boundary_condition), intent(in) :: this + character(len=:), allocatable :: name + if (allocated(this%name)) then + name = this%name + else + name = "unnamed" + end if + end function + +end module boundary_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/boundary/dirichlet.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/boundary/dirichlet.f90 new file mode 100644 index 000000000..e7647bea5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/boundary/dirichlet.f90 @@ -0,0 +1,55 @@ +! src/boundary/dirichlet.f90 +module dirichlet_boundary_module + use base_modules, only: wp, ip + use boundary_base_module, only: boundary_condition + implicit none + private + + type, extends(boundary_condition), public :: dirichlet_boundary + real(wp) :: left_value = 1.0_wp + real(wp) :: right_value = 2.0_wp + contains + procedure :: apply => dirichlet_apply + procedure :: set_values => dirichlet_set_values + end type + + interface dirichlet_boundary + module procedure create_dirichlet_boundary + end interface + +contains + + type(dirichlet_boundary) function create_dirichlet_boundary(left_val, right_val) result(this) + real(wp), optional, intent(in) :: left_val, right_val + + this%name = "dirichlet" + if (present(left_val)) this%left_value = left_val + if (present(right_val)) this%right_value = right_val + end function + + subroutine dirichlet_set_values(this, left_val, right_val) + class(dirichlet_boundary), intent(inout) :: this + real(wp), intent(in) :: left_val, right_val + this%left_value = left_val + this%right_value = right_val + end subroutine + + subroutine dirichlet_apply(this, u, nghosts, ist, ied) + class(dirichlet_boundary), intent(in) :: this + real(wp), intent(inout) :: u(:) + integer(ip), intent(in) :: nghosts, ist, ied + + integer :: i + + ! 左边界 + do i = 0, nghosts-1 + u(ist-1-i) = this%left_value + end do + + ! 右边界 + do i = 0, nghosts-1 + u(ied+i) = this%right_value + end do + end subroutine + +end module dirichlet_boundary_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/boundary/factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/boundary/factory.f90 new file mode 100644 index 000000000..012363b05 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/boundary/factory.f90 @@ -0,0 +1,41 @@ +! src/boundary/factory.f90 +module boundary_factory_module + use base_modules, only: wp, ip + use boundary_base_module, only: boundary_condition + use periodic_boundary_module, only: periodic_boundary + implicit none + private + + public :: create_boundary_condition, initialize_boundary_factory + +contains + + subroutine initialize_boundary_factory() + print *, "[BOUNDARY FACTORY] Boundary conditions placeholder" + end subroutine + + subroutine create_boundary_condition(bc_type, bc_instance, left_val, right_val) + character(len=*), intent(in) :: bc_type + class(boundary_condition), allocatable, intent(out) :: bc_instance + real(wp), optional, intent(in) :: left_val, right_val + + ! 暂时只实现周期性边界 + allocate(periodic_boundary :: bc_instance) + + select case (trim(bc_type)) + case ("periodic") + select type(bc => bc_instance) + type is (periodic_boundary) + bc%name = "periodic" + end select + + case default + print *, "[WARNING] Using periodic as default boundary" + select type(bc => bc_instance) + type is (periodic_boundary) + bc%name = "periodic" + end select + end select + end subroutine + +end module boundary_factory_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/boundary/neumann.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/boundary/neumann.f90 new file mode 100644 index 000000000..908869d94 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/boundary/neumann.f90 @@ -0,0 +1,41 @@ +! src/boundary/neumann.f90 +module neumann_boundary_module + use base_modules, only: wp, ip + use boundary_base_module, only: boundary_condition + implicit none + private + + type, extends(boundary_condition), public :: neumann_boundary + contains + procedure :: apply => neumann_apply + end type + + interface neumann_boundary + module procedure create_neumann_boundary + end interface + +contains + + type(neumann_boundary) function create_neumann_boundary() result(this) + this%name = "neumann" + end function + + subroutine neumann_apply(this, u, nghosts, ist, ied) + class(neumann_boundary), intent(in) :: this + real(wp), intent(inout) :: u(:) + integer(ip), intent(in) :: nghosts, ist, ied + + integer :: i + + ! 左边界零梯度:u[ist-1-i] = u[ist+i] + do i = 0, nghosts-1 + u(ist-1-i) = u(ist+i) + end do + + ! 右边界零梯度:u[ied+i] = u[ied-1-i] + do i = 0, nghosts-1 + u(ied+i) = u(ied-1-i) + end do + end subroutine + +end module neumann_boundary_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/boundary/periodic.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/boundary/periodic.f90 new file mode 100644 index 000000000..1fad1f036 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/boundary/periodic.f90 @@ -0,0 +1,44 @@ +! src/boundary/periodic.f90 (修正版) +module periodic_boundary_module + use base_modules, only: wp, ip + use boundary_base_module, only: boundary_condition + implicit none + private + + type, extends(boundary_condition), public :: periodic_boundary + contains + procedure :: apply => periodic_apply + procedure :: get_name => bc_get_name + end type + +contains + + subroutine periodic_apply(this, u, nghosts, ist, ied) + class(periodic_boundary), intent(in) :: this + real(wp), intent(inout) :: u(:) + integer(ip), intent(in) :: nghosts, ist, ied + + integer :: i + + ! 左ghost层:u[ist-1-i] = u[ied-1-i] + do i = 0, nghosts-1 + u(ist-1-i) = u(ied-1-i) + end do + + ! 右ghost层:u[ied+i] = u[ist+i] + do i = 0, nghosts-1 + u(ied+i) = u(ist+i) + end do + end subroutine + + function bc_get_name(this) result(name) + class(periodic_boundary), intent(in) :: this + character(len=:), allocatable :: name + if (allocated(this%name)) then + name = this%name + else + name = "periodic" + end if + end function + +end module periodic_boundary_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/core/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03g/src/core/CMakeLists.txt new file mode 100644 index 000000000..d8b8df064 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/core/CMakeLists.txt @@ -0,0 +1,14 @@ +# src/core/CMakeLists.txt +message(STATUS "Configuring core module...") + +add_library(core STATIC + registry.f90 +) + +target_link_libraries(core PRIVATE base) + +set_target_properties(core PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Core module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/core/factory_base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/core/factory_base.f90 new file mode 100644 index 000000000..302418a15 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/core/factory_base.f90 @@ -0,0 +1,57 @@ +! src/core/factory_base.f90 +module factory_base_module + use base_modules, only: wp, ip + use registry_module, only: create_component, has_component + + implicit none + private + public :: wp, ip, factory_base, factory_create + + ! 工厂基类 + type :: factory_base + character(len=max_name_length) :: category = "" + contains + procedure :: create => factory_base_create + procedure :: get_available => factory_base_get_available + end type factory_base + + ! 便捷函数类型 + abstract interface + function factory_function_interface(category, name) result(instance) + import :: wp + character(len=*), intent(in) :: category, name + class(*), allocatable :: instance + end function factory_function_interface + end interface + +contains + + ! 创建工厂实例 + function factory_create(category) result(factory) + character(len=*), intent(in) :: category + type(factory_base) :: factory + factory%category = trim(category) + end function factory_create + + ! 工厂创建方法 + function factory_base_create(this, name) result(instance) + class(factory_base), intent(in) :: this + character(len=*), intent(in) :: name + class(*), allocatable :: instance + + instance = create_component(this%category, name) + end function factory_base_create + + ! 获取可用组件列表(简化版) + subroutine factory_base_get_available(this, names, count) + class(factory_base), intent(in) :: this + character(len=*), allocatable, intent(out) :: names(:) + integer(ip), intent(out) :: count + + ! 这里需要实现从注册表获取列表的逻辑 + ! 暂时返回空列表 + count = 0 + allocate(character(len=max_name_length) :: names(0)) + end subroutine factory_base_get_available + +end module factory_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/core/factory_integrated.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/core/factory_integrated.f90 new file mode 100644 index 000000000..c58864c9d --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/core/factory_integrated.f90 @@ -0,0 +1,41 @@ +! src/core/factory_integrated.f90 +module factory_integrated + use base_modules, only: wp, ip + use registry_module, only: register_component_simple + implicit none + private + + public :: register_all_components + +contains + + subroutine register_all_components() + ! 方程 + call register_component_simple("equation", "linear_advection") + + ! 问题 + call register_component_simple("problem", "linear_advection") + + ! 重构器 + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + + ! 通量 + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist_osher") + + ! 边界条件 + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + + ! 初始条件 + call register_component_simple("initial_condition", "step") + call register_component_simple("initial_condition", "sin") + call register_component_simple("initial_condition", "gaussian") + + print *, "[FACTORY] All components registered" + end subroutine + +end module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/core/factory_interfaces.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/core/factory_interfaces.f90 new file mode 100644 index 000000000..a960167c3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/core/factory_interfaces.f90 @@ -0,0 +1,17 @@ +! src/core/factory_interfaces.f90 +module factory_interfaces + use, intrinsic :: iso_fortran_env, only: wp => real64 + implicit none + + private + public :: wp, factory_procedure + + ! Factory procedure interface + abstract interface + subroutine factory_procedure(instance) + import :: wp + class(*), allocatable, intent(out) :: instance + end subroutine factory_procedure + end interface + +end module factory_interfaces \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/core/physics_solver_integrated.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/core/physics_solver_integrated.f90 new file mode 100644 index 000000000..56840e678 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/core/physics_solver_integrated.f90 @@ -0,0 +1,127 @@ +! src/solver/physics_solver_integrated.f90 +module physics_solver_integrated + use base_modules, only: wp, ip + use config_module, only: cfd_config + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create + use registry_module, only: create_component + + implicit none + private + + type, public :: physics_solver_integrated + ! 核心组件 + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + + ! 物理组件 + class(*), allocatable :: equation + class(*), allocatable :: problem + + ! 数值组件 + class(*), allocatable :: reconstructor + class(*), allocatable :: flux_calculator + class(*), allocatable :: boundary_condition + class(*), allocatable :: residual_calculator + + contains + procedure :: initialize => solver_initialize + procedure :: run => solver_run + procedure :: cleanup => solver_cleanup + procedure, private :: create_components + procedure, private :: apply_boundary + end type + +contains + + subroutine solver_initialize(this) + class(physics_solver_integrated), intent(inout) :: this + + ! 创建域和解 + this%domain = domain_create(this%config, this%mesh) + this%solution = solution_create(this%domain) + + ! 创建所有组件 + call this%create_components() + + ! 应用初始条件 + call this%apply_initial_condition() + + print *, "[SOLVER] Initialized with physics integration" + end subroutine + + subroutine create_components(this) + class(physics_solver_integrated), intent(inout) :: this + + ! 创建方程 + call create_component("equation", "linear_advection", this%config, this%equation) + + ! 创建问题 + call create_component("problem", "linear_advection", this%config, this%problem) + + ! 创建数值组件 + call create_component("reconstructor", this%config%recon_scheme, & + this%config, this%reconstructor) + call create_component("flux", this%config%flux_type, & + this%config, this%flux_calculator) + call create_component("boundary", this%config%boundary_type, & + this%cfd_context(), this%boundary_condition) + end subroutine + + subroutine apply_initial_condition(this) + class(physics_solver_integrated), intent(inout) :: this + + ! 通过问题创建初始条件 + select type(prob => this%problem) + type is (linear_advection_problem) + class(*), allocatable :: ic + call prob%create_ic(this%config, ic) + + ! 应用初始条件到解 + select type(ic_inst => ic) + type is (step_function_ic) + call ic_inst%apply(this%solution) + end select + end select + end subroutine + + function cfd_context(this) result(ctx) + class(physics_solver_integrated), intent(in) :: this + type(cfd_context_type) :: ctx + + ! 创建包含求解器所有组件的上下文 + ctx%config => this%config + ctx%domain => this%domain + ctx%solution => this%solution + ctx%equation => this%equation + end function + + subroutine solver_run(this, final_time) + class(physics_solver_integrated), intent(inout) :: this + real(wp), intent(in) :: final_time + + real(wp) :: t, dt, dt_original + integer :: step + + dt_original = this%config%dt + t = 0.0_wp + step = 0 + + do while (t < final_time - 1e-12_wp) + dt = min(this%config%dt, final_time - t) + + ! 时间步进(需要实现) + ! call this%time_step(dt) + + t = t + dt + step = step + 1 + end do + + this%config%dt = dt_original + print *, "[SOLVER] Completed at t = ", t, ", steps = ", step + end subroutine + +end module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/core/registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/core/registry.f90 new file mode 100644 index 000000000..d155aa19b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/core/registry.f90 @@ -0,0 +1,257 @@ +! src/core/registry.f90 (更新版) +module registry_module + use base_modules, only: wp, ip, max_name_len, component_info + + implicit none + private + + ! 明确公开所有需要的接口 + public :: wp, ip ! 类型参数 + public :: component_info ! 类型 + public :: registry_init, registry_cleanup ! 初始化/清理 + public :: register_component_simple ! 注册组件 + public :: has_component_simple ! 检查组件 + public :: list_components ! 列出组件 + public :: registry_is_initialized ! 检查初始化状态 + public :: registry_get_size ! 获取大小 + public :: initialize_default_components ! 新增:初始化默认组件 + + ! 全局注册表 + type :: component_registry + type(component_info), allocatable :: components(:) + integer(ip) :: count = 0 + integer(ip) :: capacity = 100 + logical :: initialized = .false. + logical :: verbose = .true. + logical :: default_components_added = .false. ! 新增:标记是否已添加默认组件 + end type component_registry + + type(component_registry) :: registry + +contains + + ! ==================== 公共API ==================== + + subroutine registry_init(verbose) + logical, optional, intent(in) :: verbose + + if (registry%initialized) then + if (registry%verbose) then + print *, "[REGISTRY] Already initialized" + end if + return + end if + + if (present(verbose)) then + registry%verbose = verbose + end if + + allocate(registry%components(registry%capacity)) + registry%initialized = .true. + + if (registry%verbose) then + print *, "[REGISTRY] Initialized with capacity:", registry%capacity + end if + end subroutine registry_init + + subroutine registry_cleanup() + if (allocated(registry%components)) then + deallocate(registry%components) + end if + registry%initialized = .false. + registry%count = 0 + registry%default_components_added = .false. ! 重置标记 + + if (registry%verbose) then + print *, "[REGISTRY] Cleaned up" + end if + end subroutine registry_cleanup + + ! 新增:初始化默认组件 + subroutine initialize_default_components() + if (.not. registry%initialized) then + call registry_init() + end if + + if (registry%default_components_added) then + if (registry%verbose) then + print *, "[REGISTRY] Default components already added" + end if + return + end if + + ! 注册重构器 + call register_component_simple("reconstructor", "eno", order=3) + call register_component_simple("reconstructor", "weno3", order=3) + call register_component_simple("reconstructor", "weno5", order=5) + + ! 注册通量计算器 + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + + ! 注册边界条件 + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + + ! 注册时间积分器 + call register_component_simple("integrator", "rk1", order=1) + call register_component_simple("integrator", "rk2", order=2) + call register_component_simple("integrator", "rk3", order=3) + + ! 注册方程 + call register_component_simple("equation", "linear_advection") + + ! 注册问题 + call register_component_simple("problem", "linear_advection") + + ! 注册初始条件 + call register_component_simple("initial_condition", "step") + call register_component_simple("initial_condition", "sin") + call register_component_simple("initial_condition", "gaussian") + + registry%default_components_added = .true. + + if (registry%verbose) then + print *, "[REGISTRY] Default components registered" + print *, "[REGISTRY] Total components:", registry%count + end if + end subroutine initialize_default_components + + subroutine register_component_simple(category, name, order) + character(len=*), intent(in) :: category, name + integer(ip), optional, intent(in) :: order + + integer(ip) :: i + type(component_info) :: info + + if (.not. registry%initialized) then + call registry_init() + end if + + ! 检查是否已存在 + do i = 1, registry%count + if (trim(registry%components(i)%category) == trim(category) .and. & + trim(registry%components(i)%name) == trim(name)) then + if (registry%verbose) then + print *, "[WARN] Overwriting component: ", trim(category), ".", trim(name) + end if + + ! 更新 + if (present(order)) then + registry%components(i)%order = order + else + registry%components(i)%order = 0 + end if + return + end if + end do + + ! 扩展数组 + if (registry%count >= registry%capacity) then + call expand_registry() + end if + + ! 添加新组件 + registry%count = registry%count + 1 + + info%category = trim(category) + info%name = trim(name) + info%order = 0 + if (present(order)) then + info%order = order + end if + + registry%components(registry%count) = info + + if (registry%verbose) then + print *, "[OK] Registered simple: ", trim(category), ".", trim(name) + end if + end subroutine register_component_simple + + logical function has_component_simple(category, name) + character(len=*), intent(in) :: category, name + + integer(ip) :: i + + has_component_simple = .false. + + if (.not. registry%initialized) return + + do i = 1, registry%count + if (trim(registry%components(i)%category) == trim(category) .and. & + trim(registry%components(i)%name) == trim(name)) then + has_component_simple = .true. + return + end if + end do + end function has_component_simple + + subroutine list_components(category) + character(len=*), optional, intent(in) :: category + + integer(ip) :: i, count + + if (.not. registry%initialized) then + print *, "[INFO] Registry not initialized" + return + end if + + if (registry%count == 0) then + print *, "[INFO] No components registered" + return + end if + + count = 0 + print *, "=== Registry Contents ===" + do i = 1, registry%count + if (.not. present(category) .or. & + trim(registry%components(i)%category) == trim(category)) then + call print_component_info(registry%components(i)) + count = count + 1 + end if + end do + + print *, "Total:", count, "components" + print *, "==========================" + end subroutine list_components + + ! ==================== 新增函数 ==================== + + logical function registry_is_initialized() + ! 检查注册表是否已初始化 + registry_is_initialized = registry%initialized + end function registry_is_initialized + + integer(ip) function registry_get_size() + ! 获取注册表中的组件数量 + registry_get_size = registry%count + end function registry_get_size + + ! ==================== 内部辅助函数 ==================== + + subroutine expand_registry() + type(component_info), allocatable :: temp(:) + + registry%capacity = registry%capacity * 2 + allocate(temp(registry%capacity)) + temp(1:registry%count) = registry%components(1:registry%count) + call move_alloc(temp, registry%components) + + if (registry%verbose) then + print *, "[INFO] Registry expanded to capacity:", registry%capacity + end if + end subroutine expand_registry + + subroutine print_component_info(info) + type(component_info), intent(in) :: info + + if (info%order > 0) then + print *, " [", trim(info%category), ".", trim(info%name), & + " (order:", info%order, ")]" + else + print *, " [", trim(info%category), ".", trim(info%name), "]" + end if + end subroutine print_component_info + +end module registry_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/core/registry_initializer.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/core/registry_initializer.f90 new file mode 100644 index 000000000..44023d1dd --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/core/registry_initializer.f90 @@ -0,0 +1,39 @@ +! src/core/registry_initializer.f90 (新增文件) +module registry_initializer_module + use registry_module, only: register_component_simple + implicit none + private + public :: initialize_default_registry + +contains + + subroutine initialize_default_registry() + ! 注册重构器 + call register_component_simple("reconstructor", "eno", order=3) + call register_component_simple("reconstructor", "weno3", order=3) + call register_component_simple("reconstructor", "weno5", order=5) + + ! 注册通量计算器 + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + + ! 注册边界条件 + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + + ! 注册时间积分器 + call register_component_simple("integrator", "rk1", order=1) + call register_component_simple("integrator", "rk2", order=2) + call register_component_simple("integrator", "rk3", order=3) + + ! 注册方程 + call register_component_simple("equation", "linear_advection") + + ! 注册问题 + call register_component_simple("problem", "linear_advection") + + print *, "[REGISTRY] Default components registered" + end subroutine initialize_default_registry + +end module registry_initializer_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/infrastructure/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03g/src/infrastructure/CMakeLists.txt new file mode 100644 index 000000000..70cbbd2f3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/infrastructure/CMakeLists.txt @@ -0,0 +1,17 @@ +# src/infrastructure/CMakeLists.txt +message(STATUS "Configuring infrastructure module...") + +add_library(infrastructure STATIC + config.f90 + mesh.f90 + domain.f90 # 新增 + solution.f90 # 新增 +) + +target_link_libraries(infrastructure PRIVATE base) + +set_target_properties(infrastructure PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "Infrastructure module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/infrastructure/config.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/infrastructure/config.f90 new file mode 100644 index 000000000..7586a1a50 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/infrastructure/config.f90 @@ -0,0 +1,144 @@ +! src/infrastructure/config.f90 (修复版) +module config_module + use base_modules, only: wp, ip, max_name_len, cfd_config_base + + implicit none + public :: wp, ip, cfd_config, config_print, config_with_reconstruction + + ! 扩展配置类型 - 添加物理相关字段 + type, extends(cfd_config_base) :: cfd_config + ! 物理参数 + real(wp) :: left_boundary_value = 1.0_wp + real(wp) :: right_boundary_value = 2.0_wp + real(wp) :: domain_length = 2.0_wp + + ! 新增:物理模块相关配置 + real(wp) :: pulse_center = 0.5_wp ! 高斯脉冲中心 + real(wp) :: pulse_width = 0.1_wp ! 高斯脉冲宽度 + logical :: enable_physics = .true. ! 是否启用物理模块 + contains + ! 新增:物理相关配置方法 + procedure :: set_physics_parameters + procedure :: get_physics_info + end type cfd_config + +contains + + subroutine config_print(cfg) + type(cfd_config), intent(in) :: cfg + + print *, "=== CFD Configuration ===" + print *, "Initial condition: ", trim(cfg%ic_type) + print *, "Reconstruction: ", trim(cfg%recon_scheme), " (order:", cfg%spatial_order, ")" + print *, "Flux type: ", trim(cfg%flux_type) + print *, "Time integration: RK", cfg%rk_order + print *, "Wave speed: ", cfg%wave_speed + print *, "Final time: ", cfg%final_time + print *, "Time step: ", cfg%dt + print *, "Boundary: ", trim(cfg%boundary_type) + + ! 新增:物理配置信息 + print *, "--- Physics Configuration ---" + print *, "Equation type: ", trim(cfg%equation_type) + print *, "Problem type: ", trim(cfg%problem_type) + print *, "Domain length: ", cfg%domain_length + print *, "Physics enabled: ", cfg%enable_physics + + if (cfg%ic_type == "gaussian") then + print *, "Pulse center: ", cfg%pulse_center + print *, "Pulse width: ", cfg%pulse_width + end if + + print *, "===============================" + end subroutine config_print + + subroutine config_with_reconstruction(cfg, scheme, order) + type(cfd_config), intent(inout) :: cfg + character(len=*), intent(in) :: scheme + integer, optional, intent(in) :: order + + integer :: i + + ! 转换为小写 + cfg%recon_scheme = scheme + do i = 1, len_trim(cfg%recon_scheme) + if (cfg%recon_scheme(i:i) >= 'A' .and. cfg%recon_scheme(i:i) <= 'Z') then + cfg%recon_scheme(i:i) = char(ichar(cfg%recon_scheme(i:i)) + 32) + end if + end do + + ! 设置阶数 + if (present(order)) then + cfg%spatial_order = order + else + if (index(cfg%recon_scheme, 'weno') > 0) then + cfg%spatial_order = 5 + else if (trim(cfg%recon_scheme) == 'eno') then + cfg%spatial_order = 3 + end if + end if + + if (cfg%verbose) then + print *, "[CONFIG] Reconstruction: ", trim(cfg%recon_scheme), & + " Order: ", cfg%spatial_order + end if + end subroutine config_with_reconstruction + + ! ========== 新增:物理参数设置方法 ========== + + subroutine set_physics_parameters(this, equation_type, problem_type, & + domain_length, enable_physics) + class(cfd_config), intent(inout) :: this + character(len=*), intent(in), optional :: equation_type, problem_type + real(wp), intent(in), optional :: domain_length + logical, intent(in), optional :: enable_physics + + if (present(equation_type)) then + this%equation_type = trim(equation_type) + if (this%verbose) then + print *, "[CONFIG] Set equation type: ", trim(this%equation_type) + end if + end if + + if (present(problem_type)) then + this%problem_type = trim(problem_type) + if (this%verbose) then + print *, "[CONFIG] Set problem type: ", trim(this%problem_type) + end if + end if + + if (present(domain_length)) then + this%domain_length = domain_length + if (this%verbose) then + print *, "[CONFIG] Set domain length: ", this%domain_length + end if + end if + + if (present(enable_physics)) then + this%enable_physics = enable_physics + if (this%verbose) then + print *, "[CONFIG] Physics module enabled: ", this%enable_physics + end if + end if + end subroutine set_physics_parameters + + subroutine get_physics_info(this) + class(cfd_config), intent(in) :: this + + print *, "=== Physics Configuration Info ===" + print *, "Equation type: ", trim(this%equation_type) + print *, "Problem type: ", trim(this%problem_type) + print *, "Domain length: ", this%domain_length + print *, "Wave speed: ", this%wave_speed + print *, "Physics enabled: ", this%enable_physics + + if (this%ic_type == "gaussian") then + print *, "Pulse parameters:" + print *, " Center: ", this%pulse_center + print *, " Width: ", this%pulse_width + end if + + print *, "==================================" + end subroutine get_physics_info + +end module config_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/infrastructure/domain.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/infrastructure/domain.f90 new file mode 100644 index 000000000..641a9068c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/infrastructure/domain.f90 @@ -0,0 +1,102 @@ +! src/infrastructure/domain.f90(修正版) +module domain_module + use base_modules, only: wp, ip, max_name_len + use config_module, only: cfd_config + use mesh_module, only: mesh_type + + implicit none + private + public :: wp, ip, domain_type, domain_create, is_physical_cell + + type :: domain_type + type(cfd_config), pointer :: config => null() + type(mesh_type), pointer :: mesh => null() + integer(ip) :: nghosts = 0 + integer(ip) :: ist = 1 ! 物理区域起始索引(1-based) + integer(ip) :: ied = 1 ! 物理区域结束索引(exclusive) + integer(ip) :: ntcells = 0 ! 总单元数(含ghost) + contains + procedure :: print_info => domain_print_info + procedure :: get_physical_indices => domain_get_physical_indices + end type domain_type + +contains + + function domain_create(config, mesh) result(domain) + type(cfd_config), target, intent(in) :: config + type(mesh_type), target, intent(in) :: mesh + type(domain_type) :: domain + + domain%config => config + domain%mesh => mesh + + ! 计算ghost层数(参考Julia的_calc_nghosts) + domain%nghosts = calc_nghosts(config) + domain%ist = domain%nghosts + 1 + domain%ied = domain%ist + mesh%ncells + domain%ntcells = mesh%ncells + 2 * domain%nghosts + + if (config%verbose) then + print *, "[DOMAIN] Created:" + print *, " Ghost layers: ", domain%nghosts + print *, " Physical cells: ", domain%ist, " to ", domain%ied - 1 + print *, " Total cells: ", domain%ntcells + end if + end function domain_create + + function calc_nghosts(config) result(nghosts) + type(cfd_config), intent(in) :: config + integer(ip) :: nghosts + + character(len=max_name_len) :: scheme + + scheme = config%recon_scheme + + if (scheme == "eno") then + nghosts = config%spatial_order + else if (index(scheme, "weno") > 0) then + nghosts = config%spatial_order / 2 + 1 + else + print *, "[WARNING] Unknown scheme, using default nghosts=2" + nghosts = 2 + end if + + if (nghosts <= 0) then + print *, "[ERROR] Invalid nghosts: ", nghosts + nghosts = 2 + end if + end function calc_nghosts + + logical function is_physical_cell(this, idx) + class(domain_type), intent(in) :: this + integer(ip), intent(in) :: idx + is_physical_cell = (idx >= this%ist .and. idx < this%ied) + end function is_physical_cell + + function domain_get_physical_indices(this) result(indices) + class(domain_type), intent(in) :: this + integer(ip), allocatable :: indices(:) + integer(ip) :: i, count + + count = this%ied - this%ist + allocate(indices(count)) + + do i = 1, count + indices(i) = this%ist + i - 1 + end do + end function domain_get_physical_indices + + subroutine domain_print_info(this) + class(domain_type), intent(in) :: this + + print *, "=== Domain Information ===" + print *, "Configuration: ", trim(this%config%recon_scheme), & + " order ", this%config%spatial_order + print *, "Ghost layers: ", this%nghosts + print *, "Physical cells: ", this%ist, " to ", this%ied - 1 + print *, "Total cells: ", this%ntcells + print *, "Mesh cells: ", this%mesh%ncells + print *, "==========================" + end subroutine domain_print_info + +end module domain_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/infrastructure/mesh.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/infrastructure/mesh.f90 new file mode 100644 index 000000000..f810f3a1b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/infrastructure/mesh.f90 @@ -0,0 +1,73 @@ +! src/infrastructure/mesh.f90 +module mesh_module + use base_modules, only: wp, ip + + implicit none + public :: wp, ip, mesh_type, mesh_init, mesh_print_info + + ! 网格类型 + type :: mesh_type + real(wp) :: xmin = 0.0_wp + real(wp) :: xmax = 2.0_wp + integer(ip) :: ncells = 40 + integer(ip) :: nnodes + integer(ip) :: nx + real(wp), allocatable :: x(:) ! 节点坐标 + real(wp), allocatable :: xcc(:) ! 单元中心坐标 + real(wp) :: L, dx + contains + procedure :: init => mesh_init + procedure :: print_info => mesh_print_info + end type mesh_type + +contains + + subroutine mesh_init(this, xmin, xmax, ncells) + class(mesh_type), intent(inout) :: this + real(wp), optional, intent(in) :: xmin, xmax + integer(ip), optional, intent(in) :: ncells + + integer(ip) :: i + + ! 设置参数 + if (present(xmin)) this%xmin = xmin + if (present(xmax)) this%xmax = xmax + if (present(ncells)) this%ncells = ncells + + ! 计算 + this%nnodes = this%ncells + 1 + this%nx = this%ncells + this%L = this%xmax - this%xmin + this%dx = this%L / real(this%ncells, wp) + + ! 分配内存 + if (allocated(this%x)) deallocate(this%x) + if (allocated(this%xcc)) deallocate(this%xcc) + + allocate(this%x(this%nnodes)) + allocate(this%xcc(this%ncells)) + + ! 生成节点坐标 + do i = 1, this%nnodes + this%x(i) = this%xmin + (i - 1) * this%dx + end do + + ! 生成单元中心坐标 + do i = 1, this%ncells + this%xcc(i) = 0.5_wp * (this%x(i) + this%x(i + 1)) + end do + end subroutine mesh_init + + subroutine mesh_print_info(this) + class(mesh_type), intent(in) :: this + + print *, "=== Mesh Information ===" + print *, "Domain: [", this%xmin, ", ", this%xmax, "]" + print *, "Cells: ", this%ncells + print *, "Nodes: ", this%nnodes + print *, "dx: ", this%dx + print *, "L: ", this%L + print *, "========================" + end subroutine mesh_print_info + +end module mesh_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/infrastructure/solution.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/infrastructure/solution.f90 new file mode 100644 index 000000000..35e79671c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/infrastructure/solution.f90 @@ -0,0 +1,131 @@ +! src/infrastructure/solution.f90(修正版) +module solution_module + use base_modules, only: wp, ip + use domain_module, only: domain_type + + implicit none + private + public :: wp, ip, solution_type, solution_create, solution_reset + + type :: solution_type + type(domain_type), pointer :: domain => null() + real(wp), allocatable :: u(:) ! 当前解(含ghost) + real(wp), allocatable :: un(:) ! 旧解 + real(wp), allocatable :: q_face_left(:) ! 左界面值 + real(wp), allocatable :: q_face_right(:)! 右界面值 + real(wp), allocatable :: flux(:) ! 通量 + real(wp), allocatable :: res(:) ! 残差 + contains + procedure :: initialize => solution_initialize + procedure :: update_old_field => solution_update_old_field + procedure :: print_info => solution_print_info + procedure :: reset => solution_reset_instance + end type solution_type + +contains + + function solution_create(domain) result(solution) + type(domain_type), target, intent(in) :: domain + type(solution_type) :: solution + + integer(ip) :: ncells, nnodes, ntcells + + solution%domain => domain + + ncells = domain%mesh%ncells + nnodes = domain%mesh%nnodes + ntcells = domain%ntcells + + ! 分配数组(与Julia solution.jl一致) + allocate(solution%u(ntcells), source=0.0_wp) + allocate(solution%un(ntcells), source=0.0_wp) + allocate(solution%q_face_left(nnodes), source=0.0_wp) + allocate(solution%q_face_right(nnodes), source=0.0_wp) + allocate(solution%flux(nnodes), source=0.0_wp) + allocate(solution%res(ncells), source=0.0_wp) + + if (domain%config%verbose) then + print *, "[SOLUTION] Created:" + print *, " u size: ", size(solution%u), " (with ghosts)" + print *, " flux size: ", size(solution%flux) + print *, " res size: ", size(solution%res) + end if + end function solution_create + + subroutine solution_initialize(this, initial_values) + class(solution_type), intent(inout) :: this + real(wp), intent(in), optional :: initial_values(:) + + integer(ip) :: i, idx + type(domain_type), pointer :: domain + + domain => this%domain + + if (present(initial_values)) then + ! 应用初始值到物理区域 + do i = domain%ist, domain%ied - 1 + idx = i - domain%ist + 1 + if (idx <= size(initial_values)) then + this%u(i) = initial_values(idx) + end if + end do + else + ! 默认为0 + this%u = 0.0_wp + end if + + ! 同步旧场(与Julia的update_old_field一致) + call this%update_old_field() + + if (domain%config%verbose) then + print *, "[SOLUTION] Initialized" + print *, " u range: ", minval(this%u), " to ", maxval(this%u) + end if + end subroutine solution_initialize + + subroutine solution_update_old_field(this) + class(solution_type), intent(inout) :: this + this%un = this%u ! 与Julia的 un .= u 一致 + end subroutine solution_update_old_field + + subroutine solution_reset_instance(this) + class(solution_type), intent(inout) :: this + call solution_reset(this) + end subroutine solution_reset_instance + + subroutine solution_reset(solution) + type(solution_type), intent(inout) :: solution + + if (allocated(solution%u)) solution%u = 0.0_wp + if (allocated(solution%un)) solution%un = 0.0_wp + if (allocated(solution%q_face_left)) solution%q_face_left = 0.0_wp + if (allocated(solution%q_face_right)) solution%q_face_right = 0.0_wp + if (allocated(solution%flux)) solution%flux = 0.0_wp + if (allocated(solution%res)) solution%res = 0.0_wp + + if (associated(solution%domain) .and. solution%domain%config%verbose) then + print *, "[SOLUTION] Reset" + end if + end subroutine solution_reset + + subroutine solution_print_info(this) + class(solution_type), intent(in) :: this + + print *, "=== Solution Information ===" + print *, "Arrays:" + print *, " u: ", size(this%u), " elements" + print *, " un: ", size(this%un), " elements" + print *, " q_face_left: ", size(this%q_face_left), " elements" + print *, " q_face_right: ", size(this%q_face_right), " elements" + print *, " flux: ", size(this%flux), " elements" + print *, " res: ", size(this%res), " elements" + + if (allocated(this%u)) then + print *, "Values:" + print *, " u min/max: ", minval(this%u), maxval(this%u) + print *, " un min/max: ", minval(this%un), maxval(this%un) + end if + print *, "============================" + end subroutine solution_print_info + +end module solution_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/initial_condition/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03g/src/initial_condition/CMakeLists.txt new file mode 100644 index 000000000..01fbfa47b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/initial_condition/CMakeLists.txt @@ -0,0 +1,23 @@ +# src/initial_condition/CMakeLists.txt +message(STATUS "配置初始条件模块...") + +add_library(initial_condition STATIC + ic_base.f90 # 基类 + step.f90 # 阶跃函数 + sine.f90 # 正弦波 + gaussian.f90 # 高斯脉冲 + factory.f90 # 工厂 +) + +target_link_libraries(initial_condition + PRIVATE + base + infrastructure + core # 需要注册系统 +) + +set_target_properties(initial_condition PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "初始条件模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/initial_condition/factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/initial_condition/factory.f90 new file mode 100644 index 000000000..7fbdd5220 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/initial_condition/factory.f90 @@ -0,0 +1,31 @@ +! src/initial_condition/factory.f90 +module ic_factory_module + use base_modules, only: wp, ip + use ic_base_module, only: initial_condition + use step_ic_module, only: step_function_ic + implicit none + private + + public :: create_initial_condition, initialize_ic_factory + +contains + + subroutine initialize_ic_factory() + print *, "[IC FACTORY] Initial conditions placeholder" + end subroutine + + subroutine create_initial_condition(ic_type, ic_instance) + character(len=*), intent(in) :: ic_type + class(initial_condition), allocatable, intent(out) :: ic_instance + + ! 暂时只实现步函数 + allocate(step_function_ic :: ic_instance) + + select type(ic => ic_instance) + type is (step_function_ic) + ! 使用默认构造函数 + ic%name = "step" + end select + end subroutine + +end module ic_factory_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/initial_condition/gaussian.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/initial_condition/gaussian.f90 new file mode 100644 index 000000000..ed2b625b7 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/initial_condition/gaussian.f90 @@ -0,0 +1,62 @@ +! src/initial_condition/gaussian.f90 +module gaussian_ic_module + use base_modules, only: wp, ip + use ic_base_module, only: initial_condition + use solution_module, only: solution_type + use domain_module, only: domain_type + implicit none + private + + type, extends(initial_condition), public :: gaussian_pulse_ic + contains + procedure :: evaluate_at => gaussian_evaluate_at + procedure :: apply => gaussian_apply + end type + + interface gaussian_pulse_ic + module procedure create_gaussian_pulse_ic + end interface + +contains + + type(gaussian_pulse_ic) function create_gaussian_pulse_ic() result(this) + this%name = "gaussian" + end function + + function gaussian_evaluate_at(this, x) result(u) + class(gaussian_pulse_ic), intent(in) :: this + real(wp), intent(in) :: x(:) + real(wp) :: u(size(x)) + + integer :: i + real(wp) :: center, width + + center = 0.5_wp ! 脉冲中心 + width = 0.1_wp ! 脉冲宽度 + + do i = 1, size(x) + u(i) = exp(-((x(i) - center) / width)**2) + end do + end function + + subroutine gaussian_apply(this, solution) + class(gaussian_pulse_ic), intent(in) :: this + type(solution_type), intent(inout) :: solution + + integer :: i, idx + real(wp), allocatable :: u0(:) + type(domain_type), pointer :: domain + + domain => solution%domain + + ! 评估初始条件 + u0 = this%evaluate_at(domain%mesh%xcc) + + ! 应用到物理区域 + do i = domain%ist, domain%ied - 1 + idx = i - domain%ist + 1 + solution%u(i) = u0(idx) + end do + end subroutine + +end module gaussian_ic_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/initial_condition/ic_base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/initial_condition/ic_base.f90 new file mode 100644 index 000000000..4a7845fe0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/initial_condition/ic_base.f90 @@ -0,0 +1,43 @@ +! src/initial_condition/ic_base.f90 +module ic_base_module + use base_modules, only: wp, ip + use solution_module, only: solution_type + implicit none + private + + type, abstract, public :: initial_condition + character(len=:), allocatable :: name + contains + procedure :: get_name => ic_get_name + procedure(evaluate_interface), deferred :: evaluate_at + procedure(apply_interface), deferred :: apply + end type + + abstract interface + function evaluate_interface(this, x) result(u) + import :: initial_condition, wp + class(initial_condition), intent(in) :: this + real(wp), intent(in) :: x(:) + real(wp) :: u(size(x)) + end function evaluate_interface + + subroutine apply_interface(this, solution) + import :: initial_condition, solution_type + class(initial_condition), intent(in) :: this + type(solution_type), intent(inout) :: solution + end subroutine apply_interface + end interface + +contains + + function ic_get_name(this) result(name) + class(initial_condition), intent(in) :: this + character(len=:), allocatable :: name + if (allocated(this%name)) then + name = this%name + else + name = "unnamed" + end if + end function + +end module ic_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/initial_condition/sine.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/initial_condition/sine.f90 new file mode 100644 index 000000000..f9f1028c3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/initial_condition/sine.f90 @@ -0,0 +1,62 @@ +! src/initial_condition/sine.f90 +module sine_ic_module + use base_modules, only: wp, ip + use ic_base_module, only: initial_condition + use solution_module, only: solution_type + use domain_module, only: domain_type + implicit none + private + + type, extends(initial_condition), public :: sine_wave_ic + contains + procedure :: evaluate_at => sine_evaluate_at + procedure :: apply => sine_apply + end type + + interface sine_wave_ic + module procedure create_sine_wave_ic + end interface + +contains + + type(sine_wave_ic) function create_sine_wave_ic() result(this) + this%name = "sin" + end function + + function sine_evaluate_at(this, x) result(u) + class(sine_wave_ic), intent(in) :: this + real(wp), intent(in) :: x(:) + real(wp) :: u(size(x)) + + integer :: i + real(wp) :: L + + ! 假设域长度,可以根据需要调整 + L = 2.0_wp ! 默认域长度 + + do i = 1, size(x) + u(i) = sin(2.0_wp * 3.141592653589793_wp * x(i) / L) + end do + end function + + subroutine sine_apply(this, solution) + class(sine_wave_ic), intent(in) :: this + type(solution_type), intent(inout) :: solution + + integer :: i, idx + real(wp), allocatable :: u0(:) + type(domain_type), pointer :: domain + + domain => solution%domain + + ! 评估初始条件 + u0 = this%evaluate_at(domain%mesh%xcc) + + ! 应用到物理区域 + do i = domain%ist, domain%ied - 1 + idx = i - domain%ist + 1 + solution%u(i) = u0(idx) + end do + end subroutine + +end module sine_ic_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/initial_condition/step.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/initial_condition/step.f90 new file mode 100644 index 000000000..dca60265b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/initial_condition/step.f90 @@ -0,0 +1,62 @@ +! src/initial_condition/step.f90 +module step_ic_module + use base_modules, only: wp, ip + use ic_base_module, only: initial_condition + use solution_module, only: solution_type + use domain_module, only: domain_type + implicit none + private + + type, extends(initial_condition), public :: step_function_ic + contains + procedure :: evaluate_at => step_evaluate_at + procedure :: apply => step_apply + end type + + interface step_function_ic + module procedure create_step_function_ic + end interface + +contains + + type(step_function_ic) function create_step_function_ic() result(this) + this%name = "step" + end function + + function step_evaluate_at(this, x) result(u) + class(step_function_ic), intent(in) :: this + real(wp), intent(in) :: x(:) + real(wp) :: u(size(x)) + + integer :: i + + do i = 1, size(x) + if (x(i) >= 0.5_wp .and. x(i) <= 1.0_wp) then + u(i) = 2.0_wp + else + u(i) = 1.0_wp + end if + end do + end function + + subroutine step_apply(this, solution) + class(step_function_ic), intent(in) :: this + type(solution_type), intent(inout) :: solution + + integer :: i, idx + real(wp), allocatable :: u0(:) + type(domain_type), pointer :: domain + + domain => solution%domain + + ! 评估初始条件 + u0 = this%evaluate_at(domain%mesh%xcc) + + ! 应用到物理区域 + do i = domain%ist, domain%ied - 1 + idx = i - domain%ist + 1 + solution%u(i) = u0(idx) + end do + end subroutine + +end module step_ic_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/manager/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03g/src/manager/CMakeLists.txt new file mode 100644 index 000000000..00c8bf49b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/manager/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/manager/CMakeLists.txt +message(STATUS "配置管理器模块...") + +# 创建管理器库 +add_library(manager STATIC + component_manager.f90 + component_factory.f90 +) + +# 明确依赖关系:管理器依赖所有其他模块 +target_link_libraries(manager + PRIVATE + core + infrastructure + reconstructor + flux +) + +# 设置模块输出目录 +set_target_properties(manager PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "管理器模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/manager/component_factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/manager/component_factory.f90 new file mode 100644 index 000000000..bafceddbe --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/manager/component_factory.f90 @@ -0,0 +1,127 @@ +! src/manager/component_factory.f90 +module component_factory_module + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use weno5_reconstructor_module, only: weno5_reconstructor + use rusanov_flux_module, only: rusanov_flux + + implicit none + private + public :: wp, create_reconstructor, create_flux_calculator + + ! 定义wp以保持兼容性 + integer, parameter :: wp = real64 + + ! 错误代码 + integer, parameter :: CM_SUCCESS = 0 + integer, parameter :: CM_ERROR_UNKNOWN_SCHEME = 1 + integer, parameter :: CM_ERROR_UNKNOWN_FLUX = 2 + integer, parameter :: CM_ERROR_INVALID_ORDER = 3 + +contains + + ! ==================== 重构器创建 ==================== + + function create_reconstructor(config, status) result(recon) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(reconstructor_base), allocatable :: recon + + character(len=20) :: scheme + integer :: order, error_code + + scheme = trim(adjustl(config%recon_scheme)) + order = config%spatial_order + + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating reconstructor: ", scheme, " order=", order + end if + + ! 处理"weno"作为WENO5的别名 + if (scheme == "weno" .and. order == 5) then + scheme = "weno5" + end if + + select case(scheme) + case('eno') + allocate(eno_reconstructor :: recon) + select type(recon) + type is(eno_reconstructor) + recon%order = order + end select + + case('weno3') + allocate(weno3_reconstructor :: recon) + select type(recon) + type is(weno3_reconstructor) + recon%order = order + end select + + case('weno5') + allocate(weno5_reconstructor :: recon) + select type(recon) + type is(weno5_reconstructor) + recon%order = order + end select + + case default + error_code = CM_ERROR_UNKNOWN_SCHEME + if (config%verbose) then + print *, "[ERROR] Unknown reconstructor scheme: ", scheme + print *, " Available: eno, weno3, weno5" + end if + end select + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Reconstructor creation failed" + end if + end function create_reconstructor + + ! ==================== 通量计算器创建 ==================== + + function create_flux_calculator(config, status) result(flux) + type(cfd_config), intent(in) :: config + integer, optional, intent(out) :: status + class(flux_calculator_base), allocatable :: flux + + character(len=20) :: flux_type + integer :: error_code + + flux_type = trim(adjustl(config%flux_type)) + error_code = CM_SUCCESS + + if (config%verbose) then + print *, "[COMPONENT FACTORY] Creating flux calculator: ", flux_type + end if + + select case(flux_type) + case('rusanov') + allocate(rusanov_flux :: flux) + + case default + error_code = CM_ERROR_UNKNOWN_FLUX + if (config%verbose) then + print *, "[ERROR] Unknown flux type: ", flux_type + print *, " Available: rusanov" + end if + end select + + ! 设置状态码 + if (present(status)) then + status = error_code + else if (error_code /= CM_SUCCESS) then + error stop "Flux calculator creation failed" + end if + end function create_flux_calculator + +end module component_factory_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/manager/component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/manager/component_manager.f90 new file mode 100644 index 000000000..70d00991f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/manager/component_manager.f90 @@ -0,0 +1,48 @@ +! src/manager/component_manager.f90 +module component_manager_module + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config + + implicit none + private + public :: wp, component_manager_info, validate_config + + ! 定义wp以保持兼容性 + integer, parameter :: wp = real64 + +contains + + ! ==================== 配置验证 ==================== + + function validate_config(config) result(is_valid) + type(cfd_config), intent(in) :: config + logical :: is_valid + + ! 简单验证 + is_valid = .true. + + if (config%verbose) then + print *, "[CONFIG VALIDATION] Configuration is valid (simplified)" + end if + end function validate_config + + ! ==================== 信息显示 ==================== + + subroutine component_manager_info() + print *, "=== Component Manager ===" + print *, "Available reconstructors:" + print *, " - eno (orders: 1-7)" + print *, " - weno3 (order: 3)" + print *, " - weno5 (order: 5)" + print *, "" + print *, "Available flux calculators:" + print *, " - rusanov" + print *, "" + print *, "Features:" + print *, " - Configuration validation" + print *, " - Component creation from config" + print *, " - Error handling with status codes" + print *, "=========================" + end subroutine component_manager_info + +end module component_manager_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/CMakeLists.txt new file mode 100644 index 000000000..95996da6c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/CMakeLists.txt @@ -0,0 +1,8 @@ +# src/numerics/CMakeLists.txt +message(STATUS "配置数值方法模块...") + +add_subdirectory(reconstructor) +add_subdirectory(flux) +add_subdirectory(time_integration) # 新增 + +message(STATUS "数值方法模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/flux/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/flux/CMakeLists.txt new file mode 100644 index 000000000..3fde7746b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/flux/CMakeLists.txt @@ -0,0 +1,28 @@ +# src/numerics/flux/CMakeLists.txt +message(STATUS "Configuring flux calculator module...") + +# Start with just the base module +add_library(flux STATIC + base.f90 + rusanov.f90 +) + +#target_link_libraries(flux PRIVATE core infrastructure) + +target_include_directories(flux PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 明确设置不使用 /Qm64 选项 +if(CMAKE_Fortran_COMPILER_ID MATCHES "IntelLLVM|Intel") + set_target_properties(flux PROPERTIES + Fortran_FLAGS "${CMAKE_Fortran_FLAGS} /Qm64" # 明确设置,避免继承问题 + ) +endif() + +install(TARGETS flux + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Flux calculator module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/flux/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/flux/base.f90 new file mode 100644 index 000000000..7080a7ae4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/flux/base.f90 @@ -0,0 +1,30 @@ +!src/numerics/flux/base.f90 +module flux_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, flux_calculator_base + + ! Base flux calculator type + type :: flux_calculator_base + character(len=20) :: name = "Base" + contains + procedure :: info => flux_info + procedure :: print_basic_info => flux_print_basic ! 添加辅助方法 + end type flux_calculator_base + +contains + + subroutine flux_info(this) + class(flux_calculator_base), intent(in) :: this + print *, "Flux calculator information:" + print *, " Name: ", trim(this%name) + end subroutine flux_info + + subroutine flux_print_basic(this) + class(flux_calculator_base), intent(in) :: this + print *, " Name: ", trim(this%name) + end subroutine flux_print_basic + +end module flux_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/flux/engquist_osher.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/flux/engquist_osher.f90 new file mode 100644 index 000000000..90f499d12 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/flux/engquist_osher.f90 @@ -0,0 +1,41 @@ +! src/numerics/flux/engquist_osher.f90 +module engquist_osher_flux + use base_modules, only: wp, ip + use flux_base_module, only: flux_calculator_base + implicit none + private + + type, extends(flux_calculator_base), public :: engquist_osher_flux_t + real(wp) :: wave_speed = 1.0_wp + contains + procedure :: compute => eo_compute + end type + +contains + + subroutine eo_compute(this, qL, qR, flux, equation) + class(engquist_osher_flux_t), intent(inout) :: this + real(wp), intent(in) :: qL(:), qR(:) + real(wp), intent(out) :: flux(:) + class(*), intent(in) :: equation + + integer :: i, n + real(wp) :: cp, cm, uL, uR + + select type(eq => equation) + type is (linear_advection_eq) + cp = 0.5_wp * (eq%wave_speed + abs(eq%wave_speed)) + cm = 0.5_wp * (eq%wave_speed - abs(eq%wave_speed)) + + n = size(qL) + do i = 1, n + uL = qL(i) + uR = qR(i) + flux(i) = cp * uL + cm * uR + end do + class default + error stop "Unsupported equation type for Engquist-Osher flux" + end select + end subroutine + +end module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/flux/rusanov.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/flux/rusanov.f90 new file mode 100644 index 000000000..daa9e3bb5 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/flux/rusanov.f90 @@ -0,0 +1,41 @@ +! src/numerics/flux/rusanov.f90 +module rusanov_flux_module + use, intrinsic :: iso_fortran_env, only: real64 + use flux_base_module, only: flux_calculator_base + implicit none + + private + public :: real64, rusanov_flux, create_rusanov_flux + + type, extends(flux_calculator_base) :: rusanov_flux + real(real64) :: wave_speed_default = 1.0_real64 + contains + procedure :: info => rusanov_info + end type rusanov_flux + + ! 构造函数接口 + interface rusanov_flux + module procedure create_rusanov_flux + end interface + +contains + + ! 构造函数 + type(rusanov_flux) function create_rusanov_flux() result(this) + this%name = "Rusanov" + this%wave_speed_default = 1.0_real64 + end function create_rusanov_flux + + subroutine rusanov_info(this) + class(rusanov_flux), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Flux calculator information:" + call this%print_basic_info() + + ! 添加Rusanov特有信息 + print *, " Type: Rusanov flux" + print *, " Default wave speed: ", this%wave_speed_default + end subroutine rusanov_info + +end module rusanov_flux_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/reconstructor/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/reconstructor/CMakeLists.txt new file mode 100644 index 000000000..c88ea647b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/reconstructor/CMakeLists.txt @@ -0,0 +1,23 @@ +# src/numerics/reconstructor/CMakeLists.txt +message(STATUS "Configuring reconstructor module...") + +# Start with just the base module +add_library(reconstructor STATIC + base.f90 + eno.f90 + weno3.f90 + weno5.f90 +) + +target_link_libraries(reconstructor PRIVATE core infrastructure) + +target_include_directories(reconstructor PUBLIC + ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +install(TARGETS reconstructor + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) + +message(STATUS "Reconstructor module configured") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/reconstructor/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/reconstructor/base.f90 new file mode 100644 index 000000000..53798d021 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/reconstructor/base.f90 @@ -0,0 +1,33 @@ +!src/numerics/reconstructor/base.f90 +module reconstructor_base_module + use, intrinsic :: iso_fortran_env, only: real64 + implicit none + + private + public :: real64, reconstructor_base + + ! Base reconstructor type + type :: reconstructor_base + integer :: order = 1 + character(len=20) :: name = "Base" + contains + procedure :: info => reconstructor_info + procedure :: print_basic_info => reconstructor_print_basic ! 添加一个辅助方法 + end type reconstructor_base + +contains + + subroutine reconstructor_info(this) + class(reconstructor_base), intent(in) :: this + print *, "Reconstructor information:" + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_info + + subroutine reconstructor_print_basic(this) + class(reconstructor_base), intent(in) :: this + print *, " Name: ", trim(this%name) + print *, " Order: ", this%order + end subroutine reconstructor_print_basic + +end module reconstructor_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/reconstructor/eno.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/reconstructor/eno.f90 new file mode 100644 index 000000000..f973e8b32 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/reconstructor/eno.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/eno.f90 +module eno_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, eno_reconstructor, create_eno_reconstructor ! ← 添加这个 + + type, extends(reconstructor_base) :: eno_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => eno_info + end type eno_reconstructor + + ! 构造函数接口 + interface eno_reconstructor + module procedure create_eno_reconstructor + end interface + +contains + + ! 构造函数 + type(eno_reconstructor) function create_eno_reconstructor() result(this) + this%name = "ENO" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_eno_reconstructor + + subroutine eno_info(this) + class(eno_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加ENO特有信息 + print *, " Type: ENO reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine eno_info + +end module eno_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/reconstructor/weno3.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/reconstructor/weno3.f90 new file mode 100644 index 000000000..a8faa577f --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/reconstructor/weno3.f90 @@ -0,0 +1,40 @@ +! src/numerics/reconstructor/weno3.f90 +module weno3_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + private + + type, extends(reconstructor_base), public :: weno3_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno3_info + end type weno3_reconstructor + + ! 构造函数接口 - 使用类型名本身作为构造函数 + interface weno3_reconstructor + module procedure create_weno3_reconstructor + end interface + +contains + + ! 构造函数 + type(weno3_reconstructor) function create_weno3_reconstructor() result(this) + this%name = "WENO3" + this%order = 3 + this%epsilon = 1.0e-6_real64 + end function create_weno3_reconstructor + + subroutine weno3_info(this) + class(weno3_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加WENO3特有信息 + print *, " Type: WENO-3 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno3_info + +end module weno3_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/reconstructor/weno5.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/reconstructor/weno5.f90 new file mode 100644 index 000000000..a869c67d0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/reconstructor/weno5.f90 @@ -0,0 +1,42 @@ +! src/numerics/reconstructor/weno5.f90(新增) +module weno5_reconstructor_module + use, intrinsic :: iso_fortran_env, only: real64 + use reconstructor_base_module, only: reconstructor_base + implicit none + + private + public :: real64, weno5_reconstructor, create_weno5_reconstructor + + type, extends(reconstructor_base) :: weno5_reconstructor + real(real64) :: epsilon = 1.0e-6_real64 + contains + procedure :: info => weno5_info + end type weno5_reconstructor + + ! 构造函数接口 + interface weno5_reconstructor + module procedure create_weno5_reconstructor + end interface + +contains + + ! 构造函数 + type(weno5_reconstructor) function create_weno5_reconstructor() result(this) + this%name = "WENO5" + this%order = 5 + this%epsilon = 1.0e-6_real64 + end function create_weno5_reconstructor + + subroutine weno5_info(this) + class(weno5_reconstructor), intent(in) :: this + + ! 调用父类的基础信息打印 + print *, "Reconstructor information:" + call this%print_basic_info() + + ! 添加WENO5特有信息 + print *, " Type: WENO-5 reconstructor" + print *, " Epsilon: ", this%epsilon + end subroutine weno5_info + +end module weno5_reconstructor_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/time_integration/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/time_integration/CMakeLists.txt new file mode 100644 index 000000000..2938656bf --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/time_integration/CMakeLists.txt @@ -0,0 +1,24 @@ +# src/numerics/time_integration/CMakeLists.txt +message(STATUS "配置时间积分模块...") + +add_library(time_integration STATIC + base.f90 + rk1.f90 + rk2.f90 + rk3.f90 + factory.f90 +) + +target_link_libraries(time_integration + PRIVATE + infrastructure + base + manager + solver +) + +set_target_properties(time_integration PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "时间积分模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/time_integration/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/time_integration/base.f90 new file mode 100644 index 000000000..8f010e928 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/time_integration/base.f90 @@ -0,0 +1,77 @@ +! src/numerics/time_integration/base.f90 +module time_integration_base_module + use base_modules, only: wp, ip + use config_module, only: cfd_config + use domain_module, only: domain_type + use solution_module, only: solution_type + use residual_module, only: residual_calculator + use boundary_base_module, only: boundary_condition + + implicit none + private + + type, abstract, public :: time_integrator_base + type(cfd_config), pointer :: config => null() + type(domain_type), pointer :: domain => null() + type(solution_type), pointer :: solution => null() + type(residual_calculator), pointer :: residual_calc => null() + class(boundary_condition), pointer :: bc => null() + contains + procedure :: initialize => integrator_initialize + procedure(step_interface), deferred :: step + procedure :: compute_residual => integrator_compute_residual + procedure :: apply_boundary => integrator_apply_boundary + procedure :: map_idx => integrator_map_idx + end type time_integrator_base + + abstract interface + subroutine step_interface(this, dt) + import :: time_integrator_base, wp + class(time_integrator_base), intent(inout) :: this + real(wp), intent(in) :: dt + end subroutine step_interface + end interface + +contains + + subroutine integrator_initialize(this, config, domain, solution, residual_calc, bc) + class(time_integrator_base), intent(inout) :: this + type(cfd_config), target, intent(in) :: config + type(domain_type), target, intent(in) :: domain + type(solution_type), target, intent(in) :: solution + type(residual_calculator), target, intent(in) :: residual_calc + class(boundary_condition), target, intent(in) :: bc + + this%config => config + this%domain => domain + this%solution => solution + this%residual_calc => residual_calc + this%bc => bc + end subroutine integrator_initialize + + subroutine integrator_compute_residual(this) + class(time_integrator_base), intent(inout) :: this + + if (associated(this%residual_calc) .and. associated(this%solution)) then + call this%residual_calc%compute(this%solution%u) + end if + end subroutine integrator_compute_residual + + subroutine integrator_apply_boundary(this) + class(time_integrator_base), intent(inout) :: this + + if (associated(this%bc) .and. associated(this%solution)) then + call this%bc%apply(this%solution%u, & + this%domain%nghosts, & + this%domain%ist, & + this%domain%ied - 1) + end if + end subroutine integrator_apply_boundary + + integer function integrator_map_idx(this, i) result(idx) + class(time_integrator_base), intent(in) :: this + integer(ip), intent(in) :: i + idx = i - this%domain%ist + 1 + end function integrator_map_idx + +end module time_integration_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/time_integration/base_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/time_integration/base_simple.f90 new file mode 100644 index 000000000..3631e65be --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/time_integration/base_simple.f90 @@ -0,0 +1,53 @@ +! src/numerics/time_integration/base_simple.f90 +module time_integration_base_simple_module + use base_modules, only: wp, ip + use config_module, only: cfd_config + use domain_module, only: domain_type + use residual_simple_module, only: residual_calculator_simple + + implicit none + private + + type, abstract, public :: time_integrator_base_simple + type(cfd_config), pointer :: config => null() + type(domain_type), pointer :: domain => null() + type(residual_calculator_simple), pointer :: residual_calc => null() + contains + procedure :: initialize => integrator_simple_initialize + procedure(step_interface), deferred :: step + procedure :: compute_residual => integrator_simple_compute_residual + end type time_integrator_base_simple + + abstract interface + subroutine step_interface(this, u, dt) + import :: time_integrator_base_simple, wp + class(time_integrator_base_simple), intent(inout) :: this + real(wp), intent(inout) :: u(:) + real(wp), intent(in) :: dt + end subroutine step_interface + end interface + +contains + + subroutine integrator_simple_initialize(this, config, domain, residual_calc) + class(time_integrator_base_simple), intent(inout) :: this + type(cfd_config), target, intent(in) :: config + type(domain_type), target, intent(in) :: domain + type(residual_calculator_simple), target, intent(in) :: residual_calc + + this%config => config + this%domain => domain + this%residual_calc => residual_calc + end subroutine integrator_simple_initialize + + subroutine integrator_simple_compute_residual(this, u, res) + class(time_integrator_base_simple), intent(inout) :: this + real(wp), intent(in) :: u(:) + real(wp), intent(out) :: res(:) + + if (associated(this%residual_calc)) then + call this%residual_calc%compute(u, res) + end if + end subroutine integrator_simple_compute_residual + +end module time_integration_base_simple_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/time_integration/factory.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/time_integration/factory.f90 new file mode 100644 index 000000000..4b454e44c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/time_integration/factory.f90 @@ -0,0 +1,51 @@ +! src/numerics/time_integration/factory.f90 +module time_integrator_factory_module + use base_modules, only: wp, ip + use time_integration_base_module, only: time_integrator_base + use rk1_integrator_module, only: rk1_integrator + use rk2_integrator_module, only: rk2_integrator + use rk3_integrator_module, only: rk3_integrator + + implicit none + private + + public :: create_time_integrator + +contains + + function create_time_integrator(rk_order, config, domain, solution, residual_calc, bc) & + result(integrator) + integer, intent(in) :: rk_order + type(cfd_config), target, intent(in) :: config + type(domain_type), target, intent(in) :: domain + type(solution_type), target, intent(in) :: solution + type(residual_calculator), target, intent(in) :: residual_calc + class(boundary_condition), target, intent(in) :: bc + class(time_integrator_base), allocatable :: integrator + + select case (rk_order) + case (1) + allocate(rk1_integrator :: integrator) + + case (2) + allocate(rk2_integrator :: integrator) + + case (3) + allocate(rk3_integrator :: integrator) + + case default + if (config%verbose) then + print *, "[WARNING] Unsupported RK order: ", rk_order, ", using RK2 as default" + end if + allocate(rk2_integrator :: integrator) + end select + + ! 初始化积分器 + call integrator%initialize(config, domain, solution, residual_calc, bc) + + if (config%verbose) then + print *, "[TIME INTEGRATION] Created RK", rk_order, " integrator" + end if + end function create_time_integrator + +end module time_integrator_factory_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/time_integration/rk1.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/time_integration/rk1.f90 new file mode 100644 index 000000000..750f7858c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/time_integration/rk1.f90 @@ -0,0 +1,43 @@ +! src/numerics/time_integration/rk1.f90 +module rk1_integrator_module + use base_modules, only: wp, ip + use time_integration_base_module, only: time_integrator_base + use solution_module, only: update_old_field + + implicit none + private + + type, extends(time_integrator_base), public :: rk1_integrator + contains + procedure :: step => rk1_step + end type rk1_integrator + +contains + + subroutine rk1_step(this, dt) + class(rk1_integrator), intent(inout) :: this + real(wp), intent(in) :: dt + + integer(ip) :: i, idx + + ! 计算残差 + call this%compute_residual() + + ! 更新解 (一阶RK = 欧拉方法) + do i = this%domain%ist, this%domain%ied - 1 + idx = this%map_idx(i) + this%solution%u(i) = this%solution%u(i) + dt * this%solution%res(idx) + end do + + ! 应用边界条件 + call this%apply_boundary() + + ! 更新旧场 + call this%solution%update_old_field() + + if (this%config%verbose .and. mod(this%solution%current_step, 100) == 0) then + print *, " [RK1] Step completed" + end if + end subroutine rk1_step + +end module rk1_integrator_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/time_integration/rk2.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/time_integration/rk2.f90 new file mode 100644 index 000000000..c329cbaaa --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/time_integration/rk2.f90 @@ -0,0 +1,76 @@ +! src/numerics/time_integration/rk2.f90 +module rk2_integrator_module + use base_modules, only: wp, ip + use time_integration_base_module, only: time_integrator_base + use solution_module, only: update_old_field + + implicit none + private + + type, extends(time_integrator_base), public :: rk2_integrator + contains + procedure :: step => rk2_step + end type rk2_integrator + +contains + + subroutine rk2_step(this, dt) + class(rk2_integrator), intent(inout) :: this + real(wp), intent(in) :: dt + + integer(ip) :: i, idx + real(wp), allocatable :: u_pred(:) + integer(ip) :: n_cells + + ! 获取物理区域大小 + n_cells = this%domain%ied - this%domain%ist + + ! 分配预测解数组 + allocate(u_pred(this%domain%ist:this%domain%ied - 1)) + + ! 阶段1: 计算预测值 + call this%compute_residual() + do i = this%domain%ist, this%domain%ied - 1 + idx = this%map_idx(i) + u_pred(i) = this%solution%u(i) + dt * this%solution%res(idx) + end do + + ! 应用边界条件到预测值 + if (associated(this%bc)) then + call this%bc%apply(u_pred, & + this%domain%nghosts, & + this%domain%ist, & + this%domain%ied - 1) + end if + + ! 阶段2: 计算修正值 + ! 将预测值复制回当前解 + this%solution%u(this%domain%ist:this%domain%ied - 1) = & + u_pred(this%domain%ist:this%domain%ied - 1) + + ! 再次计算残差 + call this%compute_residual() + + ! 计算最终值 (Heun方法: u^{n+1} = 0.5*u^n + 0.5*(u_pred + dt*res(u_pred))) + do i = this%domain%ist, this%domain%ied - 1 + idx = this%map_idx(i) + this%solution%u(i) = 0.5_wp * this%solution%un(i) + & + 0.5_wp * this%solution%u(i) + & + 0.5_wp * dt * this%solution%res(idx) + end do + + ! 应用边界条件 + call this%apply_boundary() + + ! 更新旧场 + call this%solution%update_old_field() + + ! 清理 + deallocate(u_pred) + + if (this%config%verbose .and. mod(this%solution%current_step, 100) == 0) then + print *, " [RK2] Step completed" + end if + end subroutine rk2_step + +end module rk2_integrator_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/time_integration/rk3.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/time_integration/rk3.f90 new file mode 100644 index 000000000..b84b970dd --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/numerics/time_integration/rk3.f90 @@ -0,0 +1,98 @@ +! src/numerics/time_integration/rk3.f90 +module rk3_integrator_module + use base_modules, only: wp, ip + use time_integration_base_module, only: time_integrator_base + use solution_module, only: update_old_field + + implicit none + private + + type, extends(time_integrator_base), public :: rk3_integrator + contains + procedure :: step => rk3_step + end type rk3_integrator + +contains + + subroutine rk3_step(this, dt) + class(rk3_integrator), intent(inout) :: this + real(wp), intent(in) :: dt + + integer(ip) :: i, idx + real(wp), allocatable :: u1(:), u2(:) + integer(ip) :: n_cells + + ! 获取物理区域大小 + n_cells = this%domain%ied - this%domain%ist + + ! 分配中间解数组 + allocate(u1(this%domain%ist:this%domain%ied - 1)) + allocate(u2(this%domain%ist:this%domain%ied - 1)) + + ! === 阶段1 === + call this%compute_residual() + do i = this%domain%ist, this%domain%ied - 1 + idx = this%map_idx(i) + u1(i) = this%solution%u(i) + dt * this%solution%res(idx) + end do + + ! 应用边界条件到u1 + if (associated(this%bc)) then + call this%bc%apply(u1, & + this%domain%nghosts, & + this%domain%ist, & + this%domain%ied - 1) + end if + + ! === 阶段2 === + ! 将u1复制回当前解 + this%solution%u(this%domain%ist:this%domain%ied - 1) = & + u1(this%domain%ist:this%domain%ied - 1) + + call this%compute_residual() + + do i = this%domain%ist, this%domain%ied - 1 + idx = this%map_idx(i) + u2(i) = 0.75_wp * this%solution%un(i) + & + 0.25_wp * this%solution%u(i) + & + 0.25_wp * dt * this%solution%res(idx) + end do + + ! 应用边界条件到u2 + if (associated(this%bc)) then + call this%bc%apply(u2, & + this%domain%nghosts, & + this%domain%ist, & + this%domain%ied - 1) + end if + + ! === 阶段3 === + ! 将u2复制回当前解 + this%solution%u(this%domain%ist:this%domain%ied - 1) = & + u2(this%domain%ist:this%domain%ied - 1) + + call this%compute_residual() + + ! TVD RK3系数 + do i = this%domain%ist, this%domain%ied - 1 + idx = this%map_idx(i) + this%solution%u(i) = (1.0_wp/3.0_wp) * this%solution%un(i) + & + (2.0_wp/3.0_wp) * this%solution%u(i) + & + (2.0_wp/3.0_wp) * dt * this%solution%res(idx) + end do + + ! 最终边界条件 + call this%apply_boundary() + + ! 更新旧场 + call this%solution%update_old_field() + + ! 清理 + deallocate(u1, u2) + + if (this%config%verbose .and. mod(this%solution%current_step, 100) == 0) then + print *, " [RK3] Step completed" + end if + end subroutine rk3_step + +end module rk3_integrator_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/physics/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03g/src/physics/CMakeLists.txt new file mode 100644 index 000000000..cc4e233ab --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/physics/CMakeLists.txt @@ -0,0 +1,19 @@ +# src/physics/CMakeLists.txt +message(STATUS "配置物理模块...") + +# 创建物理模块库 +add_library(physics STATIC + physics_interface.f90 + equations/linear_convection.f90 + problems/linear_convection_problem.f90 +) + +# 链接依赖 +target_link_libraries(physics PRIVATE base) + +# 设置模块输出目录 +set_target_properties(physics PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "物理模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/physics/equations/linear_convection.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/physics/equations/linear_convection.f90 new file mode 100644 index 000000000..3be3b8f7c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/physics/equations/linear_convection.f90 @@ -0,0 +1,43 @@ +! src/physics/equations/linear_advection.f90 +module linear_advection_equation + use base_modules, only: wp, ip + implicit none + private + + type, public :: linear_advection_eq + real(wp) :: wave_speed = 1.0_wp + contains + procedure :: flux => eq_flux + procedure :: max_wave_speed => eq_max_wave_speed + procedure :: num_eqs => eq_num_eqs + procedure :: print_info => eq_print_info + end type + +contains + + pure function eq_flux(this, u) result(f) + class(linear_advection_eq), intent(in) :: this + real(wp), intent(in) :: u + real(wp) :: f + f = this%wave_speed * u + end function + + pure function eq_max_wave_speed(this, u) result(smax) + class(linear_advection_eq), intent(in) :: this + real(wp), intent(in) :: u + real(wp) :: smax + smax = abs(this%wave_speed) + end function + + integer function eq_num_eqs(this) + class(linear_advection_eq), intent(in) :: this + eq_num_eqs = 1 + end function + + subroutine eq_print_info(this) + class(linear_advection_eq), intent(in) :: this + print *, "Linear Advection Equation:" + print *, " Wave speed: ", this%wave_speed + end subroutine + +end module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/physics/physics_interface.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/physics/physics_interface.f90 new file mode 100644 index 000000000..45002da6c --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/physics/physics_interface.f90 @@ -0,0 +1,64 @@ +! src/physics/physics_interface.f90 +module physics_interface + use precision_module, only: wp, ip + implicit none + private + + ! 定义抽象基类型 - 先声明为私有,然后在公开部分导出 + type, abstract :: physics_equation + character(len=:), allocatable :: name + contains + procedure(eq_flux_abs), deferred :: flux + procedure(eq_speed_abs), deferred :: speed + end type physics_equation + + type, abstract :: physics_problem + character(len=:), allocatable :: name + contains + procedure(prob_ic_abs), deferred :: initial_condition + procedure(prob_bc_abs), deferred :: boundary_condition + procedure(prob_exact_abs), deferred :: exact_solution + end type physics_problem + + ! 抽象接口定义 + abstract interface + pure function eq_flux_abs(this, u) result(f) + import :: physics_equation, wp + class(physics_equation), intent(in) :: this + real(wp), intent(in) :: u + real(wp) :: f + end function eq_flux_abs + + pure function eq_speed_abs(this) result(a) + import :: physics_equation, wp + class(physics_equation), intent(in) :: this + real(wp) :: a + end function eq_speed_abs + + subroutine prob_ic_abs(this, x, u) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(in) :: x(:) + real(wp), intent(out) :: u(:) + end subroutine prob_ic_abs + + subroutine prob_bc_abs(this, u, t) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(inout) :: u(:) + real(wp), intent(in), optional :: t + end subroutine prob_bc_abs + + function prob_exact_abs(this, x, t) result(u) + import :: physics_problem, wp + class(physics_problem), intent(in) :: this + real(wp), intent(in) :: x(:), t + real(wp), dimension(size(x)) :: u + end function prob_exact_abs + end interface + + ! 公开接口 - 使用独立的public语句 + public :: wp, ip + public :: physics_equation, physics_problem + +end module physics_interface \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/physics/problems/linear_advection_problem.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/physics/problems/linear_advection_problem.f90 new file mode 100644 index 000000000..419d57cfd --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/physics/problems/linear_advection_problem.f90 @@ -0,0 +1,70 @@ +! src/physics/problems/linear_advection_problem.f90 +module linear_advection_problem + use base_modules, only: wp, ip + use initial_condition_module, only: create_initial_condition + use boundary_condition_module, only: create_boundary_condition + implicit none + private + + type, public :: linear_advection_problem + character(len=20) :: ic_type = "step" + character(len=20) :: boundary_type = "periodic" + real(wp) :: left_value = 1.0_wp + real(wp) :: right_value = 2.0_wp + real(wp) :: domain_length = 2.0_wp + real(wp) :: wave_speed = 1.0_wp + contains + procedure :: create_ic => prob_create_ic + procedure :: create_bc => prob_create_bc + procedure :: exact_solution => prob_exact_solution + end type + +contains + + function prob_create_ic(this, config) result(ic) + class(linear_advection_problem), intent(in) :: this + class(*), intent(in) :: config + class(*), allocatable :: ic + + ! 通过初始条件工厂创建 + call create_initial_condition(this%ic_type, config, ic) + end function + + function prob_create_bc(this, cfd) result(bc) + class(linear_advection_problem), intent(in) :: this + class(*), intent(in) :: cfd + class(*), allocatable :: bc + + ! 通过边界条件工厂创建 + call create_boundary_condition(this%boundary_type, cfd, bc) + end function + + function prob_exact_solution(this, x, t) result(u) + class(linear_advection_problem), intent(in) :: this + real(wp), intent(in) :: x(:), t + real(wp), allocatable :: u(:) + + integer :: i, n + real(wp) :: x_shifted, L + + n = size(x) + L = this%domain_length + allocate(u(n)) + + ! 周期性平移 + do i = 1, n + x_shifted = x(i) - this%wave_speed * t + x_shifted = modulo(x_shifted, L) + if (x_shifted < 0.0_wp) x_shifted = x_shifted + L + + ! 初始条件逻辑(简化) + if (this%ic_type == "step" .and. & + x_shifted >= 0.5_wp .and. x_shifted <= 1.0_wp) then + u(i) = 2.0_wp + else + u(i) = 1.0_wp + end if + end do + end function + +end module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/physics/problems/linear_convection_problem.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/physics/problems/linear_convection_problem.f90 new file mode 100644 index 000000000..06226ed13 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/physics/problems/linear_convection_problem.f90 @@ -0,0 +1,118 @@ +! src/physics/problems/linear_convection_problem.f90 +module linear_convection_problem + use precision_module, only: wp, ip + use physics_interface, only: physics_problem + implicit none + private + + ! 具体问题类型 - 先声明 + type, extends(physics_problem) :: linear_convection_prob + real(wp) :: wave_speed = 1.0_wp + real(wp) :: domain_length = 2.0_wp + character(len=20) :: ic_type = "step" + character(len=20) :: boundary_type = "periodic" + contains + procedure :: initial_condition => lc_initial_condition + procedure :: boundary_condition => lc_boundary_condition + procedure :: exact_solution => lc_exact_solution + end type linear_convection_prob + + ! 公开接口 + public :: wp, ip + public :: linear_convection_prob, create_linear_convection_prob + +contains + + ! 构造函数 + function create_linear_convection_prob(wave_speed, domain_length, & + ic_type, boundary_type) result(prob) + real(wp), intent(in), optional :: wave_speed, domain_length + character(len=*), intent(in), optional :: ic_type, boundary_type + type(linear_convection_prob) :: prob + + prob%name = "Linear Convection Problem" + + if (present(wave_speed)) prob%wave_speed = wave_speed + if (present(domain_length)) prob%domain_length = domain_length + if (present(ic_type)) prob%ic_type = ic_type + if (present(boundary_type)) prob%boundary_type = boundary_type + end function create_linear_convection_prob + + ! 初始条件 + subroutine lc_initial_condition(this, x, u) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(in) :: x(:) + real(wp), intent(out) :: u(:) + + integer :: i + + select case (trim(this%ic_type)) + case ("step") + do i = 1, size(x) + if (x(i) >= 0.5_wp .and. x(i) <= 1.0_wp) then + u(i) = 2.0_wp + else + u(i) = 1.0_wp + end if + end do + + case ("sin", "sine") + do i = 1, size(x) + u(i) = sin(2.0_wp * 3.141592653589793_wp * x(i) / this%domain_length) + end do + + case ("gaussian") + do i = 1, size(x) + u(i) = exp(-((x(i) - 0.5_wp) / 0.1_wp)**2) + end do + + case default + ! 默认阶跃函数 + do i = 1, size(x) + if (x(i) >= 0.5_wp .and. x(i) <= 1.0_wp) then + u(i) = 2.0_wp + else + u(i) = 1.0_wp + end if + end do + end select + end subroutine lc_initial_condition + + ! 边界条件(虚拟实现,实际在boundary模块) + subroutine lc_boundary_condition(this, u, t) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(inout) :: u(:) + real(wp), intent(in), optional :: t + + ! 边界条件将在独立模块实现 + print *, "[PROBLEM] Boundary condition placeholder" + if (present(t)) then + print *, " Time = ", t + end if + end subroutine lc_boundary_condition + + ! 精确解(周期性平移) + function lc_exact_solution(this, x, t) result(u) + class(linear_convection_prob), intent(in) :: this + real(wp), intent(in) :: x(:), t + real(wp), dimension(size(x)) :: u + real(wp), dimension(size(x)) :: x_shifted + integer :: i + + ! 周期性平移 + do i = 1, size(x) + x_shifted(i) = x(i) - this%wave_speed * t + ! 确保在 [0, domain_length) 范围内 + do while (x_shifted(i) < 0.0_wp) + x_shifted(i) = x_shifted(i) + this%domain_length + end do + do while (x_shifted(i) >= this%domain_length) + x_shifted(i) = x_shifted(i) - this%domain_length + end do + end do + + ! 重用初始条件函数 + call this%initial_condition(x_shifted, u) + end function lc_exact_solution + +end module linear_convection_problem \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/results.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/results.f90 new file mode 100644 index 000000000..f7ce0a7ad --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/results.f90 @@ -0,0 +1,290 @@ +! src/results.f90 (修正版) +module results_module + use base_modules, only: wp, ip + use config_module, only: cfd_config + use mesh_module, only: mesh_type + use domain_module, only: domain_type + use solution_module, only: solution_type + ! use physics_solver_module, only: physics_solver ! 暂时注释掉,避免循环依赖 + + implicit none + private + public :: results_saver, results_saver_create, save_results + + ! 定义字符串长度常量 + integer, parameter :: STR_LEN = 128 + + ! 结果类型 - 对应Julia的result字典 + type :: cfd_results + character(len=STR_LEN) :: solver_name = "" + real(wp), allocatable :: x(:) ! 网格坐标(单元中心) + real(wp), allocatable :: numerical(:) ! 数值解 + real(wp), allocatable :: analytical(:) ! 解析解 + character(len=STR_LEN) :: scheme = "" ! 格式名称 + integer :: order = 0 ! 阶数 + integer :: rk_order = 0 ! RK阶数 + real(wp) :: final_time = 0.0_wp ! 最终时间 + real(wp) :: current_time = 0.0_wp ! 当前时间 + integer :: total_steps = 0 ! 总步数 + integer :: solver_state = 0 ! 求解器状态 + end type cfd_results + + ! 结果保存器 + type :: results_saver + character(len=STR_LEN) :: base_filename = "results" + logical :: verbose = .true. + contains + procedure :: save_text => results_saver_save_text + procedure :: save_binary => results_saver_save_binary + procedure :: load => results_saver_load + end type results_saver + + ! 接口声明 + interface results_saver + module procedure results_saver_constructor + end interface + +contains + + ! 构造函数 + function results_saver_constructor(base_filename, verbose) result(saver) + character(len=*), optional :: base_filename + logical, optional :: verbose + type(results_saver) :: saver + + if (present(base_filename)) then + saver%base_filename = trim(adjustl(base_filename)) + end if + if (present(verbose)) then + saver%verbose = verbose + end if + end function results_saver_constructor + + ! 保持向后兼容的创建函数 + function results_saver_create(base_filename, verbose) result(saver) + character(len=*), optional :: base_filename + logical, optional :: verbose + type(results_saver) :: saver + + saver = results_saver_constructor(base_filename, verbose) + end function results_saver_create + + ! 生成文件名(与Julia风格一致) + function generate_filename(saver, solver_name, mesh_size) result(filename) + class(results_saver), intent(in) :: saver + character(len=*), intent(in) :: solver_name + integer, intent(in) :: mesh_size + character(len=STR_LEN) :: filename + + write(filename, '(A, "_", A, "_", I0, ".dat")') & + trim(saver%base_filename), trim(solver_name), mesh_size + end function generate_filename + + ! 主保存函数 - 生成与Julia兼容的结果 + subroutine save_results(saver, solver_name, config, mesh, domain, solution, & + current_time, total_steps, solver_state) + class(results_saver), intent(in) :: saver + character(len=*), intent(in) :: solver_name + type(cfd_config), intent(in) :: config + type(mesh_type), intent(in) :: mesh + type(domain_type), intent(in) :: domain + type(solution_type), intent(in) :: solution + real(wp), intent(in) :: current_time + integer, intent(in) :: total_steps, solver_state + + type(cfd_results) :: results + character(len=STR_LEN) :: filename + integer :: i, n_physical + + ! 准备结果数据 + results%solver_name = trim(solver_name) + results%scheme = trim(config%recon_scheme) + results%order = config%spatial_order + results%rk_order = config%rk_order + results%final_time = config%final_time + results%current_time = current_time + results%total_steps = total_steps + results%solver_state = solver_state + + ! 分配数组 + n_physical = mesh%ncells + allocate(results%x(n_physical)) + allocate(results%numerical(n_physical)) + allocate(results%analytical(n_physical)) + + ! 填充网格坐标(单元中心) + results%x = mesh%xcc + + ! 填充数值解(仅物理区域) + do i = 1, n_physical + results%numerical(i) = solution%u(domain%ist + i - 1) + end do + + ! 生成解析解(与Julia的exact_solution对应) + call generate_analytical_solution(results%x, config, results%analytical, current_time) + + ! 生成文件名 + filename = generate_filename(saver, solver_name, mesh%ncells) + + ! 保存文件 + if (saver%verbose) then + print *, "[RESULTS] Saving results to: ", trim(filename) + print *, " Solver: ", trim(results%solver_name) + print *, " Scheme: ", trim(results%scheme), " order ", results%order + print *, " Time: ", results%current_time, " / ", results%final_time + print *, " Steps: ", results%total_steps + end if + + call saver%save_text(results, filename) + + ! 清理 + deallocate(results%x, results%numerical, results%analytical) + end subroutine save_results + + ! 生成解析解(匹配Julia的exact_solution逻辑) + subroutine generate_analytical_solution(x, config, analytical, current_time) + real(wp), intent(in) :: x(:), current_time + type(cfd_config), intent(in) :: config + real(wp), intent(out) :: analytical(:) + + integer :: i, n + real(wp) :: x_shifted, L + + n = size(x) + L = config%domain_length + + select case (trim(config%ic_type)) + case ("step") + ! 阶跃函数的精确解(周期性) + do i = 1, n + ! 周期性平移 + x_shifted = x(i) - config%wave_speed * current_time + x_shifted = modulo(x_shifted, L) + if (x_shifted < 0.0_wp) x_shifted = x_shifted + L + + ! 阶跃在 [0.5, 1.0] 内为 2.0,其他为 1.0 + if (x_shifted >= 0.5_wp .and. x_shifted <= 1.0_wp) then + analytical(i) = 2.0_wp + else + analytical(i) = 1.0_wp + end if + end do + + case ("sin", "sine") + ! 正弦波的精确解 + do i = 1, n + x_shifted = x(i) - config%wave_speed * current_time + x_shifted = modulo(x_shifted, L) + analytical(i) = sin(2.0_wp * 3.141592653589793_wp * x_shifted / L) + end do + + case ("gaussian") + ! 高斯脉冲的精确解 + do i = 1, n + x_shifted = x(i) - config%wave_speed * current_time + x_shifted = modulo(x_shifted, L) + analytical(i) = exp(-50.0_wp * (x_shifted - 1.0_wp)**2) + end do + + case default + ! 默认:阶跃函数 + do i = 1, n + x_shifted = x(i) - config%wave_speed * current_time + x_shifted = modulo(x_shifted, L) + if (x_shifted >= 0.5_wp .and. x_shifted <= 1.0_wp) then + analytical(i) = 2.0_wp + else + analytical(i) = 1.0_wp + end if + end do + end select + end subroutine generate_analytical_solution + + ! 文本格式保存(与Julia的纯文本输出兼容) + subroutine results_saver_save_text(this, results, filename) + class(results_saver), intent(in) :: this + type(cfd_results), intent(in) :: results + character(len=*), intent(in) :: filename + + integer :: i, n, unit, ierr + + n = size(results%x) + + ! 打开文件 + open(newunit=unit, file=trim(filename), status='replace', & + action='write', iostat=ierr) + + if (ierr /= 0) then + if (this%verbose) then + print *, "[ERROR] Cannot open file: ", trim(filename) + end if + return + end if + + ! 写入头部信息(类似Julia的输出格式) + write(unit, '(A)') "========================================" + write(unit, '(A)') "CFD SOLVER RESULTS (Fortran)" + write(unit, '(A)') "========================================" + write(unit, '(A, A)') "Solver: ", trim(results%solver_name) + write(unit, '(A, A)') "Scheme: ", trim(results%scheme) + write(unit, '(A, I0)') "Order: ", results%order + write(unit, '(A, I0)') "RK Order: ", results%rk_order + write(unit, '(A, ES15.8)') "Final Time: ", results%final_time + write(unit, '(A, ES15.8)') "Current Time: ", results%current_time + write(unit, '(A, I0)') "Total Steps: ", results%total_steps + write(unit, '(A, I0)') "Solver State: ", results%solver_state + write(unit, '(A, I0)') "Grid Points: ", n + write(unit, '(A)') "========================================" + write(unit, '(A)') "DATA: x, numerical, analytical" + write(unit, '(A)') "========================================" + + ! 写入数据 + do i = 1, n + write(unit, '(3ES20.12)') results%x(i), results%numerical(i), results%analytical(i) + end do + + ! 关闭文件 + close(unit) + + if (this%verbose) then + print *, "[RESULTS] Saved ", n, " data points to ", trim(filename) + end if + end subroutine results_saver_save_text + + ! 二进制保存(可选) + subroutine results_saver_save_binary(this, results, filename) + class(results_saver), intent(in) :: this + type(cfd_results), intent(in) :: results + character(len=*), intent(in) :: filename + + ! 暂时实现文本格式,二进制格式可后续添加 + if (this%verbose) then + print *, "[INFO] Binary save not implemented, using text format" + end if + call this%save_text(results, filename) + end subroutine results_saver_save_binary + + ! 加载结果(暂时简单实现) + subroutine results_saver_load(this, filename, results) + class(results_saver), intent(in) :: this + character(len=*), intent(in) :: filename + type(cfd_results), intent(out) :: results + + ! 简化:只打印文件信息 + if (this%verbose) then + print *, "[RESULTS] Would load from: ", trim(filename) + print *, " Note: Load functionality needs implementation" + end if + + ! 初始化结果结构以避免未初始化警告 + results%solver_name = "" + results%scheme = "" + results%order = 0 + results%rk_order = 0 + results%final_time = 0.0_wp + results%current_time = 0.0_wp + results%total_steps = 0 + results%solver_state = 0 + end subroutine results_saver_load + +end module results_module diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/solver/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03g/src/solver/CMakeLists.txt new file mode 100644 index 000000000..059dc8213 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/solver/CMakeLists.txt @@ -0,0 +1,44 @@ +# src/solver/CMakeLists.txt(简化版) +message(STATUS "配置求解器模块...") + +# 基础求解器库 +add_library(solver STATIC + base.f90 + physics_solver.f90 + residual_simple.f90 # 添加简化残差计算器 +) + +target_link_libraries(solver + PRIVATE + infrastructure + core + physics + manager + results + boundary + initial_condition +) + +set_target_properties(solver PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +# 简单集成求解器库(演示用) +add_library(solver_integrated STATIC + solver_integrated.f90 +) + +target_link_libraries(solver_integrated + PRIVATE + solver + infrastructure + base + boundary + initial_condition +) + +set_target_properties(solver_integrated PROPERTIES + Fortran_MODULE_DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY} +) + +message(STATUS "求解器模块配置完成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/solver/base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/solver/base.f90 new file mode 100644 index 000000000..cfd78c475 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/solver/base.f90 @@ -0,0 +1,264 @@ +! src/solver/base.f90 +module solver_base_module + use base_modules, only: wp => wp, ip => ip ! 重命名以避免冲突 + + use config_module, only: cfd_config + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create + + implicit none + private + + ! 明确导出列表 + public :: wp, ip ! 类型参数 + public :: solver_base, create_solver_base ! 类型和构造函数 + public :: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, SOLVER_RUNNING + public :: SOLVER_COMPLETED, SOLVER_ERROR ! 状态常量 + + ! 求解器状态枚举 + integer, parameter :: SOLVER_UNINITIALIZED = 0 + integer, parameter :: SOLVER_INITIALIZED = 1 + integer, parameter :: SOLVER_RUNNING = 2 + integer, parameter :: SOLVER_COMPLETED = 3 + integer, parameter :: SOLVER_ERROR = 4 + + ! 求解器基类 + type :: solver_base + ! 基本组件 + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + + ! 状态管理 + integer :: state = SOLVER_UNINITIALIZED + character(len=100) :: error_message = "" + real(wp) :: current_time = 0.0_wp + integer(ip) :: current_step = 0 + + ! 时间控制 + real(wp) :: dt_original = 0.0_wp + contains + procedure :: initialize => solver_base_initialize + procedure :: step => solver_base_step + procedure :: run_to_time => solver_base_run_to_time + procedure :: cleanup => solver_base_cleanup + procedure :: get_state => solver_base_get_state + procedure :: get_error => solver_base_get_error + procedure :: print_info => solver_base_print_info + end type solver_base + + ! 构造函数接口 + interface solver_base + module procedure create_solver_base + end interface + +contains + + ! ==================== 构造函数 ==================== + + function create_solver_base(config, mesh) result(solver) + type(cfd_config), intent(in) :: config + type(mesh_type), intent(in) :: mesh + type(solver_base) :: solver + + solver%config = config + solver%mesh = mesh + + ! 创建域 + solver%domain = domain_create(config, mesh) + + ! 创建解 + solver%solution = solution_create(solver%domain) + + ! 保存原始时间步长 + solver%dt_original = config%dt + + if (config%verbose) then + print *, "[SOLVER] Base solver created" + print *, " Mesh cells: ", mesh%ncells + print *, " Domain total cells: ", solver%domain%ntcells + end if + end function create_solver_base + + ! ==================== 初始化 ==================== + + subroutine solver_base_initialize(this) + class(solver_base), intent(inout) :: this + + if (this%state == SOLVER_INITIALIZED) then + if (this%config%verbose) then + print *, "[SOLVER] Already initialized" + end if + return + end if + + ! 初始化解(通过配置) + ! 这里暂时简化,实际需要调用初始条件工厂 + print *, "[INFO] Base solver initialized (simplified)" + + ! 更新状态 + this%state = SOLVER_INITIALIZED + this%current_time = 0.0_wp + this%current_step = 0 + + if (this%config%verbose) then + print *, "[SOLVER] Initialized at t = ", this%current_time + end if + end subroutine solver_base_initialize + + ! ==================== 单步计算(虚方法) ==================== + + subroutine solver_base_step(this, dt) + class(solver_base), intent(inout) :: this + real(wp), intent(in) :: dt + + ! 基类中这只是虚方法,需要在子类中实现 + print *, "[INFO] Base solver step (virtual method)" + print *, " dt = ", dt + print *, " t = ", this%current_time + + ! 更新时间 + this%current_time = this%current_time + dt + this%current_step = this%current_step + 1 + + ! 简单模拟:只是更新状态 + if (this%config%verbose) then + print *, "[SOLVER] Step completed: t = ", this%current_time, & + ", step = ", this%current_step + end if + end subroutine solver_base_step + + ! ==================== 运行到指定时间 ==================== + + subroutine solver_base_run_to_time(this, final_time) + class(solver_base), intent(inout) :: this + real(wp), intent(in) :: final_time + + real(wp) :: dt, t_remaining + integer :: step_count + + if (this%state /= SOLVER_INITIALIZED) then + this%error_message = "Solver not initialized" + this%state = SOLVER_ERROR + if (this%config%verbose) then + print *, "[SOLVER BASE ERROR] Not initialized: ", trim(this%error_message) + end if + return + end if + + this%state = SOLVER_RUNNING + step_count = 0 + + if (this%config%verbose) then + print *, "[SOLVER BASE] Running from t = ", this%current_time, & + " to t = ", final_time + print *, " Time step: ", this%config%dt + end if + + do while (this%current_time < final_time - 1e-12_wp) + ! 计算时间步长 + t_remaining = final_time - this%current_time + dt = min(this%config%dt, t_remaining) + + ! 执行时间步 + call this%step(dt) + + step_count = step_count + 1 + + ! 每50步输出一次进度 + if (mod(step_count, 50) == 0 .and. this%config%verbose) then + print *, "[SOLVER BASE] Progress: t = ", this%current_time, & + " / ", final_time, " (step ", step_count, ")" + end if + end do + + ! 恢复原始时间步长 + this%config%dt = this%dt_original + + ! 更新状态 + this%state = SOLVER_COMPLETED + + if (this%config%verbose) then + print *, "[SOLVER BASE] Run completed:" + print *, " Final time: ", this%current_time + print *, " Total steps: ", this%current_step + print *, " State: ", this%state + end if + end subroutine solver_base_run_to_time + + ! ==================== 清理 ==================== + + subroutine solver_base_cleanup(this) + class(solver_base), intent(inout) :: this + + ! 重置状态 + this%state = SOLVER_UNINITIALIZED + this%current_time = 0.0_wp + this%current_step = 0 + this%error_message = "" + + if (this%config%verbose) then + print *, "[SOLVER] Cleaned up" + end if + end subroutine solver_base_cleanup + + ! ==================== 状态查询 ==================== + + function solver_base_get_state(this) result(state) + class(solver_base), intent(in) :: this + integer :: state + state = this%state + end function solver_base_get_state + + function solver_base_get_error(this) result(error_msg) + class(solver_base), intent(in) :: this + character(len=100) :: error_msg + error_msg = trim(this%error_message) + end function solver_base_get_error + + ! ==================== 信息打印 ==================== + + subroutine solver_base_print_info(this) + class(solver_base), intent(in) :: this + + character(len=20) :: state_str + + ! 状态字符串 + select case (this%state) + case (SOLVER_UNINITIALIZED) + state_str = "Uninitialized" + case (SOLVER_INITIALIZED) + state_str = "Initialized" + case (SOLVER_RUNNING) + state_str = "Running" + case (SOLVER_COMPLETED) + state_str = "Completed" + case (SOLVER_ERROR) + state_str = "Error" + case default + state_str = "Unknown" + end select + + print *, "=== Solver Information ===" + print *, "State: ", trim(state_str) + print *, "Current time: ", this%current_time + print *, "Current step: ", this%current_step + print *, "Error message: '", trim(this%error_message), "'" + + ! 配置信息 + print *, "Configuration:" + print *, " Scheme: ", trim(this%config%recon_scheme) + print *, " Order: ", this%config%spatial_order + print *, " dt: ", this%config%dt + + ! 域信息 + print *, "Domain:" + print *, " Ghost layers: ", this%domain%nghosts + print *, " Physical cells: ", this%domain%ist, " to ", this%domain%ied - 1 + + print *, "=========================" + end subroutine solver_base_print_info + +end module solver_base_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/solver/physics_solver.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/solver/physics_solver.f90 new file mode 100644 index 000000000..4582042db --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/solver/physics_solver.f90 @@ -0,0 +1,188 @@ +! src/solver/physics_solver.f90 (修正版) +module physics_solver_module + use base_modules, only: wp => wp, ip => ip + use config_module, only: cfd_config + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create + + implicit none + private + + ! 明确导出列表 + public :: wp, ip, physics_solver + public :: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, SOLVER_COMPLETED, SOLVER_ERROR + + ! 求解器状态枚举 + integer, parameter :: SOLVER_UNINITIALIZED = 0 + integer, parameter :: SOLVER_INITIALIZED = 1 + integer, parameter :: SOLVER_RUNNING = 2 + integer, parameter :: SOLVER_COMPLETED = 3 + integer, parameter :: SOLVER_ERROR = 4 + + ! 物理求解器类型 + type :: physics_solver + ! 基本组件 + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + + ! 状态管理 + integer :: state = SOLVER_UNINITIALIZED + character(len=100) :: error_message = "" + real(wp) :: current_time = 0.0_wp + integer(ip) :: current_step = 0 + + ! 时间控制 + real(wp) :: dt_original = 0.0_wp + contains + procedure :: initialize => physics_solver_initialize + procedure :: run_to_time => physics_solver_run_to_time + procedure :: cleanup => physics_solver_cleanup + procedure :: get_state => physics_solver_get_state + procedure, private :: apply_simple_initial_condition ! 添加这行 + end type physics_solver + +contains + + ! ==================== 初始化 ==================== + + subroutine physics_solver_initialize(this) + class(physics_solver), intent(inout) :: this + + if (this%state == SOLVER_INITIALIZED) then + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Already initialized" + end if + return + end if + + ! 创建域 + this%domain = domain_create(this%config, this%mesh) + + ! 创建解 + this%solution = solution_create(this%domain) + + ! 应用简化的初始条件 + call this%apply_simple_initial_condition() + + ! 保存原始时间步长 + this%dt_original = this%config%dt + + ! 更新状态 + this%state = SOLVER_INITIALIZED + this%current_time = 0.0_wp + this%current_step = 0 + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Initialized at t = ", this%current_time + end if + end subroutine physics_solver_initialize + + subroutine apply_simple_initial_condition(this) + class(physics_solver), intent(inout) :: this + + integer :: i, idx + real(wp) :: x + + ! 简化的阶跃函数初始条件 + do i = this%domain%ist, this%domain%ied - 1 + idx = i - this%domain%ist + 1 + x = this%mesh%xcc(idx) + + if (x >= 0.5_wp .and. x <= 1.0_wp) then + this%solution%u(i) = 2.0_wp + else + this%solution%u(i) = 1.0_wp + end if + end do + + ! 同步旧场 + this%solution%un = this%solution%u + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Applied simple initial condition" + end if + end subroutine + + ! ==================== 运行到指定时间 ==================== + + subroutine physics_solver_run_to_time(this, final_time) + class(physics_solver), intent(inout) :: this + real(wp), intent(in) :: final_time + + real(wp) :: dt, t_remaining + integer :: step_count + + if (this%state /= SOLVER_INITIALIZED) then + this%error_message = "Solver not initialized" + this%state = SOLVER_ERROR + if (this%config%verbose) then + print *, "[PHYSICS SOLVER ERROR] Not initialized" + end if + return + end if + + this%state = SOLVER_RUNNING + step_count = 0 + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Running from t = ", this%current_time, & + " to t = ", final_time + end if + + do while (this%current_time < final_time - 1e-12_wp) + ! 计算时间步长 + t_remaining = final_time - this%current_time + dt = min(this%config%dt, t_remaining) + + ! 简单的时间步进(占位符) + this%current_time = this%current_time + dt + this%current_step = this%current_step + 1 + step_count = step_count + 1 + + ! 每100步输出一次进度 + if (mod(step_count, 100) == 0 .and. this%config%verbose) then + print *, "[PHYSICS SOLVER] Progress: t = ", this%current_time, & + " / ", final_time, " (step ", step_count, ")" + end if + end do + + ! 恢复原始时间步长 + this%config%dt = this%dt_original + + ! 更新状态 + this%state = SOLVER_COMPLETED + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Run completed:" + print *, " Final time: ", this%current_time + print *, " Total steps: ", this%current_step + end if + end subroutine physics_solver_run_to_time + + ! ==================== 清理 ==================== + + subroutine physics_solver_cleanup(this) + class(physics_solver), intent(inout) :: this + + this%state = SOLVER_UNINITIALIZED + this%current_time = 0.0_wp + this%current_step = 0 + this%error_message = "" + + if (this%config%verbose) then + print *, "[PHYSICS SOLVER] Cleaned up" + end if + end subroutine physics_solver_cleanup + + ! ==================== 状态查询 ==================== + + function physics_solver_get_state(this) result(state) + class(physics_solver), intent(in) :: this + integer :: state + state = this%state + end function physics_solver_get_state + +end module physics_solver_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/solver/residual.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/solver/residual.f90 new file mode 100644 index 000000000..ad0d5b8c3 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/solver/residual.f90 @@ -0,0 +1,152 @@ +! src/solver/residual.f90 (增强版) +module residual_module + use base_modules, only: wp, ip + use domain_module, only: domain_type + use solution_module, only: solution_type + use mesh_module, only: mesh_type + use config_module, only: cfd_config + use reconstructor_base_module, only: reconstructor_base + use flux_base_module, only: flux_calculator_base + + implicit none + private + + type, public :: residual_calculator + ! 配置和域 + type(cfd_config), pointer :: config => null() + type(mesh_type), pointer :: mesh => null() + type(domain_type), pointer :: domain => null() + + ! 数值组件 + class(reconstructor_base), pointer :: reconstructor => null() + class(flux_calculator_base), pointer :: flux_calc => null() + + ! 工作数组 + real(wp), pointer :: qL(:) => null() + real(wp), pointer :: qR(:) => null() + real(wp), pointer :: flux(:) => null() + real(wp), pointer :: res(:) => null() + + real(wp) :: dx = 0.0_wp + logical :: initialized = .false. + + contains + procedure :: initialize => residual_init + procedure :: compute => residual_compute + procedure :: cleanup => residual_cleanup + end type residual_calculator + +contains + + subroutine residual_init(this, config, mesh, domain, reconstructor, flux_calc, & + solution) + class(residual_calculator), intent(inout) :: this + type(cfd_config), target, intent(in) :: config + type(mesh_type), target, intent(in) :: mesh + type(domain_type), target, intent(in) :: domain + class(reconstructor_base), target, intent(in) :: reconstructor + class(flux_calculator_base), target, intent(in) :: flux_calc + type(solution_type), target, intent(in) :: solution + + this%config => config + this%mesh => mesh + this%domain => domain + this%reconstructor => reconstructor + this%flux_calc => flux_calc + + ! 链接工作数组 + this%qL => solution%q_face_left + this%qR => solution%q_face_right + this%flux => solution%flux + this%res => solution%res + + ! 计算网格间距 + this%dx = mesh%dx + + this%initialized = .true. + + if (config%verbose) then + print *, "[RESIDUAL] Initialized residual calculator" + print *, " Scheme: ", trim(reconstructor%name) + print *, " Flux: ", trim(flux_calc%name) + print *, " dx: ", this%dx + end if + end subroutine residual_init + + subroutine residual_compute(this, u) + class(residual_calculator), intent(inout) :: this + real(wp), intent(in) :: u(:) + + integer :: i, n_faces, n_cells + real(wp) :: f_left, f_right + + if (.not. this%initialized) then + if (this%config%verbose) then + print *, "[ERROR] Residual calculator not initialized" + end if + return + end if + + n_faces = this%mesh%nnodes + n_cells = this%mesh%ncells + + ! 阶段1: 重构界面值(简化实现) + ! 在实际实现中,这里应该调用重构器 + if (this%config%verbose .and. mod(this%domain%current_step, 100) == 0) then + print *, " [RESIDUAL] Reconstructing with ", trim(this%reconstructor%name) + end if + + ! 简化重构:线性插值 + do i = 1, n_faces - 1 + this%qL(i+1) = 0.5_wp * (u(this%domain%ist + i - 2) + & + u(this%domain%ist + i - 1)) + this%qR(i) = this%qL(i+1) + end do + + ! 周期边界处理 + this%qL(1) = 0.5_wp * (u(this%domain%ied - 1) + u(this%domain%ist)) + this%qR(n_faces) = this%qL(1) + + ! 阶段2: 计算通量(简化实现) + ! 在实际实现中,这里应该调用通量计算器 + if (this%config%verbose .and. mod(this%domain%current_step, 100) == 0) then + print *, " [RESIDUAL] Computing fluxes with ", trim(this%flux_calc%name) + end if + + ! 简化通量:Lax-Friedrichs + do i = 1, n_faces + f_left = this%config%wave_speed * this%qL(i) + f_right = this%config%wave_speed * this%qR(i) + this%flux(i) = 0.5_wp * (f_left + f_right) - & + 0.5_wp * abs(this%config%wave_speed) * (this%qR(i) - this%qL(i)) + end do + + ! 阶段3: 计算残差 + do i = 1, n_cells + this%res(i) = -(this%flux(i+1) - this%flux(i)) / this%dx + end do + + ! 调试输出 + if (this%config%verbose .and. mod(this%domain%current_step, 100) == 0) then + print *, " [RESIDUAL] Residual range: ", & + minval(this%res), " to ", maxval(this%res) + end if + end subroutine residual_compute + + subroutine residual_cleanup(this) + class(residual_calculator), intent(inout) :: this + this%initialized = .false. + + ! 清空指针 + this%config => null() + this%mesh => null() + this%domain => null() + this%reconstructor => null() + this%flux_calc => null() + this%qL => null() + this%qR => null() + this%flux => null() + this%res => null() + end subroutine residual_cleanup + +end module residual_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/solver/residual_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/solver/residual_simple.f90 new file mode 100644 index 000000000..5b8bc766b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/solver/residual_simple.f90 @@ -0,0 +1,96 @@ +! src/solver/residual_simple.f90(简化版) +module residual_simple_module + use base_modules, only: wp, ip + use config_module, only: cfd_config + use mesh_module, only: mesh_type + use domain_module, only: domain_type + + implicit none + private + + type, public :: residual_calculator_simple + ! 配置和域 + type(cfd_config), pointer :: config => null() + type(mesh_type), pointer :: mesh => null() + type(domain_type), pointer :: domain => null() + + real(wp) :: dx = 0.0_wp + logical :: initialized = .false. + + contains + procedure :: initialize => residual_simple_init + procedure :: compute => residual_simple_compute + procedure :: cleanup => residual_simple_cleanup + end type residual_calculator_simple + +contains + + subroutine residual_simple_init(this, config, mesh, domain) + class(residual_calculator_simple), intent(inout) :: this + type(cfd_config), target, intent(in) :: config + type(mesh_type), target, intent(in) :: mesh + type(domain_type), target, intent(in) :: domain + + this%config => config + this%mesh => mesh + this%domain => domain + this%dx = mesh%dx + this%initialized = .true. + + if (config%verbose) then + print *, "[RESIDUAL SIMPLE] Initialized" + print *, " dx: ", this%dx + end if + end subroutine residual_simple_init + + subroutine residual_simple_compute(this, u, res) + class(residual_calculator_simple), intent(inout) :: this + real(wp), intent(in) :: u(:) + real(wp), intent(out) :: res(:) + + integer :: i, n_cells + real(wp) :: f_left, f_right + + if (.not. this%initialized) then + if (this%config%verbose) then + print *, "[ERROR] Residual calculator not initialized" + end if + return + end if + + n_cells = this%mesh%ncells + + ! 简化的残差计算(Lax-Friedrichs格式) + do i = 1, n_cells + ! 简化的通量计算 + f_left = this%config%wave_speed * u(this%domain%ist + i - 1) + f_right = this%config%wave_speed * u(this%domain%ist + i) + + ! Lax-Friedrichs通量 + f_left = 0.5_wp * (f_left + f_right) - & + 0.5_wp * abs(this%config%wave_speed) * & + (u(this%domain%ist + i) - u(this%domain%ist + i - 1)) + + f_right = 0.5_wp * (f_left + f_right) - & + 0.5_wp * abs(this%config%wave_speed) * & + (u(this%domain%ist + i) - u(this%domain%ist + i - 1)) + + ! 残差 = -∂F/∂x + res(i) = -(f_right - f_left) / this%dx + end do + + if (this%config%verbose .and. mod(this%domain%current_step, 100) == 0) then + print *, " [RESIDUAL] Computed residual" + end if + end subroutine residual_simple_compute + + subroutine residual_simple_cleanup(this) + class(residual_calculator_simple), intent(inout) :: this + this%initialized = .false. + + this%config => null() + this%mesh => null() + this%domain => null() + end subroutine residual_simple_cleanup + +end module residual_simple_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/src/solver/solver_integrated.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/src/solver/solver_integrated.f90 new file mode 100644 index 000000000..6a9a0bfd4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/src/solver/solver_integrated.f90 @@ -0,0 +1,390 @@ +! src/solver/solver_integrated.f90 (完全修正版) +module solver_integrated_module + use base_modules, only: wp, ip + use config_module, only: cfd_config + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create + use boundary_base_module, only: boundary_condition + use periodic_boundary_module, only: periodic_boundary + + implicit none + private + + ! 求解器状态常量 + integer, parameter, public :: SOLVER_READY = 0 + integer, parameter, public :: SOLVER_INITIALIZED = 1 + integer, parameter, public :: SOLVER_RUNNING = 2 + integer, parameter, public :: SOLVER_COMPLETED = 3 + integer, parameter, public :: SOLVER_ERROR = -1 + + type, public :: integrated_solver + ! 基本组件 + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + + ! 组件实例 + class(boundary_condition), allocatable :: bc + + ! 状态 + integer :: state = SOLVER_READY + real(wp) :: current_time = 0.0_wp + integer(ip) :: current_step = 0 + character(len=100) :: error_msg = "" + + ! 数据模式 + logical :: use_real_data = .true. + + contains + procedure :: initialize => solver_initialize + procedure :: run_to_time => solver_run_to_time + procedure :: cleanup => solver_cleanup + procedure :: get_state => solver_get_state + procedure :: enable_real_data => solver_enable_real_data + procedure, private :: apply_initial_condition + procedure, private :: apply_boundary_conditions + procedure, private :: simple_time_step + procedure, private :: calculate_dt + procedure, private :: setup_boundary_condition ! 修改方法名 + end type integrated_solver + +contains + + ! ==================== 公共接口 ==================== + + subroutine solver_initialize(this) + class(integrated_solver), intent(inout) :: this + + if (this%state /= SOLVER_READY) then + this%error_msg = "Solver already initialized" + this%state = SOLVER_ERROR + if (this%config%verbose) then + print *, "[ERROR] ", trim(this%error_msg) + end if + return + end if + + ! 创建域 + this%domain = domain_create(this%config, this%mesh) + + ! 创建解 + this%solution = solution_create(this%domain) + + ! 设置边界条件 + call this%setup_boundary_condition() ! 修改调用 + + ! 应用初始条件 + call this%apply_initial_condition() + + ! 初始化状态 + this%state = SOLVER_INITIALIZED + this%current_time = 0.0_wp + this%current_step = 0 + + if (this%config%verbose) then + print *, "[INTEGRATED SOLVER] Initialized successfully" + print *, " Domain cells (with ghosts): ", this%domain%ntcells + print *, " Ghost layers: ", this%domain%nghosts + if (this%use_real_data) then + print *, " Data mode: Real" + else + print *, " Data mode: Simple" + end if + end if + end subroutine solver_initialize + + ! ==================== 边界条件设置 ==================== + + subroutine setup_boundary_condition(this) + class(integrated_solver), intent(inout) :: this + + ! 根据配置创建边界条件 + select case (trim(this%config%boundary_type)) + case ("periodic") + ! 创建周期性边界条件 + if (.not. allocated(this%bc)) then + allocate(periodic_boundary :: this%bc) + end if + + select type(bc => this%bc) + type is (periodic_boundary) + bc%name = "periodic" + end select + + case default + ! 默认使用周期性边界 + if (this%config%verbose) then + print *, "[WARNING] Using periodic boundary as default" + end if + + if (.not. allocated(this%bc)) then + allocate(periodic_boundary :: this%bc) + end if + + select type(bc => this%bc) + type is (periodic_boundary) + bc%name = "periodic" + end select + end select + + if (this%config%verbose) then + print *, "[INTEGRATED SOLVER] Boundary condition created: ", & + trim(this%bc%get_name()) + end if + end subroutine setup_boundary_condition + + subroutine solver_run_to_time(this, final_time) + class(integrated_solver), intent(inout) :: this + real(wp), intent(in) :: final_time + + real(wp) :: dt, t_remaining + integer :: step_count + real(wp) :: original_dt + + if (this%state /= SOLVER_INITIALIZED) then + this%error_msg = "Solver not initialized" + this%state = SOLVER_ERROR + if (this%config%verbose) then + print *, "[ERROR] ", trim(this%error_msg) + end if + return + end if + + ! 保存原始时间步长 + original_dt = this%config%dt + + this%state = SOLVER_RUNNING + step_count = 0 + + if (this%config%verbose) then + print *, "[INTEGRATED SOLVER] Starting time integration" + print *, " Initial time: ", this%current_time + print *, " Final time: ", final_time + print *, " Initial dt: ", this%config%dt + end if + + do while (this%current_time < final_time - 1e-12_wp) + ! 计算时间步长 + call this%calculate_dt() + + ! 确保不超过最终时间 + t_remaining = final_time - this%current_time + dt = min(this%config%dt, t_remaining) + + ! 应用边界条件 + call this%apply_boundary_conditions() + + ! 执行时间步 + call this%simple_time_step(dt) + + ! 更新时间 + this%current_time = this%current_time + dt + this%current_step = this%current_step + 1 + step_count = step_count + 1 + + ! 进度输出 + if (mod(step_count, 50) == 0 .and. this%config%verbose) then + print *, " Progress: t = ", this%current_time, & + " / ", final_time, " (step ", step_count, ")" + end if + end do + + ! 恢复原始时间步长 + this%config%dt = original_dt + + ! 更新状态 + this%state = SOLVER_COMPLETED + + if (this%config%verbose) then + print *, "[INTEGRATED SOLVER] Time integration completed" + print *, " Final time: ", this%current_time + print *, " Total steps: ", this%current_step + if (allocated(this%solution%u)) then + print *, " Solution range: [", minval(this%solution%u), ", ", & + maxval(this%solution%u), "]" + end if + end if + end subroutine solver_run_to_time + + subroutine solver_cleanup(this) + class(integrated_solver), intent(inout) :: this + + ! 清理分配的组件 + if (allocated(this%bc)) then + deallocate(this%bc) + end if + + ! 重置状态 + this%state = SOLVER_READY + this%current_time = 0.0_wp + this%current_step = 0 + this%error_msg = "" + + if (this%config%verbose) then + print *, "[INTEGRATED SOLVER] Cleaned up" + end if + end subroutine solver_cleanup + + function solver_get_state(this) result(state) + class(integrated_solver), intent(in) :: this + integer :: state + state = this%state + end function solver_get_state + + subroutine solver_enable_real_data(this, use_real) + class(integrated_solver), intent(inout) :: this + logical, intent(in) :: use_real + this%use_real_data = use_real + + if (this%config%verbose) then + if (use_real) then + print *, "[INTEGRATED SOLVER] Data mode set to: Real" + else + print *, "[INTEGRATED SOLVER] Data mode set to: Simple" + end if + end if + end subroutine solver_enable_real_data + + ! ==================== 私有方法 ==================== + + subroutine apply_initial_condition(this) + class(integrated_solver), intent(inout) :: this + + integer :: i, idx + real(wp) :: x + + if (this%config%verbose) then + print *, "[INTEGRATED SOLVER] Applying initial condition: ", & + trim(this%config%ic_type) + end if + + ! 简化的初始条件应用 + do i = this%domain%ist, this%domain%ied - 1 + idx = i - this%domain%ist + 1 + x = this%mesh%xcc(idx) + + select case (trim(this%config%ic_type)) + case ("step") + ! 阶跃函数 + if (x >= 0.5_wp .and. x <= 1.0_wp) then + this%solution%u(i) = 2.0_wp + else + this%solution%u(i) = 1.0_wp + end if + + case ("sin", "sine") + ! 正弦波 + this%solution%u(i) = sin(2.0_wp * 3.141592653589793_wp * x / & + this%config%domain_length) + + case ("gaussian") + ! 高斯脉冲 + this%solution%u(i) = exp(-((x - 0.5_wp) / 0.1_wp)**2) + + case default + ! 默认阶跃函数 + if (x >= 0.5_wp .and. x <= 1.0_wp) then + this%solution%u(i) = 2.0_wp + else + this%solution%u(i) = 1.0_wp + end if + end select + end do + + ! 同步旧场 + this%solution%un = this%solution%u + + if (this%config%verbose) then + print *, "[INTEGRATED SOLVER] Initial condition applied" + if (allocated(this%solution%u)) then + print *, " Min value: ", minval(this%solution%u) + print *, " Max value: ", maxval(this%solution%u) + end if + end if + end subroutine apply_initial_condition + + subroutine apply_boundary_conditions(this) + class(integrated_solver), intent(inout) :: this + + if (.not. allocated(this%bc)) then + if (this%config%verbose) then + print *, "[WARNING] No boundary condition allocated" + end if + return + end if + + select type(bc => this%bc) + type is (periodic_boundary) + ! 应用周期性边界条件 + call bc%apply(this%solution%u, & + this%domain%nghosts, & + this%domain%ist, & + this%domain%ied - 1) + + class default + ! 对于其他边界条件类型 + if (this%config%verbose) then + print *, "[WARNING] Boundary condition type not fully implemented" + end if + end select + end subroutine apply_boundary_conditions + + subroutine simple_time_step(this, dt) + class(integrated_solver), intent(inout) :: this + real(wp), intent(in) :: dt + + integer :: i + real(wp) :: dx, cfl + + ! 简化的时间步进(一阶迎风格式) + dx = this%mesh%dx + cfl = this%config%wave_speed * dt / dx + + if (cfl > 1.0_wp .and. this%config%verbose) then + print *, "[WARNING] CFL = ", cfl, " > 1.0" + end if + + ! 保存旧解 + this%solution%un = this%solution%u + + ! 一阶迎风格式(只更新内部点) + if (this%domain%ist + 1 <= this%domain%ied - 2) then + do i = this%domain%ist + 1, this%domain%ied - 2 + this%solution%u(i) = this%solution%un(i) - & + cfl * (this%solution%un(i) - this%solution%un(i-1)) + end do + end if + + ! 调试输出 + if (mod(this%current_step, 100) == 0 .and. this%config%verbose) then + print *, " [TIME STEP] Step: ", this%current_step, & + ", t = ", this%current_time, & + ", CFL = ", cfl, & + ", dt = ", dt + end if + end subroutine simple_time_step + + subroutine calculate_dt(this) + class(integrated_solver), intent(inout) :: this + + real(wp) :: cfl, dx + + dx = this%mesh%dx + + if (this%use_real_data) then + ! 真实计算使用CFL条件 + cfl = 0.8_wp ! CFL数 + this%config%dt = cfl * dx / abs(this%config%wave_speed) + else + ! 简单数据使用固定时间步长 + this%config%dt = 0.0025_wp + end if + + if (this%config%verbose .and. this%current_step == 0) then + print *, "[INTEGRATED SOLVER] Calculated dt = ", this%config%dt + end if + end subroutine calculate_dt + +end module solver_integrated_module \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/tests/CMakeLists.txt b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/CMakeLists.txt new file mode 100644 index 000000000..ced7163ee --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/CMakeLists.txt @@ -0,0 +1,137 @@ +# tests/CMakeLists.txt +message(STATUS "Configuring tests...") + +# 基础设施测试 +add_executable(test_infrastructure test_infrastructure.f90) +target_link_libraries(test_infrastructure + PRIVATE + infrastructure + core +) + +# 注册系统测试 +add_executable(test_registry test_registry.f90) +target_link_libraries(test_registry + PRIVATE + core + infrastructure +) + +# 物理模块测试 +add_executable(test_physics test_physics.f90) +target_link_libraries(test_physics + PRIVATE + physics + base +) + +# 组件管理器测试 +add_executable(test_component_manager test_component_manager.f90) +target_link_libraries(test_component_manager + PRIVATE + manager + infrastructure +) + +# 配置物理测试 +add_executable(test_config_physics test_config_physics.f90) +target_link_libraries(test_config_physics + PRIVATE + infrastructure + core +) + +# 求解器基础测试 +add_executable(test_solver_base test_solver_base.f90) +target_link_libraries(test_solver_base + PRIVATE + solver + infrastructure + core +) + +# 物理求解器测试 +add_executable(test_physics_solver test_physics_solver.f90) +target_link_libraries(test_physics_solver + PRIVATE + solver + infrastructure + core + physics + manager +) + +# 新增:简单物理求解器测试 +add_executable(test_physics_solver_simple test_physics_solver_simple.f90) +target_link_libraries(test_physics_solver_simple + PRIVATE + solver + infrastructure + core + physics + manager +) + +add_executable(test_simple_link test_simple_link.f90) +target_link_libraries(test_simple_link + PRIVATE + reconstructor + flux +) + + +add_executable(test_factory_simple test_factory_simple.f90) +target_link_libraries(test_factory_simple + PRIVATE + core + infrastructure + reconstructor + flux +) + +add_executable(test_domain_solution test_domain_solution.f90) +target_link_libraries(test_domain_solution + PRIVATE + infrastructure + core +) + +add_executable(test_component_manager_physics test_component_manager_physics.f90) +target_link_libraries(test_component_manager_physics + PRIVATE + manager + infrastructure + physics + core +) + +# 初始条件测试 +add_executable(test_initial_condition test_initial_condition.f90) +target_link_libraries(test_initial_condition + PRIVATE + initial_condition + infrastructure + core + base +) + +# 新增:简单求解器测试(只包含现有的) +# 注释掉缺失文件的测试,或者创建简单的占位符文件 +# add_executable(test_solver_integrated test_solver_integrated.f90) +# target_link_libraries(test_solver_integrated +# PRIVATE +# solver +# infrastructure +# core +# physics +# manager +# ) + +# 注释掉缺失的残差测试 +# add_executable(test_residual test_residual.f90) +# target_link_libraries(test_residual +# PRIVATE +# solver +# infrastructure +# physics +# ) diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_architecture.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_architecture.f90 new file mode 100644 index 000000000..1c40d4677 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_architecture.f90 @@ -0,0 +1,117 @@ +! tests/test_architecture.f90 +program test_architecture + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module + use config_module + use mesh_module + use component_manager_module + + implicit none + + print *, "=== CFD架构测试 ===" + print *, "" + + ! 测试1: 基本系统 + call test_basic_systems() + + ! 测试2: 组件注册 + call test_registry_integration() + + ! 测试3: 配置验证 + call test_config_validation() + + ! 测试4: 完整流程 + call test_full_workflow() + + print *, "" + print *, "=== 架构测试完成 ===" + +contains + + subroutine test_basic_systems() + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "1. 测试基本系统..." + print *, "-------------------" + + ! 测试配置 + call config_print(config) + print *, "✓ 配置创建成功" + + ! 测试网格 + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "✓ 网格创建成功" + print *, "" + end subroutine test_basic_systems + + subroutine test_registry_integration() + print *, "2. 测试注册系统集成..." + print *, "------------------------" + + call initialize_registry(verbose=.true.) + + ! 注册核心组件 + print *, "注册核心组件:" + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + ! 显示注册内容 + call component_registry%list_simple() + print *, "注册表大小: ", registry_get_size() + + call cleanup_registry() + print *, "✓ 注册系统测试完成" + print *, "" + end subroutine test_registry_integration + + subroutine test_config_validation() + type(cfd_config) :: config + logical :: is_valid + + print *, "3. 测试配置验证..." + print *, "-------------------" + + ! 测试有效配置 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + print *, "测试配置:" + print *, " - 重构器: ", trim(config%recon_scheme), " 阶数: ", config%spatial_order + print *, " - 通量: ", trim(config%flux_type) + print *, " - 波速: ", config%wave_speed + + ! 这里调用验证函数(需要先实现) + ! is_valid = validate_config(config) + print *, "✓ 配置验证接口准备就绪" + print *, "" + end subroutine test_config_validation + + subroutine test_full_workflow() + print *, "4. 测试完整流程..." + print *, "-------------------" + + print *, "步骤1: 初始化系统 ✓" + print *, "步骤2: 创建网格 ✓" + print *, "步骤3: 设置配置 ✓" + print *, "步骤4: 创建组件 (待实现)" + print *, "步骤5: 初始化解 (待实现)" + print *, "步骤6: 时间推进 (待实现)" + print *, "步骤7: 输出结果 (待实现)" + print *, "" + + print *, "✓ 流程框架定义完成" + end subroutine test_full_workflow + +end program test_architecture \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_cfd_architecture.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_cfd_architecture.f90 new file mode 100644 index 000000000..1c40d4677 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_cfd_architecture.f90 @@ -0,0 +1,117 @@ +! tests/test_architecture.f90 +program test_architecture + use, intrinsic :: iso_fortran_env, only: real64 + use registry_module + use config_module + use mesh_module + use component_manager_module + + implicit none + + print *, "=== CFD架构测试 ===" + print *, "" + + ! 测试1: 基本系统 + call test_basic_systems() + + ! 测试2: 组件注册 + call test_registry_integration() + + ! 测试3: 配置验证 + call test_config_validation() + + ! 测试4: 完整流程 + call test_full_workflow() + + print *, "" + print *, "=== 架构测试完成 ===" + +contains + + subroutine test_basic_systems() + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "1. 测试基本系统..." + print *, "-------------------" + + ! 测试配置 + call config_print(config) + print *, "✓ 配置创建成功" + + ! 测试网格 + call mesh%init(xmin=0.0_real64, xmax=1.0_real64, ncells=10) + call mesh%print_info() + print *, "✓ 网格创建成功" + print *, "" + end subroutine test_basic_systems + + subroutine test_registry_integration() + print *, "2. 测试注册系统集成..." + print *, "------------------------" + + call initialize_registry(verbose=.true.) + + ! 注册核心组件 + print *, "注册核心组件:" + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("boundary", "neumann") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + ! 显示注册内容 + call component_registry%list_simple() + print *, "注册表大小: ", registry_get_size() + + call cleanup_registry() + print *, "✓ 注册系统测试完成" + print *, "" + end subroutine test_registry_integration + + subroutine test_config_validation() + type(cfd_config) :: config + logical :: is_valid + + print *, "3. 测试配置验证..." + print *, "-------------------" + + ! 测试有效配置 + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_real64 + + print *, "测试配置:" + print *, " - 重构器: ", trim(config%recon_scheme), " 阶数: ", config%spatial_order + print *, " - 通量: ", trim(config%flux_type) + print *, " - 波速: ", config%wave_speed + + ! 这里调用验证函数(需要先实现) + ! is_valid = validate_config(config) + print *, "✓ 配置验证接口准备就绪" + print *, "" + end subroutine test_config_validation + + subroutine test_full_workflow() + print *, "4. 测试完整流程..." + print *, "-------------------" + + print *, "步骤1: 初始化系统 ✓" + print *, "步骤2: 创建网格 ✓" + print *, "步骤3: 设置配置 ✓" + print *, "步骤4: 创建组件 (待实现)" + print *, "步骤5: 初始化解 (待实现)" + print *, "步骤6: 时间推进 (待实现)" + print *, "步骤7: 输出结果 (待实现)" + print *, "" + + print *, "✓ 流程框架定义完成" + end subroutine test_full_workflow + +end program test_architecture \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_component_manager.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_component_manager.f90 new file mode 100644 index 000000000..cf4cf38a8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_component_manager.f90 @@ -0,0 +1,56 @@ +! tests/test_component_manager.f90 (修正版) +program test_component_manager + use base_modules, only: wp + use config_module, only: cfd_config, config_print, config_with_reconstruction + implicit none + + type(cfd_config) :: config + + print *, "=== Component Manager Test (简化版) ===" + print *, "" + + ! 测试1: 基本配置 + print *, "1. Testing basic configuration..." + print *, "-----------------------------------" + + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.5_wp + + call config_print(config) + print *, "" + + print *, "2. Testing component manager info (简化)..." + print *, "------------------------------------------" + print *, "Component manager functions (简化版本):" + print *, " - Configuration validation available" + print *, " - Component creation framework ready" + print *, "" + + print *, "3. Testing WENO3 configuration..." + print *, "---------------------------------" + + call config_with_reconstruction(config, "weno3", 3) + + print *, "WENO3 configuration:" + print *, " Scheme: ", trim(config%recon_scheme) + print *, " Order: ", config%spatial_order + print *, "" + + ! 测试4: 错误配置测试 + print *, "4. Testing error handling..." + print *, "-----------------------------------" + + config%recon_scheme = "unknown_scheme" + config%flux_type = "unknown_flux" + + print *, "Invalid configuration test:" + print *, " Scheme: ", trim(config%recon_scheme) + print *, " Flux: ", trim(config%flux_type) + print *, "" + + print *, "=== Component manager test completed (简化版) ===" + print *, "下一步: 完善组件管理器功能" + +end program test_component_manager \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_component_manager_physics.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_component_manager_physics.f90 new file mode 100644 index 000000000..f2becca95 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_component_manager_physics.f90 @@ -0,0 +1,120 @@ +! tests/test_component_manager_physics.f90 (简化版) +program test_component_manager_physics + use base_modules, only: wp + use config_module, only: cfd_config, config_print, config_with_reconstruction + use component_manager_module, only: component_manager_info, validate_config + + implicit none + + type(cfd_config) :: config + logical :: is_valid + + print *, "=== Component Manager Physics Test (Simplified) ===" + print *, "" + + ! 测试1: 显示组件管理器信息 + print *, "1. Testing component manager info..." + print *, "-------------------------------------" + call component_manager_info() + print *, "" + + ! 测试2: 物理模块测试(默认) + print *, "2. Testing physics module with default configuration..." + print *, "------------------------------------------------------" + + config%verbose = .true. + call config_print(config) + + ! 验证配置 + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Default configuration is valid" + else + print *, "[ERROR] Default configuration is invalid" + end if + print *, "" + + ! 测试3: 测试物理配置 + print *, "3. Testing physics configuration..." + print *, "------------------------------------" + + ! 修改物理参数 + config%equation_type = "linear_advection" + config%problem_type = "linear_advection" + config%wave_speed = 2.5_wp + config%domain_length = 3.0_wp + + print *, "Modified physics configuration:" + print *, " Equation type: ", trim(config%equation_type) + print *, " Problem type: ", trim(config%problem_type) + print *, " Wave speed: ", config%wave_speed + print *, " Domain length: ", config%domain_length + print *, " Physics enabled: ", config%enable_physics + + ! 验证修改后的配置 + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Modified physics configuration is valid" + else + print *, "[ERROR] Modified physics configuration is invalid" + end if + print *, "" + + ! 测试4: 数值组件测试 + print *, "4. Testing numerical components with physics..." + print *, "-----------------------------------------------" + + call config_with_reconstruction(config, "weno3", 3) + config%flux_type = "rusanov" + + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Combined physics+numerics configuration is valid" + else + print *, "[ERROR] Combined configuration is invalid" + end if + print *, "" + + ! 测试5: 物理模块禁用测试 + print *, "5. Testing physics module disabled..." + print *, "---------------------------------------" + + config%enable_physics = .false. + config%verbose = .false. + + is_valid = validate_config(config) + if (is_valid) then + print *, "[OK] Configuration valid even with physics disabled" + else + print *, "[ERROR] Configuration should be valid with physics disabled" + end if + print *, "" + + ! 测试6: 错误配置测试 + print *, "6. Testing error handling..." + print *, "-----------------------------" + + config%verbose = .true. + config%enable_physics = .true. + config%recon_scheme = "unknown_scheme" + config%flux_type = "unknown_flux" + config%equation_type = "unknown_equation" + config%problem_type = "unknown_problem" + + is_valid = validate_config(config) + if (.not. is_valid) then + print *, "[OK] Invalid configuration correctly rejected" + else + print *, "[ERROR] Invalid configuration should have been rejected" + end if + print *, "" + + print *, "=== Component Manager Physics Test Summary ===" + print *, "✓ Component manager info works" + print *, "✓ Configuration validation works with physics" + print *, "✓ Error handling works correctly" + print *, "✓ Combined physics+numerics validation works" + print *, "" + print *, "下一步: 集成物理模块到求解器框架" + +end program test_component_manager_physics \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_config_physics.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_config_physics.f90 new file mode 100644 index 000000000..c6fef5c0b --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_config_physics.f90 @@ -0,0 +1,141 @@ +! tests/test_config_physics.f90 (修复版) +program test_config_physics + use base_modules, only: wp + use config_module, only: cfd_config, config_print, config_with_reconstruction + + implicit none + + type(cfd_config) :: config + + print *, "=== Configuration Physics Test (Simplified) ===" + print *, "" + + ! 测试1: 默认配置 + print *, "1. Testing default configuration..." + print *, "-----------------------------------" + call config_print(config) + print *, "" + + ! 测试2: 验证基础物理字段 + print *, "2. Testing basic physics fields..." + print *, "----------------------------------" + + print *, "Verifying default physics fields:" + + if (trim(config%equation_type) == "linear_advection") then + print *, " ✓ Default equation type: linear_advection" + else + print *, " ✗ Unexpected equation type: ", trim(config%equation_type) + end if + + if (trim(config%problem_type) == "linear_advection") then + print *, " ✓ Default problem type: linear_advection" + else + print *, " ✗ Unexpected problem type: ", trim(config%problem_type) + end if + + if (abs(config%domain_length - 2.0_wp) < 1e-10_wp) then + print *, " ✓ Default domain length: 2.0" + else + print *, " ✗ Unexpected domain length: ", config%domain_length + end if + + if (config%enable_physics) then + print *, " ✓ Physics enabled by default" + else + print *, " ✗ Physics not enabled by default" + end if + + print *, "" + + ! 测试3: 使用类型绑定的方法(正确的方法名) + print *, "3. Testing type-bound procedures..." + print *, "--------------------------------------" + + call config%set_physics_parameters( & + equation_type="burgers_equation", & + problem_type="sod_shock_tube", & + domain_length=3.0_wp, & + enable_physics=.false.) + + print *, "After set_physics_parameters:" + print *, " Equation type: ", trim(config%equation_type) + print *, " Problem type: ", trim(config%problem_type) + print *, " Domain length: ", config%domain_length + print *, " Physics enabled: ", config%enable_physics + + if (trim(config%equation_type) == "burgers_equation") then + print *, " ✓ Equation type modified successfully via set_physics_parameters" + end if + + if (trim(config%problem_type) == "sod_shock_tube") then + print *, " ✓ Problem type modified successfully via set_physics_parameters" + end if + + if (abs(config%domain_length - 3.0_wp) < 1e-10_wp) then + print *, " ✓ Domain length modified successfully via set_physics_parameters" + end if + + if (.not. config%enable_physics) then + print *, " ✓ Physics disabled successfully via set_physics_parameters" + end if + + print *, "" + + ! 测试4: 调用get_physics_info方法 + print *, "4. Testing get_physics_info method..." + print *, "--------------------------------------" + call config%get_physics_info() + print *, "" + + ! 测试5: 高斯脉冲配置 + print *, "5. Testing Gaussian pulse configuration..." + print *, "-----------------------------------------" + + config%ic_type = "gaussian" + config%pulse_center = 0.6_wp + config%pulse_width = 0.15_wp + + print *, "Gaussian pulse parameters:" + print *, " IC type: ", trim(config%ic_type) + print *, " Center: ", config%pulse_center + print *, " Width: ", config%pulse_width + + if (trim(config%ic_type) == "gaussian") then + print *, " ✓ Gaussian IC type set" + end if + + if (abs(config%pulse_center - 0.6_wp) < 1e-10_wp) then + print *, " ✓ Pulse center set" + end if + + if (abs(config%pulse_width - 0.15_wp) < 1e-10_wp) then + print *, " ✓ Pulse width set" + end if + + print *, "" + + ! 测试6: 重构配置 + print *, "6. Testing reconstruction configuration..." + print *, "------------------------------------------" + + call config_with_reconstruction(config, "weno", 5) + + print *, "Reconstruction configuration:" + print *, " Scheme: ", trim(config%recon_scheme) + print *, " Order: ", config%spatial_order + + if (trim(config%recon_scheme) == "weno" .and. config%spatial_order == 5) then + print *, " ✓ WENO5 configuration successful" + else + print *, " ✗ Reconstruction configuration failed" + end if + + print *, "" + + print *, "=== Configuration Physics Test Complete ===" + print *, "✓ Config module updated with physics support" + print *, "✓ Fields can be directly accessed and modified" + print *, "✓ Type-bound procedures work correctly" + +end program test_config_physics \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_domain_solution.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_domain_solution.f90 new file mode 100644 index 000000000..ff659bac2 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_domain_solution.f90 @@ -0,0 +1,102 @@ +! tests/test_domain_solution.f90 +program test_domain_solution + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create, solution_reset + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + real(wp), allocatable :: initial_values(:) + integer :: i + + print *, "=== Domain and Solution Test ===" + print *, "" + + ! 测试1: 不同重构方案的ghost层计算 + print *, "1. Testing ghost layer calculation..." + print *, "--------------------------------------" + + ! ENO3 + call config_with_reconstruction(config, "eno", 3) + config%verbose = .false. + call mesh%init(ncells=10) + domain = domain_create(config, mesh) + print *, "ENO3: nghosts = ", domain%nghosts, " (expected: 3)" + + ! WENO3 + call config_with_reconstruction(config, "weno3", 3) + domain = domain_create(config, mesh) + print *, "WENO3: nghosts = ", domain%nghosts, " (expected: 2)" + + ! WENO5 + call config_with_reconstruction(config, "weno", 5) + domain = domain_create(config, mesh) + print *, "WENO5: nghosts = ", domain%nghosts, " (expected: 3)" + print *, "" + + ! 测试2: Solution数组 + print *, "2. Testing solution arrays..." + print *, "------------------------------" + + call config_with_reconstruction(config, "eno", 3) + config%verbose = .true. + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=10) + + domain = domain_create(config, mesh) + call domain%print_info() + print *, "" + + solution = solution_create(domain) + call solution%print_info() + print *, "" + + ! 测试3: 初始化和更新 + print *, "3. Testing initialization and update..." + print *, "----------------------------------------" + + allocate(initial_values(mesh%ncells)) + do i = 1, mesh%ncells + initial_values(i) = sin(2.0_wp * 3.14159265358979_wp * mesh%xcc(i) / mesh%L) + end do + + call solution%initialize(initial_values) + print *, "After initialization:" + print *, " u range: ", minval(solution%u), " to ", maxval(solution%u) + print *, " un range: ", minval(solution%un), " to ", maxval(solution%un) + + ! 修改当前解,测试更新 + solution%u = solution%u * 2.0_wp + call solution%update_old_field() + print *, "After update: max|u - un| = ", maxval(abs(solution%u - solution%un)) + print *, "" + + ! 测试4: 重置 + print *, "4. Testing reset..." + print *, "-------------------" + + call solution_reset(solution) + print *, "After reset:" + print *, " u max: ", maxval(abs(solution%u)) + print *, " un max: ", maxval(abs(solution%un)) + print *, " flux max: ", maxval(abs(solution%flux)) + print *, "" + + deallocate(initial_values) + + print *, "=== Test Summary ===" + print *, "✓ Ghost layer calculation works" + print *, "✓ Domain creation works" + print *, "✓ Solution arrays work" + print *, "✓ Initialization works" + print *, "✓ Field update works" + print *, "✓ Reset works" + print *, "" + print *, "Ready for next step: Implementing Physics modules" + +end program test_domain_solution \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_factory_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_factory_simple.f90 new file mode 100644 index 000000000..db65da7cf --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_factory_simple.f90 @@ -0,0 +1,58 @@ +! tests/test_factory_simple.f90 (修复版) +program test_factory_simple + use base_modules, only: wp ! ← 添加这行 + use config_module, only: cfd_config, config_print + use mesh_module, only: mesh_type + use reconstructor_base_module, only: reconstructor_base + use eno_reconstructor_module, only: eno_reconstructor + use weno3_reconstructor_module, only: weno3_reconstructor + use flux_base_module, only: flux_calculator_base + use rusanov_flux_module, only: rusanov_flux + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(eno_reconstructor) :: eno + type(weno3_reconstructor) :: weno3 + type(rusanov_flux) :: rusanov + + print *, "=== Factory Pattern Simple Test ===" + print *, "" + + ! Test 1: Basic systems + print *, "1. Testing basic systems..." + print *, "-----------------------------" + call config_print(config) + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 2: Creating reconstructors + print *, "2. Testing reconstructors..." + print *, "------------------------------" + + ! 创建并测试ENO重构器 + print *, "Creating ENO reconstructor..." + eno = eno_reconstructor() ! 使用构造函数 + call eno%info() ! 必须调用info方法 + + print *, "" + print *, "Creating WENO3 reconstructor..." + weno3 = weno3_reconstructor() ! 使用构造函数 + call weno3%info() ! 必须调用info方法 + print *, "" + + ! Test 3: Creating flux calculator + print *, "3. Testing flux calculator..." + print *, "-------------------------------" + + print *, "Creating Rusanov flux calculator..." + rusanov = rusanov_flux() ! 使用构造函数 + call rusanov%info() ! 必须调用info方法 + print *, "" + + print *, "=== Factory pattern simple test completed successfully ===" + +end program test_factory_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_infrastructure.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_infrastructure.f90 new file mode 100644 index 000000000..22fa92d1a --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_infrastructure.f90 @@ -0,0 +1,56 @@ +! tests/test_infrastructure.f90 (原test_basic_only.f90) +program test_infrastructure + use base_modules, only: wp + use config_module, only: cfd_config, config_print + use mesh_module, only: mesh_type + use registry_module, only: registry_init, registry_cleanup, & + register_component_simple, list_components + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + + print *, "=== 基础设施测试 ===" + print *, "" + + ! 测试1: 配置 + print *, "1. 测试配置模块..." + print *, "-------------------" + call config_print(config) + print *, "" + + ! 测试2: 网格 + print *, "2. 测试网格模块..." + print *, "------------------" + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=5) + print *, "网格初始化:" + print *, " 单元数: ", mesh%ncells + print *, " 节点数: ", mesh%nnodes + print *, " 网格间距: ", mesh%dx + print *, "" + + ! 测试3: 注册系统 + print *, "3. 测试注册系统..." + print *, "------------------" + + call registry_init() + + ! 注册组件(使用简化版本) + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("flux", "rusanov") + + ! 列出组件 + call list_components() + print *, "" + + ! 清理 + call registry_cleanup() + + print *, "=== 基础设施测试通过 ===" + print *, "✓ 配置模块工作正常" + print *, "✓ 网格模块工作正常" + print *, "✓ 注册系统工作正常" + +end program test_infrastructure \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_initial_condition.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_initial_condition.f90 new file mode 100644 index 000000000..1c6064b60 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_initial_condition.f90 @@ -0,0 +1,96 @@ +! tests/test_initial_condition.f90 +program test_initial_condition + use base_modules, only: wp + use config_module, only: cfd_config + use mesh_module, only: mesh_type + use domain_module, only: domain_type, domain_create + use solution_module, only: solution_type, solution_create + use ic_factory_module, only: create_initial_condition + use ic_base_module, only: initial_condition + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(domain_type) :: domain + type(solution_type) :: solution + class(initial_condition), allocatable :: ic + integer :: i + + print *, "=== 初始条件模块测试 ===" + print *, "" + + ! 创建配置和网格 + config%verbose = .false. + config%recon_scheme = "eno" + config%spatial_order = 3 + + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=10) + domain = domain_create(config, mesh) + solution = solution_create(domain) + + ! 测试1: 阶跃函数初始条件 + print *, "1. 测试阶跃函数初始条件..." + call create_initial_condition("step", ic) + + if (allocated(ic)) then + call ic%apply(solution) + print *, " 成功应用阶跃函数初始条件" + print *, " 解范围: ", minval(solution%u), " 到 ", maxval(solution%u) + + ! 检查结果 + if (abs(maxval(solution%u) - 2.0_wp) < 1e-10_wp .and. & + abs(minval(solution%u) - 1.0_wp) < 1e-10_wp) then + print *, " ✓ 阶跃函数测试通过" + else + print *, " ✗ 阶跃函数测试失败" + end if + end if + + deallocate(ic) + print *, "" + + ! 测试2: 正弦波初始条件 + print *, "2. 测试正弦波初始条件..." + call create_initial_condition("sin", ic) + + if (allocated(ic)) then + call solution%reset() + call ic%apply(solution) + print *, " 成功应用正弦波初始条件" + print *, " 解范围: ", minval(solution%u), " 到 ", maxval(solution%u) + print *, " ✓ 正弦波测试通过" + end if + + deallocate(ic) + print *, "" + + ! 测试3: 高斯脉冲初始条件 + print *, "3. 测试高斯脉冲初始条件..." + call create_initial_condition("gaussian", ic) + + if (allocated(ic)) then + call solution%reset() + call ic%apply(solution) + print *, " 成功应用高斯脉冲初始条件" + print *, " 解范围: ", minval(solution%u), " 到 ", maxval(solution%u) + print *, " ✓ 高斯脉冲测试通过" + end if + + deallocate(ic) + print *, "" + + ! 测试4: 错误处理 + print *, "4. 测试错误处理..." + call create_initial_condition("unknown", ic) + + if (allocated(ic)) then + print *, " ✓ 错误处理测试通过(使用了默认初始条件)" + else + print *, " ✗ 错误处理测试失败" + end if + + print *, "" + print *, "=== 初始条件模块测试完成 ===" + +end program test_initial_condition \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_physics.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_physics.f90 new file mode 100644 index 000000000..784a3ffc4 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_physics.f90 @@ -0,0 +1,20 @@ +! tests/test_physics.f90 +program test_physics + use base_modules, only: wp, ip + implicit none + + print *, "=== 物理模块测试(简化版)===" + print *, "" + + print *, "1. 测试基本物理概念..." + print *, " ✓ 物理模块占位符" + print *, "" + + print *, "2. 测试阶跃函数逻辑..." + print *, " ✓ 阶跃函数逻辑占位符" + print *, "" + + print *, "=== 物理模块测试完成 ===" + print *, "注意: 这是简化测试,物理模块尚未完全集成" + +end program test_physics \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_physics_solver.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_physics_solver.f90 new file mode 100644 index 000000000..0c83641f8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_physics_solver.f90 @@ -0,0 +1,85 @@ +! tests/test_physics_solver.f90 (简化修正版) +program test_physics_solver + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction + use mesh_module, only: mesh_type + use physics_solver_module, only: physics_solver, SOLVER_COMPLETED + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(physics_solver) :: psolver + real(wp) :: final_time + integer :: state + + print *, "=== Physics Solver Test (简化版) ===" + print *, "" + + ! 测试1: 创建物理求解器 + print *, "1. Creating physics solver..." + print *, "-----------------------------" + + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%dt = 0.01_wp + config%enable_physics = .true. + + print *, "Configuration:" + print *, " Scheme: ", trim(config%recon_scheme) + print *, " dt: ", config%dt + print *, " Physics enabled: ", config%enable_physics + + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=10) + + ! 设置求解器配置和网格 + psolver%config = config + psolver%mesh = mesh + + print *, " Solver created successfully" + print *, "" + + ! 测试2: 初始化 + print *, "2. Initializing physics solver..." + print *, "---------------------------------" + + call psolver%initialize() + state = psolver%get_state() + + print *, " State after initialization: ", state + print *, " Expected: initialized (1)" + print *, "" + + ! 测试3: 运行一小段时间 + print *, "3. Running physics solver (short time)..." + print *, "------------------------------------------" + + call psolver%run_to_time(0.02_wp) + state = psolver%get_state() + + print *, " State after run: ", state + print *, " Expected: completed (3)" + print *, " Current time: ", psolver%current_time + print *, " Current step: ", psolver%current_step + print *, "" + + ! 测试4: 清理 + print *, "4. Testing cleanup..." + print *, "----------------------" + + call psolver%cleanup() + state = psolver%get_state() + + print *, " State after cleanup: ", state + print *, " Expected: uninitialized (0)" + print *, "" + + ! 结果验证 + print *, "=== Test Summary ===" + if (state == 0) then + print *, "✓ All basic tests passed" + else + print *, "✗ Some tests failed" + end if + +end program test_physics_solver \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_physics_solver_simple.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_physics_solver_simple.f90 new file mode 100644 index 000000000..13312efde --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_physics_solver_simple.f90 @@ -0,0 +1,161 @@ +! tests/test_physics_solver_simple.f90 +program test_physics_solver_simple + use base_modules, only: wp + use config_module, only: cfd_config, config_with_reconstruction + use mesh_module, only: mesh_type + use physics_solver_module, only: physics_solver, SOLVER_COMPLETED + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(physics_solver) :: solver + real(wp) :: final_time, final_step + integer :: state + + print *, "=========================================" + print *, " 简单物理求解器测试" + print *, "=========================================" + print *, "" + + ! 步骤1: 配置 + print *, "[步骤1] 配置求解器..." + print *, "---------------------" + + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%dt = 0.01_wp + config%final_time = 0.1_wp + config%wave_speed = 1.0_wp + config%ic_type = "step" + config%boundary_type = "periodic" + config%equation_type = "linear_advection" + config%problem_type = "linear_advection" + config%enable_physics = .true. + config%domain_length = 1.0_wp + + print *, "配置参数:" + print *, " 重构格式: ", trim(config%recon_scheme) + print *, " 时间步长: ", config%dt + print *, " 最终时间: ", config%final_time + print *, " 波速: ", config%wave_speed + print *, "" + + ! 步骤2: 创建网格 + print *, "[步骤2] 创建网格..." + print *, "-------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + + print *, "网格信息:" + print *, " 单元数: ", mesh%ncells + print *, " 节点数: ", mesh%nnodes + print *, " 网格间距: ", mesh%dx + print *, "" + + ! 步骤3: 创建求解器 + print *, "[步骤3] 创建求解器..." + print *, "---------------------" + + solver = physics_solver(config, mesh) + + print *, "求解器创建成功" + print *, " 初始状态: ", solver%get_state() + print *, "" + + ! 步骤4: 初始化 + print *, "[步骤4] 初始化求解器..." + print *, "-----------------------" + + call solver%initialize() + + state = solver%get_state() + print *, "初始化完成" + print *, " 状态: ", state + print *, " 当前时间: ", solver%current_time + print *, " 当前步数: ", solver%current_step + print *, "" + + ! 步骤5: 运行求解器 + print *, "[步骤5] 运行求解器..." + print *, "---------------------" + + call solver%run_to_time(config%final_time) + + state = solver%get_state() + print *, "运行完成" + print *, " 状态: ", state + print *, " 最终时间: ", solver%current_time + print *, " 总步数: ", solver%current_step + print *, "" + + ! 步骤6: 保存结果 + print *, "[步骤6] 保存结果..." + print *, "-------------------" + + final_time = solver%current_time + final_step = real(solver%current_step, wp) + state = solver%get_state() + + print *, "保存的结果:" + print *, " 状态: ", state + print *, " 时间: ", final_time + print *, " 步数: ", final_step + print *, "" + + ! 步骤7: 清理求解器 + print *, "[步骤7] 清理求解器..." + print *, "---------------------" + + call solver%cleanup() + + print *, "清理后状态:" + print *, " 状态: ", solver%get_state() + print *, " 时间: ", solver%current_time + print *, " 步数: ", solver%current_step + print *, "" + + ! 步骤8: 验证结果 + print *, "[步骤8] 验证结果..." + print *, "-------------------" + + print *, "验证标准:" + print *, " 1. 运行后状态应为 COMPLETED (", SOLVER_COMPLETED, ")" + print *, " 2. 最终时间应接近 ", config%final_time + print *, " 3. 步数应大于 0" + print *, "" + + if (state == SOLVER_COMPLETED) then + print *, "✓ 状态验证通过: COMPLETED" + else + print *, "✗ 状态验证失败: 期望 ", SOLVER_COMPLETED, ", 实际 ", state + end if + + if (abs(final_time - config%final_time) < 1e-5_wp) then + print *, "✓ 时间验证通过: ", final_time, " ≈ ", config%final_time + else + print *, "✗ 时间验证失败: ", final_time, " ≠ ", config%final_time + end if + + if (final_step > 0) then + print *, "✓ 步数验证通过: ", final_step, " > 0" + else + print *, "✗ 步数验证失败: ", final_step, " ≤ 0" + end if + + print *, "" + + ! 最终判断 + if (state == SOLVER_COMPLETED .and. & + abs(final_time - config%final_time) < 1e-5_wp .and. & + final_step > 0) then + print *, "=========================================" + print *, " 所有测试通过! ✓" + print *, "=========================================" + else + print *, "=========================================" + print *, " 测试失败 ✗" + print *, "=========================================" + end if + +end program test_physics_solver_simple \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_registry.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_registry.f90 new file mode 100644 index 000000000..e82651ffb --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_registry.f90 @@ -0,0 +1,87 @@ +! tests/test_registry.f90 (原test_minimal_simple.f90) +program test_registry + use base_modules, only: wp + use registry_module + use config_module + use mesh_module + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== 注册系统功能测试 ===" + print *, "" + + ! 测试1: 配置系统 + print *, "1. 测试配置系统" + print *, "--------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! 测试2: 网格系统 + print *, "2. 测试网格系统" + print *, "--------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! 测试3: 注册系统 + print *, "3. 测试注册系统" + print *, "--------------" + + call registry_init() + + ! 注册组件 + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call list_components() + print *, "注册表大小: ", registry_get_size() + print *, "" + + ! 测试组件查找 + print *, "4. 测试组件查找" + print *, "--------------" + + if (has_component_simple("reconstructor", "eno")) then + print *, "找到: reconstructor.eno" + else + print *, "未找到: reconstructor.eno" + end if + + if (has_component_simple("reconstructor", "unknown")) then + print *, "找到: reconstructor.unknown" + else + print *, "未找到: reconstructor.unknown" + end if + print *, "" + + ! 测试获取可用组件 + print *, "5. 测试注册系统功能" + print *, "------------------" + print *, "注册表已初始化: ", registry_is_initialized() + print *, "组件数量: ", registry_get_size() + print *, "" + + ! 清理 + call registry_cleanup() + + print *, "=== 注册系统测试完成 ===" + +end program test_registry \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_simple_link.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_simple_link.f90 new file mode 100644 index 000000000..71cc614e8 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_simple_link.f90 @@ -0,0 +1,78 @@ +! tests/test_simple_link.f90 +program test_simple_link + use base_modules, only: wp ! ← 添加这行 + use registry_module + use config_module + use mesh_module + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + integer :: i + + print *, "=== Minimal Functionality Test ===" + print *, "" + + ! Test 1: Configuration system + print *, "1. Testing configuration system" + print *, "--------------------------------" + + call config_print(config) + + call config_with_reconstruction(config, "eno", 3) + + call config_print(config) + print *, "" + + ! Test 2: Mesh system + print *, "2. Testing mesh system" + print *, "----------------------" + + call mesh%init(xmin=0.0_wp, xmax=1.0_wp, ncells=10) + call mesh%print_info() + print *, "" + + ! Test 3: Registry system + print *, "3. Testing registry system" + print *, "--------------------------" + + call registry_init() + + ! Register some components + call register_component_simple("reconstructor", "eno") + call register_component_simple("reconstructor", "weno3") + call register_component_simple("reconstructor", "weno5") + call register_component_simple("flux", "rusanov") + call register_component_simple("flux", "engquist-osher") + call register_component_simple("boundary", "periodic") + call register_component_simple("boundary", "dirichlet") + call register_component_simple("integrator", "rk1") + call register_component_simple("integrator", "rk2") + call register_component_simple("integrator", "rk3") + + call list_components() + print *, "Registry size: ", registry_get_size() + print *, "" + + ! Test component lookup + print *, "4. Testing component lookup" + print *, "---------------------------" + if (has_component_simple("reconstructor", "eno")) then + print *, "Found: reconstructor.eno" + else + print *, "Not found: reconstructor.eno" + end if + + if (has_component_simple("reconstructor", "unknown")) then + print *, "Found: reconstructor.unknown" + else + print *, "Not found: reconstructor.unknown" + end if + print *, "" + + ! Cleanup + call registry_cleanup() + + print *, "=== Minimal test completed successfully ===" + +end program test_simple_link \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_solver_base.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_solver_base.f90 new file mode 100644 index 000000000..6cfe47e41 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_solver_base.f90 @@ -0,0 +1,99 @@ +! tests/test_solver_base.f90 (修复版) +program test_solver_base + ! 所有 USE 语句必须在程序开始处 + use base_modules, only: wp + use config_module, only: cfd_config, config_print, config_with_reconstruction + use mesh_module, only: mesh_type + use solver_base_module, only: solver_base, SOLVER_UNINITIALIZED, & + SOLVER_INITIALIZED, SOLVER_COMPLETED, SOLVER_ERROR + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(solver_base) :: solver + integer :: state + + print *, "=== Solver Base Test ===" + print *, "" + + ! 测试1: 创建求解器 + print *, "1. Creating solver..." + print *, "----------------------" + + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%dt = 0.01_wp + + call config_print(config) + + call mesh%init(xmin=0.0_wp, xmax=2.0_wp, ncells=10) + + solver = solver_base(config, mesh) + call solver%print_info() + print *, "" + + ! 测试2: 初始化 + print *, "2. Initializing solver..." + print *, "-------------------------" + + call solver%initialize() + state = solver%get_state() + print *, "State after initialization: ", state + print *, "Expected: ", SOLVER_INITIALIZED + print *, "Match? ", state == SOLVER_INITIALIZED + print *, "Error message: '", trim(solver%get_error()), "'" + print *, "" + + ! 测试3: 运行求解器 + print *, "3. Running solver..." + print *, "--------------------" + + call solver%run_to_time(0.05_wp) + state = solver%get_state() + print *, "State after run: ", state + print *, "Expected: ", SOLVER_COMPLETED + print *, "Match? ", state == SOLVER_COMPLETED + print *, "Current time: ", solver%current_time + print *, "Current step: ", solver%current_step + print *, "" + + ! 测试4: 再次运行(从已完成状态) + print *, "4. Running again from completed state..." + print *, "----------------------------------------" + + ! 需要先清理才能重新运行 + call solver%cleanup() + call solver%initialize() + call solver%run_to_time(0.1_wp) + + call solver%print_info() + print *, "" + + ! 测试5: 错误处理 + print *, "5. Testing error states..." + print *, "--------------------------" + + ! 创建一个未初始化的求解器 + call solver%cleanup() + state = solver%get_state() + print *, "Uninitialized state: ", state + print *, "Expected: ", SOLVER_UNINITIALIZED + print *, "Match? ", state == SOLVER_UNINITIALIZED + + ! 尝试运行未初始化的求解器 + call solver%run_to_time(0.01_wp) + state = solver%get_state() + print *, "State after error: ", state + print *, "Expected: ", SOLVER_ERROR + print *, "Match? ", state == SOLVER_ERROR + print *, "Error message: '", trim(solver%get_error()), "'" + print *, "" + + print *, "=== Solver Base Test Complete ===" + print *, "✓ Solver base class works" + print *, "✓ State management works" + print *, "✓ Time stepping framework works" + print *, "✓ Error handling works" + +end program test_solver_base \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_solver_framework.f90 b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_solver_framework.f90 new file mode 100644 index 000000000..6754323d0 --- /dev/null +++ b/example/1d-linear-convection/weno3/fortran/registry/03g/tests/test_solver_framework.f90 @@ -0,0 +1,91 @@ +! tests/test_solver_framework.f90 +program test_solver_framework + use, intrinsic :: iso_fortran_env, only: real64 + use config_module, only: cfd_config, config_print, config_with_reconstruction + use mesh_module, only: mesh_type + use solver_module, only: cfd_solver, solver_create, solver_run, solver_cleanup + use solver_module, only: SOLVER_UNINITIALIZED, SOLVER_INITIALIZED, & + SOLVER_COMPLETED, SOLVER_ERROR + + implicit none + + type(cfd_config) :: config + type(mesh_type) :: mesh + type(cfd_solver) :: solver + + print *, "=== 求解器框架测试 ===" + print *, "" + + ! 测试1: 基本创建 + print *, "1. 测试求解器创建..." + print *, "----------------------" + + ! 创建配置 + config%verbose = .true. + call config_with_reconstruction(config, "eno", 3) + config%flux_type = "rusanov" + config%wave_speed = 1.0_real64 + config%dt = 0.01_real64 + + call config_print(config) + print *, "" + + ! 创建网格 + call mesh%init(xmin=0.0_real64, xmax=2.0_real64, ncells=20) + call mesh%print_info() + print *, "" + + ! 创建求解器 + solver = solver_create(config, mesh) + print *, "✓ 求解器创建成功" + print *, " 状态: ", solver%get_state() + print *, "" + + ! 测试2: 求解器初始化 + print *, "2. 测试求解器初始化..." + print *, "------------------------" + + call solver%initialize() + print *, "✓ 求解器初始化完成" + print *, " 状态: ", solver%get_state() + print *, " 错误信息: '", trim(solver%get_error()), "'" + print *, "" + + ! 测试3: 简单运行 + print *, "3. 测试求解器运行..." + print *, "----------------------" + + call solver_run(solver, 0.05_real64) ! 运行到0.05秒 + print *, "✓ 求解器运行完成" + print *, " 最终状态: ", solver%get_state() + print *, "" + + ! 测试4: 清理 + print *, "4. 测试求解器清理..." + print *, "----------------------" + + call solver_cleanup(solver) + print *, "✓ 求解器清理完成" + print *, " 状态: ", solver%get_state() + print *, "" + + ! 测试5: 错误处理 + print *, "5. 测试错误处理..." + print *, "-------------------" + + ! 尝试重复初始化 + call solver%initialize() + print *, " 重复初始化状态: ", solver%get_state() + print *, " 错误信息: '", trim(solver%get_error()), "'" + + call solver_cleanup(solver) + print *, "" + + print *, "=== 框架测试总结 ===" + print *, "✓ 求解器创建/初始化/运行/清理流程验证完成" + print *, "✓ 状态管理正常工作" + print *, "✓ 错误处理机制就绪" + print *, "" + print *, "下一步: 添加实际数值计算功能" + +end program test_solver_framework \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01/julia/boundary.jl b/example/1d-linear-convection/weno3/julia/01/julia/boundary.jl new file mode 100644 index 000000000..5b0baaf43 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01/julia/boundary.jl @@ -0,0 +1,90 @@ +# julia/boundary.jl +# 目标:与 Python boundary.py 行为完全一致(1:1 同构) +# 前提:ist/ied 在 Julia 中按 1-based 设置(ist = nghosts + 1) + +using NPZ # 仅用于测试,实际模块可不依赖 + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象基类(Julia 风格:用函数接口) ---------------------- +# Julia 无 abstract class,用文档+约定 +# 所有子类型必须实现 apply!(bc, u) + +# ---------------------- PeriodicBoundary ---------------------- +mutable struct PeriodicBoundary + cfd::Any +end + +function apply!(self::PeriodicBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist # 1-based, e.g., 3 + ied = self.cfd.domain.ied # 1-based, e.g., 43 + + # 左 ghost: u[ist - 1 - ig] = u[ied - 1 - ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ied - 1 - ig] + end + # 右 ghost: u[ied + ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ied + ig] = u[ist + ig] + end +end + +# ---------------------- DirichletBoundary ---------------------- +mutable struct DirichletBoundary + cfd::Any +end + +function apply!(self::DirichletBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + left_value = getfield_safe(self.cfd.config, :left_boundary_value, 1.0) + right_value = getfield_safe(self.cfd.config, :right_boundary_value, 2.0) + + # 左边界 + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = left_value + end + # 右边界 + for ig in 0:(nghosts-1) + u[ied + ig] = right_value + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Dirichlet边界: 左值=$left_value, 右值=$right_value") + end +end + +# ---------------------- NeumannBoundary ---------------------- +mutable struct NeumannBoundary + cfd::Any +end + +function apply!(self::NeumannBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + # 左边界零梯度:u[ist - 1 - ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ist + ig] + end + # 右边界零梯度:u[ied + ig] = u[ied - 1 - ig] ← 注意是 ied - 1 - ig + for ig in 0:(nghosts-1) + u[ied + ig] = u[ied - 1 - ig] + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Neumann边界: 零梯度") + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01/julia/test/test_boundary.jl b/example/1d-linear-convection/weno3/julia/01/julia/test/test_boundary.jl new file mode 100644 index 000000000..ef27d6820 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01/julia/test/test_boundary.jl @@ -0,0 +1,57 @@ +# julia/test/test_boundary.jl +using NPZ +include("../boundary.jl") + +struct MockConfig + boundary_type::String + left_boundary_value::Float64 + right_boundary_value::Float64 + debug::Bool +end + +struct MockDomain + nghosts::Int + ist::Int # Julia 本地索引(1-based) + ied::Int + ntcells::Int +end + +struct MockCfd + config::MockConfig + domain::MockDomain +end + +# ===== 关键:使用 Julia 本地索引规则 ===== +nghosts = 2 +ncells = 40 +ist = nghosts + 1 # = 3 +ied = ist + ncells # = 43 +ntcells = ncells + 2 * nghosts # = 44 + +config = MockConfig("dirichlet", 0.5, 1.5, true) +domain = MockDomain(nghosts, ist, ied, ntcells) +cfd_mock = MockCfd(config, domain) + +# 加载 Python 生成的 u_input.npy +# 注意:Python u[0] → Julia u[1],所以内容完全对应 +u_input = npzread("../../python/u_input.npy") +@assert length(u_input) == ntcells "数组长度不匹配!" + +# 测试三种边界 +test_cases = [ + ("periodic", PeriodicBoundary(cfd_mock)), + ("dirichlet", DirichletBoundary(cfd_mock)), + ("neumann", NeumannBoundary(cfd_mock)) +] + +for (name, bc) in test_cases + u = copy(u_input) + apply!(bc, u) + + u_py = npzread("../../python/u_$(name)_py.npy") + err = maximum(abs.(u .- u_py)) + println("边界: $(name) | 最大误差: $(err)") + @assert err < 1e-12 "❌ $(name) 不一致!" +end + +println("✅ 所有边界条件测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01/python/boundary.py b/example/1d-linear-convection/weno3/julia/01/python/boundary.py new file mode 100644 index 000000000..6054f92de --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01/python/boundary.py @@ -0,0 +1,103 @@ +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from factories.base_factory import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01/python/cfd_registry.py b/example/1d-linear-convection/weno3/julia/01/python/cfd_registry.py new file mode 100644 index 000000000..c12eb106c --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01/python/cfd_registry.py @@ -0,0 +1,62 @@ +# cfd_registry.py +""" +CFD注册系统统一导入模块 +所有其他模块从这里导入注册系统 +""" + +import sys +import os + +# 添加当前目录到路径,确保能找到registry.py +current_dir = os.path.dirname(os.path.abspath(__file__)) +if current_dir not in sys.path: + sys.path.insert(0, current_dir) + +try: + # 尝试导入注册系统 + from registry import ComponentRegistry, register_component + REGISTRY_AVAILABLE = True +except ImportError: + # 如果找不到,提供简单的空实现 + print("⚠️ 警告: 未找到 registry.py,使用最小化回退实现") + + class ComponentRegistry: + """最小化的注册系统回退实现""" + _registries = {} + + @classmethod + def register(cls, category, name, component_class): + if category not in cls._registries: + cls._registries[category] = {} + cls._registries[category][name] = component_class + + @classmethod + def get(cls, category, name): + if category not in cls._registries: + raise ValueError(f"未知类别: {category}") + if name not in cls._registries[category]: + raise ValueError(f"未找到: {category}.{name}") + return cls._registries[category][name] + + @classmethod + def create(cls, category, name, *args, **kwargs): + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) + for cat, comps in cls._registries.items()} + + def register_component(category, name=None): + """简化的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + REGISTRY_AVAILABLE = False + +# 导出统一的接口 +__all__ = ['ComponentRegistry', 'register_component', 'REGISTRY_AVAILABLE'] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01/python/config.py b/example/1d-linear-convection/weno3/julia/01/python/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01/python/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01/python/domain.py b/example/1d-linear-convection/weno3/julia/01/python/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01/python/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01/python/factories/base_factory.py b/example/1d-linear-convection/weno3/julia/01/python/factories/base_factory.py new file mode 100644 index 000000000..7b8b6105e --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01/python/factories/base_factory.py @@ -0,0 +1,49 @@ +# factories/base_factory.py +""" +工厂基类 - 提供统一的组件创建接口 +""" + +from cfd_registry import ComponentRegistry +from typing import Any, Optional + +class BaseFactory: + """工厂基类,提供统一的组件创建方法""" + + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + """ + 统一创建组件的方法 + + Args: + category: 组件类别(如'boundary', 'flux'等) + name: 组件名称(如'periodic', 'rusanov'等) + *args, **kwargs: 传递给构造函数的参数 + + Returns: + 创建的组件实例 + + Raises: + ValueError: 如果组件不存在 + """ + name_lower = name.lower() + + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + # 获取可用组件列表 + available = ComponentRegistry.list_all().get(category, []) + + # 构建友好的错误信息 + if available: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"可用类型:{available}") + else: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"({category}类别下没有注册任何组件)") + + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + """获取指定类别的可用组件列表""" + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01/python/flux.py b/example/1d-linear-convection/weno3/julia/01/python/flux.py new file mode 100644 index 000000000..beb9ed488 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01/python/flux.py @@ -0,0 +1,73 @@ +""" +通量计算器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册,替代硬编码工厂 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象通量计算基类(统一接口) ---------------------- +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass + +# ---------------------- 2. 具体通量计算子类(使用装饰器注册) ---------------------- + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + +class FluxCalculatorFactory: + """通量计算器工厂""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD对象 + + Returns: + 通量计算器实例 + """ + from factories.base_factory import BaseFactory + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01/python/gen_boundary_test_data.py b/example/1d-linear-convection/weno3/julia/01/python/gen_boundary_test_data.py new file mode 100644 index 000000000..c7fc2a9c5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01/python/gen_boundary_test_data.py @@ -0,0 +1,35 @@ +# python/gen_boundary_test_data.py +import numpy as np +from config import CfdConfig +from mesh import Mesh +from domain import Domain + +# 固定测试配置 +config = CfdConfig() +config.with_boundary("dirichlet", left_value=0.5, right_value=1.5) +config.debug = True + +mesh = Mesh() +domain = Domain(config, mesh) + +# 构造 mock CFD 对象(仅含 config + domain) +class MockCfd: + def __init__(self, config, domain): + self.config = config + self.domain = domain + +# 测试用 u:0,1,2,...,N-1 +u_input = np.arange(domain.ntcells, dtype=np.float64) +np.save("u_input.npy", u_input) + +# 测试每种边界 +from boundary import PeriodicBoundary, DirichletBoundary, NeumannBoundary + +for bc_name, bc_class in [("periodic", PeriodicBoundary), ("dirichlet", DirichletBoundary), ("neumann", NeumannBoundary)]: + u = u_input.copy() + cfd_mock = MockCfd(config, domain) + bc = bc_class(cfd_mock) + bc.apply(u) + np.save(f"u_{bc_name}_py.npy", u) + +print("✅ 测试数据已生成:u_*.npy") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01/python/initial_condition.py b/example/1d-linear-convection/weno3/julia/01/python/initial_condition.py new file mode 100644 index 000000000..dabe7e8c1 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01/python/initial_condition.py @@ -0,0 +1,90 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = self.config.get("pulse_center", 0.5) + width = self.config.get("pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, ic_type: str, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from factories.base_factory import BaseFactory + return BaseFactory.create_component('initial_condition', ic_type, config) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01/python/mesh.py b/example/1d-linear-convection/weno3/julia/01/python/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01/python/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01/python/plotter.py b/example/1d-linear-convection/weno3/julia/01/python/plotter.py new file mode 100644 index 000000000..9f1a414fd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01/python/plotter.py @@ -0,0 +1,107 @@ +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01/python/reconstructor/__init__.py b/example/1d-linear-convection/weno3/julia/01/python/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01/python/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01/python/reconstructor/base.py b/example/1d-linear-convection/weno3/julia/01/python/reconstructor/base.py new file mode 100644 index 000000000..bbd638503 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01/python/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def reconstruct(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01/python/reconstructor/eno.py b/example/1d-linear-convection/weno3/julia/01/python/reconstructor/eno.py new file mode 100644 index 000000000..c2fb385dd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01/python/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def reconstruct(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01/python/reconstructor/factory.py b/example/1d-linear-convection/weno3/julia/01/python/reconstructor/factory.py new file mode 100644 index 000000000..efa4a1446 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01/python/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from cfd_registry import ComponentRegistry + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from factories.base_factory import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01/python/reconstructor/weno3.py b/example/1d-linear-convection/weno3/julia/01/python/reconstructor/weno3.py new file mode 100644 index 000000000..6e8c3f230 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01/python/reconstructor/weno3.py @@ -0,0 +1,88 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def reconstruct(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + """ + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 # smoothness indicator for stencil [v2, v3] + beta1 = (v2 - v1)**2 # smoothness indicator for stencil [v1, v2] + + d0, d1 = 2/3, 1/3 # optimal linear weights (for right value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 0.5 * v2 + 0.5 * v3 # reconstruction from [v2, v3] + q1 = -0.5 * v1 + 1.5 * v2 # reconstruction from [v1, v2] + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 + beta1 = (v2 - v1)**2 + + d0, d1 = 1/3, 2/3 # optimal linear weights (for left value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 1.5 * v2 - 0.5 * v3 # from [v2, v3] + q1 = 0.5 * v1 + 0.5 * v2 # from [v1, v2] + return w0 * q0 + w1 * q1 + """ diff --git a/example/1d-linear-convection/weno3/julia/01/python/registry.py b/example/1d-linear-convection/weno3/julia/01/python/registry.py new file mode 100644 index 000000000..61be90500 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01/python/registry.py @@ -0,0 +1,75 @@ +# registry.py (改进版) +""" +CFD组件注册系统 - 简洁高效版 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表""" + + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True # 控制是否打印注册信息 + + @classmethod + def set_verbose(cls, verbose: bool): + """设置是否打印注册信息""" + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + # 检查是否已注册 + if name in cls._registries[category]: + if cls._registries[category][name] == component_class: + # 相同类重复注册,静默跳过 + return + elif cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取组件类""" + if category not in cls._registries: + available = list(cls._registries.keys()) + raise ValueError(f"❌ 未知类别: {category} (可用类别: {available})") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + @classmethod + def get_count(cls) -> int: + """获取注册组件总数""" + return sum(len(components) for components in cls._registries.values()) + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01/python/residual.py b/example/1d-linear-convection/weno3/julia/01/python/residual.py new file mode 100644 index 000000000..b4d4d7dcc --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01/python/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux import FluxCalculatorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + self.reconstructor = self.cfd.reconstructor + + # 初始化通量计算器(工厂创建) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._reconstruct() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _reconstruct(self): + """私有方法:界面值重建""" + self.reconstructor.reconstruct(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01/python/run_eno_weno.py b/example/1d-linear-convection/weno3/julia/01/python/run_eno_weno.py new file mode 100644 index 000000000..fd7f38744 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01/python/run_eno_weno.py @@ -0,0 +1,52 @@ +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + #mesh = Mesh(ncells=100, L=2.0) + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行ENO3求解(使用你的链式调用) + print("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + config_eno3.with_reconstruction("eno", 3) # 显式指定3阶(也可省略,ENO默认3阶) + # 可选:覆盖默认值(如dt) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + cfd_eno3.run() # 求解并生成result字典 + + # 可选:快速验证ENO3结果 + # plotter.plot_quick(cfd_eno3.result, title="ENO3 Quick Check") + + # 3. 配置并运行WENO3求解(注意:WENO默认5阶,这里显式指定3阶) + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) # 显式指定3阶(默认是5阶) + # 可选:覆盖默认值 + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + cfd_weno3.run() + + # 4. 可选:保存结果(供离线绘图) + # cfd_eno3.save_result("eno3_result.npz") + # cfd_weno3.save_result("weno3_result.npz") + + # 5. 绘制ENO/WENO对比图 + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" # 可选:保存图片 + ) + +if __name__ == "__main__": + # 主程序入口 + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01/python/solution.py b/example/1d-linear-convection/weno3/julia/01/python/solution.py new file mode 100644 index 000000000..92a46d3f4 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01/python/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config.ic_type, config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01/python/solver.py b/example/1d-linear-convection/weno3/julia/01/python/solver.py new file mode 100644 index 000000000..0d0b442da --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01/python/solver.py @@ -0,0 +1,86 @@ +import numpy as np +import matplotlib.pyplot as plt +import inflect +from abc import ABC, abstractmethod + + +from flux import InviscidFluxCalculator, RusanovFluxCalculator, EngquistOsherFluxCalculator, FluxCalculatorFactory + +from boundary import BoundaryCondition, PeriodicBoundary, DirichletBoundary, NeumannBoundary, BoundaryConditionFactory + +from time_integration import TimeIntegrator, RK1Integrator, RK2Integrator, RK3Integrator, TimeIntegratorFactory + +from mesh import Mesh + +from reconstructor import ReconstructorFactory + +from initial_condition import InitialCondition, StepFunctionIC, SineWaveIC, GaussianPulseIC, InitialConditionFactory + +from domain import Domain +from solution import Solution + +from config import CfdConfig +from residual import ResidualCalculator + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, config, mesh): + self.config = config + self.domain = Domain(config, mesh) + self.solution = Solution(config, self.domain) + self.reconstructor = ReconstructorFactory.create(config, self.domain) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + def exact_solution(self): + """通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界""" + x = self.domain.mesh.xcc + T = self.config.final_time + c = self.config.wave_speed + L = self.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = InitialConditionFactory.create(self.config.ic_type, self.config) + return ic.evaluate_at(x_shifted) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + #runge_kutta(self) + self.integrator.step(dt) + t += dt + config.dt = dt_old + + # 整理标准化结果 + u_numerical = self.solution.u[self.domain.ist:self.domain.ied].copy() + self.result = { + "x": domain.mesh.xcc, + "numerical": u_numerical, + "analytical": self.exact_solution(), + "config": { + "scheme": self.config.recon_scheme, + "order": self.config.spatial_order, + "rk_order": self.config.rk_order, + "final_time": self.config.final_time + } + } + + return u_numerical diff --git a/example/1d-linear-convection/weno3/julia/01/python/time_integration.py b/example/1d-linear-convection/weno3/julia/01/python/time_integration.py new file mode 100644 index 000000000..25ac0b4ca --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01/python/time_integration.py @@ -0,0 +1,125 @@ +# time_integration.py + +""" +时间推进器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = cfd.residual_calculator + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(使用装饰器注册) ---------------------- + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class TimeIntegratorFactory: + """时间推进器工厂""" + + @staticmethod + def create(cfd) -> 'TimeIntegrator': + """ + 创建时间推进器实例 + + Args: + cfd: CFD对象 + + Returns: + 时间推进器实例 + """ + from factories.base_factory import BaseFactory + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01/python/u_dirichlet_py.npy b/example/1d-linear-convection/weno3/julia/01/python/u_dirichlet_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..3aa20bd2e395654a6e8c59bbbc9f5eaa955b345e GIT binary patch literal 480 zcmbWzxeftg6o%oW6O}@tQ2Y_`BNMTVeHmMHM59p2#LPq?$c$)2;vU?f%TQY8ZM07F zov8Zw0H1NzP(=`e8>IlQ+pS*SsNB7-1(=_si zx~l&#Cf|Elzsex&D8?~?Nlc-_G-fc1IV_-oMJ!<%D_BJnYgoqyHnD|m>|hsr*hdQo Wp*NHvjWVK9#x%-=MtSnjr}YA`88kxx literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01/python/u_input.npy b/example/1d-linear-convection/weno3/julia/01/python/u_input.npy new file mode 100644 index 0000000000000000000000000000000000000000..ef506fa75cacbbe63dfcc17332ddd6c33de5b888 GIT binary patch literal 480 zcmbWrI}gE70EXdX6O+MUFr0`uNFy$FzqBs35sSejjUF+GN-GwT_z(W5pP?kM2cBT{2ChY{-ZF ze<|^^w?*2qNlaq~vzSAPc`RTNOISezt60N2Hn52%wy=#I>|zi5IKUx}aEun(u}>5e R8pV`GF{4q;X%r6a*dIEnGEe{j literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01/python/u_neumann_py.npy b/example/1d-linear-convection/weno3/julia/01/python/u_neumann_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..fa723d1efce906983b6e6710379207d15afbc271 GIT binary patch literal 480 zcmbWrI}gE70EXdX6O+MUFr0`uC5^ZgRkymtMl1%CG}^`>Dy>*V;y?JqN?(i7w>|hs7?4gBy9N-W~I7U11fnvf( PG3BF}@lnkAKgRt9WDhZ? literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01/python/u_periodic_py.npy b/example/1d-linear-convection/weno3/julia/01/python/u_periodic_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..156aff85dfa4c84deaaed404e0dccc37937f8836 GIT binary patch literal 480 zcmbWzyAA+Jw5Jw7YF-Tj}jFJcs^aeGCKh-lzUPGOs<_`6u&g%W>?2t#0c7 zOUUOY-iJGid|Tp}#1y76g9@{l!#rA8L>o(3#tK%kh7Q)TflX{-8#~xV7kk*p0eWBW QC_@@$M5Bypl;8h*0 Vector{Float64} +# apply(ic, solution) + +# ---------------------- StepFunctionIC ---------------------- +struct StepFunctionIC + config::Any +end + +function evaluate_at(ic::StepFunctionIC, x::AbstractVector{Float64}) + u0 = ones(Float64, length(x)) + for i in eachindex(x) + if 0.5 <= x[i] <= 1.0 + u0[i] = 2.0 + end + end + return u0 +end + +function apply(ic::StepFunctionIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- SineWaveIC ---------------------- +struct SineWaveIC + config::Any +end + +function evaluate_at(ic::SineWaveIC, x::AbstractVector{Float64}) + L = getfield_safe(ic.config, :domain_length, 2.0) + return sin.(2π * x / L) +end + +function apply(ic::SineWaveIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- GaussianPulseIC ---------------------- +struct GaussianPulseIC + config::Any +end + +function evaluate_at(ic::GaussianPulseIC, x::AbstractVector{Float64}) + center = getfield_safe(ic.config, :pulse_center, 0.5) + width = getfield_safe(ic.config, :pulse_width, 0.1) + return exp.(-((x .- center) ./ width).^2) +end + +function apply(ic::GaussianPulseIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- 公共辅助函数 ---------------------- +""" +将初始场 values 应用到 solution.u 的物理区域(含 ghost 的数组) +""" +function _apply_to_interior(solution, values) + domain = solution.domain + # Python: for i in range(domain.ist, domain.ied) + # Julia: for i in domain.ist:domain.ied-1 (因为 ied 是结束索引,不包含) + for i in domain.ist:(domain.ied - 1) + j = i - domain.ist + 1 # values 是 1-based,i - ist 是 0-based → +1 + solution.u[i] = values[j] + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01a/julia/test/test_boundary.jl b/example/1d-linear-convection/weno3/julia/01a/julia/test/test_boundary.jl new file mode 100644 index 000000000..ef27d6820 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01a/julia/test/test_boundary.jl @@ -0,0 +1,57 @@ +# julia/test/test_boundary.jl +using NPZ +include("../boundary.jl") + +struct MockConfig + boundary_type::String + left_boundary_value::Float64 + right_boundary_value::Float64 + debug::Bool +end + +struct MockDomain + nghosts::Int + ist::Int # Julia 本地索引(1-based) + ied::Int + ntcells::Int +end + +struct MockCfd + config::MockConfig + domain::MockDomain +end + +# ===== 关键:使用 Julia 本地索引规则 ===== +nghosts = 2 +ncells = 40 +ist = nghosts + 1 # = 3 +ied = ist + ncells # = 43 +ntcells = ncells + 2 * nghosts # = 44 + +config = MockConfig("dirichlet", 0.5, 1.5, true) +domain = MockDomain(nghosts, ist, ied, ntcells) +cfd_mock = MockCfd(config, domain) + +# 加载 Python 生成的 u_input.npy +# 注意:Python u[0] → Julia u[1],所以内容完全对应 +u_input = npzread("../../python/u_input.npy") +@assert length(u_input) == ntcells "数组长度不匹配!" + +# 测试三种边界 +test_cases = [ + ("periodic", PeriodicBoundary(cfd_mock)), + ("dirichlet", DirichletBoundary(cfd_mock)), + ("neumann", NeumannBoundary(cfd_mock)) +] + +for (name, bc) in test_cases + u = copy(u_input) + apply!(bc, u) + + u_py = npzread("../../python/u_$(name)_py.npy") + err = maximum(abs.(u .- u_py)) + println("边界: $(name) | 最大误差: $(err)") + @assert err < 1e-12 "❌ $(name) 不一致!" +end + +println("✅ 所有边界条件测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01a/julia/test/test_initial_condition.jl b/example/1d-linear-convection/weno3/julia/01a/julia/test/test_initial_condition.jl new file mode 100644 index 000000000..dc58691a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01a/julia/test/test_initial_condition.jl @@ -0,0 +1,45 @@ +# julia/test/test_initial_condition.jl +using NPZ + +# 包含初始条件模块 +include("../initial_condition.jl") + +# 构造 mock config(与 Python 完全一致) +struct MockConfig + ic_type::String + domain_length::Float64 + pulse_center::Float64 + pulse_width::Float64 +end + +# 生成与 Python Mesh.xcc 完全相同的 x 坐标 +function generate_xcc() + xmin, xmax = 0.0, 2.0 + ncells = 40 + dx = (xmax - xmin) / ncells + xcc = Vector{Float64}(undef, ncells) + for i in 1:ncells + xcc[i] = xmin + (i - 0.5) * dx # i-1 + 0.5 → i-0.5 + end + return xcc +end + +# 主测试 +xcc = generate_xcc() + +test_cases = [ + ("step", StepFunctionIC(MockConfig("step", 2.0, 0.5, 0.1))), + ("sin", SineWaveIC(MockConfig("sin", 2.0, 0.5, 0.1))), + ("gaussian", GaussianPulseIC(MockConfig("gaussian", 2.0, 0.5, 0.1))) +] + +for (name, ic) in test_cases + u_jl = evaluate_at(ic, xcc) + u_py = npzread("../../python/u_$(name)_interior_py.npy") + + err = maximum(abs.(u_jl .- u_py)) + println("IC: $(name) | 最大误差: $(err)") + @assert err < 1e-12 "❌ $(name) 不一致!" +end + +println("✅ 所有初始条件测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01a/python/boundary.py b/example/1d-linear-convection/weno3/julia/01a/python/boundary.py new file mode 100644 index 000000000..6054f92de --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01a/python/boundary.py @@ -0,0 +1,103 @@ +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from factories.base_factory import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01a/python/cfd_registry.py b/example/1d-linear-convection/weno3/julia/01a/python/cfd_registry.py new file mode 100644 index 000000000..c12eb106c --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01a/python/cfd_registry.py @@ -0,0 +1,62 @@ +# cfd_registry.py +""" +CFD注册系统统一导入模块 +所有其他模块从这里导入注册系统 +""" + +import sys +import os + +# 添加当前目录到路径,确保能找到registry.py +current_dir = os.path.dirname(os.path.abspath(__file__)) +if current_dir not in sys.path: + sys.path.insert(0, current_dir) + +try: + # 尝试导入注册系统 + from registry import ComponentRegistry, register_component + REGISTRY_AVAILABLE = True +except ImportError: + # 如果找不到,提供简单的空实现 + print("⚠️ 警告: 未找到 registry.py,使用最小化回退实现") + + class ComponentRegistry: + """最小化的注册系统回退实现""" + _registries = {} + + @classmethod + def register(cls, category, name, component_class): + if category not in cls._registries: + cls._registries[category] = {} + cls._registries[category][name] = component_class + + @classmethod + def get(cls, category, name): + if category not in cls._registries: + raise ValueError(f"未知类别: {category}") + if name not in cls._registries[category]: + raise ValueError(f"未找到: {category}.{name}") + return cls._registries[category][name] + + @classmethod + def create(cls, category, name, *args, **kwargs): + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) + for cat, comps in cls._registries.items()} + + def register_component(category, name=None): + """简化的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + REGISTRY_AVAILABLE = False + +# 导出统一的接口 +__all__ = ['ComponentRegistry', 'register_component', 'REGISTRY_AVAILABLE'] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01a/python/config.py b/example/1d-linear-convection/weno3/julia/01a/python/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01a/python/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01a/python/domain.py b/example/1d-linear-convection/weno3/julia/01a/python/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01a/python/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01a/python/factories/base_factory.py b/example/1d-linear-convection/weno3/julia/01a/python/factories/base_factory.py new file mode 100644 index 000000000..7b8b6105e --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01a/python/factories/base_factory.py @@ -0,0 +1,49 @@ +# factories/base_factory.py +""" +工厂基类 - 提供统一的组件创建接口 +""" + +from cfd_registry import ComponentRegistry +from typing import Any, Optional + +class BaseFactory: + """工厂基类,提供统一的组件创建方法""" + + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + """ + 统一创建组件的方法 + + Args: + category: 组件类别(如'boundary', 'flux'等) + name: 组件名称(如'periodic', 'rusanov'等) + *args, **kwargs: 传递给构造函数的参数 + + Returns: + 创建的组件实例 + + Raises: + ValueError: 如果组件不存在 + """ + name_lower = name.lower() + + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + # 获取可用组件列表 + available = ComponentRegistry.list_all().get(category, []) + + # 构建友好的错误信息 + if available: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"可用类型:{available}") + else: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"({category}类别下没有注册任何组件)") + + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + """获取指定类别的可用组件列表""" + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01a/python/flux.py b/example/1d-linear-convection/weno3/julia/01a/python/flux.py new file mode 100644 index 000000000..beb9ed488 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01a/python/flux.py @@ -0,0 +1,73 @@ +""" +通量计算器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册,替代硬编码工厂 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象通量计算基类(统一接口) ---------------------- +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass + +# ---------------------- 2. 具体通量计算子类(使用装饰器注册) ---------------------- + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + +class FluxCalculatorFactory: + """通量计算器工厂""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD对象 + + Returns: + 通量计算器实例 + """ + from factories.base_factory import BaseFactory + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01a/python/gen_boundary_test_data.py b/example/1d-linear-convection/weno3/julia/01a/python/gen_boundary_test_data.py new file mode 100644 index 000000000..c7fc2a9c5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01a/python/gen_boundary_test_data.py @@ -0,0 +1,35 @@ +# python/gen_boundary_test_data.py +import numpy as np +from config import CfdConfig +from mesh import Mesh +from domain import Domain + +# 固定测试配置 +config = CfdConfig() +config.with_boundary("dirichlet", left_value=0.5, right_value=1.5) +config.debug = True + +mesh = Mesh() +domain = Domain(config, mesh) + +# 构造 mock CFD 对象(仅含 config + domain) +class MockCfd: + def __init__(self, config, domain): + self.config = config + self.domain = domain + +# 测试用 u:0,1,2,...,N-1 +u_input = np.arange(domain.ntcells, dtype=np.float64) +np.save("u_input.npy", u_input) + +# 测试每种边界 +from boundary import PeriodicBoundary, DirichletBoundary, NeumannBoundary + +for bc_name, bc_class in [("periodic", PeriodicBoundary), ("dirichlet", DirichletBoundary), ("neumann", NeumannBoundary)]: + u = u_input.copy() + cfd_mock = MockCfd(config, domain) + bc = bc_class(cfd_mock) + bc.apply(u) + np.save(f"u_{bc_name}_py.npy", u) + +print("✅ 测试数据已生成:u_*.npy") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01a/python/gen_ic_test_data.py b/example/1d-linear-convection/weno3/julia/01a/python/gen_ic_test_data.py new file mode 100644 index 000000000..69c2573b8 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01a/python/gen_ic_test_data.py @@ -0,0 +1,27 @@ +# python/gen_ic_test_data.py +import sys, os +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +import numpy as np +from config import CfdConfig +from mesh import Mesh +from domain import Domain +from solution import Solution + +# 固定 mesh +mesh = Mesh() +config = CfdConfig() + +# 测试三种 IC +for ic_type in ["step", "sin", "gaussian"]: + config.ic_type = ic_type + domain = Domain(config, mesh) + sol = Solution(config, domain) + + u_full = sol.u.copy() # 包含 ghost + u_interior = sol.u[domain.ist:domain.ied].copy() + + np.save(f"u_{ic_type}_full_py.npy", u_full) + np.save(f"u_{ic_type}_interior_py.npy", u_interior) + +print("✅ 初始条件测试数据已生成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01a/python/initial_condition.py b/example/1d-linear-convection/weno3/julia/01a/python/initial_condition.py new file mode 100644 index 000000000..047415b74 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01a/python/initial_condition.py @@ -0,0 +1,90 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, ic_type: str, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from factories.base_factory import BaseFactory + return BaseFactory.create_component('initial_condition', ic_type, config) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01a/python/mesh.py b/example/1d-linear-convection/weno3/julia/01a/python/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01a/python/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01a/python/plotter.py b/example/1d-linear-convection/weno3/julia/01a/python/plotter.py new file mode 100644 index 000000000..9f1a414fd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01a/python/plotter.py @@ -0,0 +1,107 @@ +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01a/python/reconstructor/__init__.py b/example/1d-linear-convection/weno3/julia/01a/python/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01a/python/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01a/python/reconstructor/base.py b/example/1d-linear-convection/weno3/julia/01a/python/reconstructor/base.py new file mode 100644 index 000000000..bbd638503 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01a/python/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def reconstruct(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01a/python/reconstructor/eno.py b/example/1d-linear-convection/weno3/julia/01a/python/reconstructor/eno.py new file mode 100644 index 000000000..c2fb385dd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01a/python/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def reconstruct(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01a/python/reconstructor/factory.py b/example/1d-linear-convection/weno3/julia/01a/python/reconstructor/factory.py new file mode 100644 index 000000000..efa4a1446 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01a/python/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from cfd_registry import ComponentRegistry + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from factories.base_factory import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01a/python/reconstructor/weno3.py b/example/1d-linear-convection/weno3/julia/01a/python/reconstructor/weno3.py new file mode 100644 index 000000000..6e8c3f230 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01a/python/reconstructor/weno3.py @@ -0,0 +1,88 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def reconstruct(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + """ + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 # smoothness indicator for stencil [v2, v3] + beta1 = (v2 - v1)**2 # smoothness indicator for stencil [v1, v2] + + d0, d1 = 2/3, 1/3 # optimal linear weights (for right value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 0.5 * v2 + 0.5 * v3 # reconstruction from [v2, v3] + q1 = -0.5 * v1 + 1.5 * v2 # reconstruction from [v1, v2] + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 + beta1 = (v2 - v1)**2 + + d0, d1 = 1/3, 2/3 # optimal linear weights (for left value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 1.5 * v2 - 0.5 * v3 # from [v2, v3] + q1 = 0.5 * v1 + 0.5 * v2 # from [v1, v2] + return w0 * q0 + w1 * q1 + """ diff --git a/example/1d-linear-convection/weno3/julia/01a/python/registry.py b/example/1d-linear-convection/weno3/julia/01a/python/registry.py new file mode 100644 index 000000000..61be90500 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01a/python/registry.py @@ -0,0 +1,75 @@ +# registry.py (改进版) +""" +CFD组件注册系统 - 简洁高效版 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表""" + + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True # 控制是否打印注册信息 + + @classmethod + def set_verbose(cls, verbose: bool): + """设置是否打印注册信息""" + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + # 检查是否已注册 + if name in cls._registries[category]: + if cls._registries[category][name] == component_class: + # 相同类重复注册,静默跳过 + return + elif cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取组件类""" + if category not in cls._registries: + available = list(cls._registries.keys()) + raise ValueError(f"❌ 未知类别: {category} (可用类别: {available})") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + @classmethod + def get_count(cls) -> int: + """获取注册组件总数""" + return sum(len(components) for components in cls._registries.values()) + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01a/python/residual.py b/example/1d-linear-convection/weno3/julia/01a/python/residual.py new file mode 100644 index 000000000..b4d4d7dcc --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01a/python/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux import FluxCalculatorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + self.reconstructor = self.cfd.reconstructor + + # 初始化通量计算器(工厂创建) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._reconstruct() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _reconstruct(self): + """私有方法:界面值重建""" + self.reconstructor.reconstruct(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01a/python/run_eno_weno.py b/example/1d-linear-convection/weno3/julia/01a/python/run_eno_weno.py new file mode 100644 index 000000000..fd7f38744 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01a/python/run_eno_weno.py @@ -0,0 +1,52 @@ +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + #mesh = Mesh(ncells=100, L=2.0) + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行ENO3求解(使用你的链式调用) + print("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + config_eno3.with_reconstruction("eno", 3) # 显式指定3阶(也可省略,ENO默认3阶) + # 可选:覆盖默认值(如dt) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + cfd_eno3.run() # 求解并生成result字典 + + # 可选:快速验证ENO3结果 + # plotter.plot_quick(cfd_eno3.result, title="ENO3 Quick Check") + + # 3. 配置并运行WENO3求解(注意:WENO默认5阶,这里显式指定3阶) + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) # 显式指定3阶(默认是5阶) + # 可选:覆盖默认值 + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + cfd_weno3.run() + + # 4. 可选:保存结果(供离线绘图) + # cfd_eno3.save_result("eno3_result.npz") + # cfd_weno3.save_result("weno3_result.npz") + + # 5. 绘制ENO/WENO对比图 + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" # 可选:保存图片 + ) + +if __name__ == "__main__": + # 主程序入口 + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01a/python/solution.py b/example/1d-linear-convection/weno3/julia/01a/python/solution.py new file mode 100644 index 000000000..92a46d3f4 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01a/python/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config.ic_type, config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01a/python/solver.py b/example/1d-linear-convection/weno3/julia/01a/python/solver.py new file mode 100644 index 000000000..0d0b442da --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01a/python/solver.py @@ -0,0 +1,86 @@ +import numpy as np +import matplotlib.pyplot as plt +import inflect +from abc import ABC, abstractmethod + + +from flux import InviscidFluxCalculator, RusanovFluxCalculator, EngquistOsherFluxCalculator, FluxCalculatorFactory + +from boundary import BoundaryCondition, PeriodicBoundary, DirichletBoundary, NeumannBoundary, BoundaryConditionFactory + +from time_integration import TimeIntegrator, RK1Integrator, RK2Integrator, RK3Integrator, TimeIntegratorFactory + +from mesh import Mesh + +from reconstructor import ReconstructorFactory + +from initial_condition import InitialCondition, StepFunctionIC, SineWaveIC, GaussianPulseIC, InitialConditionFactory + +from domain import Domain +from solution import Solution + +from config import CfdConfig +from residual import ResidualCalculator + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, config, mesh): + self.config = config + self.domain = Domain(config, mesh) + self.solution = Solution(config, self.domain) + self.reconstructor = ReconstructorFactory.create(config, self.domain) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + def exact_solution(self): + """通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界""" + x = self.domain.mesh.xcc + T = self.config.final_time + c = self.config.wave_speed + L = self.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = InitialConditionFactory.create(self.config.ic_type, self.config) + return ic.evaluate_at(x_shifted) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + #runge_kutta(self) + self.integrator.step(dt) + t += dt + config.dt = dt_old + + # 整理标准化结果 + u_numerical = self.solution.u[self.domain.ist:self.domain.ied].copy() + self.result = { + "x": domain.mesh.xcc, + "numerical": u_numerical, + "analytical": self.exact_solution(), + "config": { + "scheme": self.config.recon_scheme, + "order": self.config.spatial_order, + "rk_order": self.config.rk_order, + "final_time": self.config.final_time + } + } + + return u_numerical diff --git a/example/1d-linear-convection/weno3/julia/01a/python/time_integration.py b/example/1d-linear-convection/weno3/julia/01a/python/time_integration.py new file mode 100644 index 000000000..25ac0b4ca --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01a/python/time_integration.py @@ -0,0 +1,125 @@ +# time_integration.py + +""" +时间推进器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = cfd.residual_calculator + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(使用装饰器注册) ---------------------- + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class TimeIntegratorFactory: + """时间推进器工厂""" + + @staticmethod + def create(cfd) -> 'TimeIntegrator': + """ + 创建时间推进器实例 + + Args: + cfd: CFD对象 + + Returns: + 时间推进器实例 + """ + from factories.base_factory import BaseFactory + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01a/python/u_dirichlet_py.npy b/example/1d-linear-convection/weno3/julia/01a/python/u_dirichlet_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..3aa20bd2e395654a6e8c59bbbc9f5eaa955b345e GIT binary patch literal 480 zcmbWzxeftg6o%oW6O}@tQ2Y_`BNMTVeHmMHM59p2#LPq?$c$)2;vU?f%TQY8ZM07F zov8Zw0H1NzP(=`e8>IlQ+pS*SsNB7-1(=_si zx~l&#Cf|Elzsex&D8?~?Nlc-_G-fc1IV_-oMJ!<%D_BJnYgoqyHnD|m>|hsr*hdQo Wp*NHvjWVK9#x%-=MtSnjr}YA`88kxx literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01a/python/u_gaussian_full_py.npy b/example/1d-linear-convection/weno3/julia/01a/python/u_gaussian_full_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..b762f0fe295b52a9a176817021995ce5a561d778 GIT binary patch literal 480 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yCOVor3bhL411<(MU?Ou)P3Oi_+soE}ylQv)*=cg#Ue98-%FeWXxBn~o z-*&0B&Pq*ej`k{D0z3w9EA6|!ORWz4wA}u9Vfdl$E2r%H!VVg&w0>m2nCbZM)%@?^ zbQ@G0rY;|Sof!j>qBKc_8=pP9zb3SzgEt8=WEn__NZ z$*8-a{Z)*4!nvvF3 wqbFAl+ziF%o9|Zhkuvbx>B0B<*(1H@$~rUDs~6~AeE<2_q`W{KwBTd_0INu`7ytkO literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01a/python/u_gaussian_interior_py.npy b/example/1d-linear-convection/weno3/julia/01a/python/u_gaussian_interior_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..68af8954e7a17e69b6b2e6954b78ee542d4e96a0 GIT binary patch literal 448 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#y20EHL3bhL411=Mpb80#_p4wiv{^M1<)6Y(m^Y(fcvsHGc?YsS7$^W)X zt#wvvVso@t=@Q^Ecw1@T^<8Rp;HTyG#|y&`bzeDU-xqe!V5RjV`^8Mhf3N0$2dCSh z;xKjjP<`JzA?Co${oe+$2WBtK9e1T6?wYnbA&=>Lk?qOJ*(aWE{cN*=zyHlG=1iNl zN(soJS7TeOgcr6%N&Go&S^Ug2epV2>rCgn3z1$RY3rj}b z1?{h5%p2Bhd1c#FYWC{z!qS+GX48p_*$(R2wVEWXJEgqJq{jHxHq-S&9M{! literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01a/python/u_input.npy b/example/1d-linear-convection/weno3/julia/01a/python/u_input.npy new file mode 100644 index 0000000000000000000000000000000000000000..ef506fa75cacbbe63dfcc17332ddd6c33de5b888 GIT binary patch literal 480 zcmbWrI}gE70EXdX6O+MUFr0`uNFy$FzqBs35sSejjUF+GN-GwT_z(W5pP?kM2cBT{2ChY{-ZF ze<|^^w?*2qNlaq~vzSAPc`RTNOISezt60N2Hn52%wy=#I>|zi5IKUx}aEun(u}>5e R8pV`GF{4q;X%r6a*dIEnGEe{j literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01a/python/u_neumann_py.npy b/example/1d-linear-convection/weno3/julia/01a/python/u_neumann_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..fa723d1efce906983b6e6710379207d15afbc271 GIT binary patch literal 480 zcmbWrI}gE70EXdX6O+MUFr0`uC5^ZgRkymtMl1%CG}^`>Dy>*V;y?JqN?(i7w>|hs7?4gBy9N-W~I7U11fnvf( PG3BF}@lnkAKgRt9WDhZ? literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01a/python/u_periodic_py.npy b/example/1d-linear-convection/weno3/julia/01a/python/u_periodic_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..156aff85dfa4c84deaaed404e0dccc37937f8836 GIT binary patch literal 480 zcmbWzyAA+Jw5Jw7YF-Tj}jFJcs^aeGCKh-lzUPGOs<_`6u&g%W>?2t#0c7 zOUUOY-iJGid|Tp}#1y76g9@{l!#rA8L>o(3#tK%kh7Q)TflX{-8#~xV7kk*p0eWBW QC_@@$M5Bypl;8h*0k_X5-%Mo@PtK#WJA?o%og6P}-0bsJ$1U?l6M7O94XfzYY=Kp9oR6e-T9A{tpmy_6tDF-R}f( z-+mW}`}RZKw;$@h{Tm?e+z)l%epjeEd#FAks5zgY=FW!NlLxi;2-F?cP8 literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01b/julia/boundary.jl b/example/1d-linear-convection/weno3/julia/01b/julia/boundary.jl new file mode 100644 index 000000000..5b0baaf43 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01b/julia/boundary.jl @@ -0,0 +1,90 @@ +# julia/boundary.jl +# 目标:与 Python boundary.py 行为完全一致(1:1 同构) +# 前提:ist/ied 在 Julia 中按 1-based 设置(ist = nghosts + 1) + +using NPZ # 仅用于测试,实际模块可不依赖 + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象基类(Julia 风格:用函数接口) ---------------------- +# Julia 无 abstract class,用文档+约定 +# 所有子类型必须实现 apply!(bc, u) + +# ---------------------- PeriodicBoundary ---------------------- +mutable struct PeriodicBoundary + cfd::Any +end + +function apply!(self::PeriodicBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist # 1-based, e.g., 3 + ied = self.cfd.domain.ied # 1-based, e.g., 43 + + # 左 ghost: u[ist - 1 - ig] = u[ied - 1 - ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ied - 1 - ig] + end + # 右 ghost: u[ied + ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ied + ig] = u[ist + ig] + end +end + +# ---------------------- DirichletBoundary ---------------------- +mutable struct DirichletBoundary + cfd::Any +end + +function apply!(self::DirichletBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + left_value = getfield_safe(self.cfd.config, :left_boundary_value, 1.0) + right_value = getfield_safe(self.cfd.config, :right_boundary_value, 2.0) + + # 左边界 + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = left_value + end + # 右边界 + for ig in 0:(nghosts-1) + u[ied + ig] = right_value + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Dirichlet边界: 左值=$left_value, 右值=$right_value") + end +end + +# ---------------------- NeumannBoundary ---------------------- +mutable struct NeumannBoundary + cfd::Any +end + +function apply!(self::NeumannBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + # 左边界零梯度:u[ist - 1 - ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ist + ig] + end + # 右边界零梯度:u[ied + ig] = u[ied - 1 - ig] ← 注意是 ied - 1 - ig + for ig in 0:(nghosts-1) + u[ied + ig] = u[ied - 1 - ig] + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Neumann边界: 零梯度") + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01b/julia/initial_condition.jl b/example/1d-linear-convection/weno3/julia/01b/julia/initial_condition.jl new file mode 100644 index 000000000..5e029e8d2 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01b/julia/initial_condition.jl @@ -0,0 +1,86 @@ +# julia/initial_condition.jl +""" +初始条件模块(Julia 版) +目标:与 Python initial_condition.py 行为完全一致 +""" + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象接口(Julia 无继承,用函数约定) ---------------------- +# 所有初始条件必须实现: +# evaluate_at(ic, x::AbstractVector{Float64}) -> Vector{Float64} +# apply(ic, solution) + +# ---------------------- StepFunctionIC ---------------------- +struct StepFunctionIC + config::Any +end + +function evaluate_at(ic::StepFunctionIC, x::AbstractVector{Float64}) + u0 = ones(Float64, length(x)) + for i in eachindex(x) + if 0.5 <= x[i] <= 1.0 + u0[i] = 2.0 + end + end + return u0 +end + +function apply(ic::StepFunctionIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- SineWaveIC ---------------------- +struct SineWaveIC + config::Any +end + +function evaluate_at(ic::SineWaveIC, x::AbstractVector{Float64}) + L = getfield_safe(ic.config, :domain_length, 2.0) + return sin.(2π * x / L) +end + +function apply(ic::SineWaveIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- GaussianPulseIC ---------------------- +struct GaussianPulseIC + config::Any +end + +function evaluate_at(ic::GaussianPulseIC, x::AbstractVector{Float64}) + center = getfield_safe(ic.config, :pulse_center, 0.5) + width = getfield_safe(ic.config, :pulse_width, 0.1) + return exp.(-((x .- center) ./ width).^2) +end + +function apply(ic::GaussianPulseIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- 公共辅助函数 ---------------------- +""" +将初始场 values 应用到 solution.u 的物理区域(含 ghost 的数组) +""" +function _apply_to_interior(solution, values) + domain = solution.domain + # Python: for i in range(domain.ist, domain.ied) + # Julia: for i in domain.ist:domain.ied-1 (因为 ied 是结束索引,不包含) + for i in domain.ist:(domain.ied - 1) + j = i - domain.ist + 1 # values 是 1-based,i - ist 是 0-based → +1 + solution.u[i] = values[j] + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01b/julia/mesh.jl b/example/1d-linear-convection/weno3/julia/01b/julia/mesh.jl new file mode 100644 index 000000000..1b0dd4bf9 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01b/julia/mesh.jl @@ -0,0 +1,45 @@ +# julia/mesh.jl +""" +Mesh 结构体(与提供的 mesh.py 完全同构) +- 字段名、初始化顺序、循环逻辑完全一致 +- 不做任何泛化或优化 +""" + +mutable struct Mesh + xmin::Float64 + xmax::Float64 + ncells::Int + nnodes::Int + nx::Int + x::Vector{Float64} + xcc::Vector{Float64} + L::Float64 # 在 init_mesh 中赋值 + dx::Float64 # 在 init_mesh 中赋值 + + function Mesh() + # 与 Python __init__ 完全一致 + xmin = 0.0 + xmax = 2.0 + ncells = 40 + nnodes = ncells + 1 + nx = ncells + x = zeros(Float64, nnodes) + xcc = zeros(Float64, ncells) + + # 调用 init_mesh(模拟 Python) + L = xmax - xmin + dx = L / ncells + + # Generate node coordinates: for i in range(self.nnodes) + for i in 1:nnodes + x[i] = xmin + (i - 1) * dx # Python i → Julia i-1 + end + + # Generate cell center coordinates + for i in 1:ncells + xcc[i] = 0.5 * (x[i] + x[i+1]) + end + + new(xmin, xmax, ncells, nnodes, nx, x, xcc, L, dx) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01b/julia/test/test_boundary.jl b/example/1d-linear-convection/weno3/julia/01b/julia/test/test_boundary.jl new file mode 100644 index 000000000..ef27d6820 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01b/julia/test/test_boundary.jl @@ -0,0 +1,57 @@ +# julia/test/test_boundary.jl +using NPZ +include("../boundary.jl") + +struct MockConfig + boundary_type::String + left_boundary_value::Float64 + right_boundary_value::Float64 + debug::Bool +end + +struct MockDomain + nghosts::Int + ist::Int # Julia 本地索引(1-based) + ied::Int + ntcells::Int +end + +struct MockCfd + config::MockConfig + domain::MockDomain +end + +# ===== 关键:使用 Julia 本地索引规则 ===== +nghosts = 2 +ncells = 40 +ist = nghosts + 1 # = 3 +ied = ist + ncells # = 43 +ntcells = ncells + 2 * nghosts # = 44 + +config = MockConfig("dirichlet", 0.5, 1.5, true) +domain = MockDomain(nghosts, ist, ied, ntcells) +cfd_mock = MockCfd(config, domain) + +# 加载 Python 生成的 u_input.npy +# 注意:Python u[0] → Julia u[1],所以内容完全对应 +u_input = npzread("../../python/u_input.npy") +@assert length(u_input) == ntcells "数组长度不匹配!" + +# 测试三种边界 +test_cases = [ + ("periodic", PeriodicBoundary(cfd_mock)), + ("dirichlet", DirichletBoundary(cfd_mock)), + ("neumann", NeumannBoundary(cfd_mock)) +] + +for (name, bc) in test_cases + u = copy(u_input) + apply!(bc, u) + + u_py = npzread("../../python/u_$(name)_py.npy") + err = maximum(abs.(u .- u_py)) + println("边界: $(name) | 最大误差: $(err)") + @assert err < 1e-12 "❌ $(name) 不一致!" +end + +println("✅ 所有边界条件测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01b/julia/test/test_initial_condition.jl b/example/1d-linear-convection/weno3/julia/01b/julia/test/test_initial_condition.jl new file mode 100644 index 000000000..dc58691a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01b/julia/test/test_initial_condition.jl @@ -0,0 +1,45 @@ +# julia/test/test_initial_condition.jl +using NPZ + +# 包含初始条件模块 +include("../initial_condition.jl") + +# 构造 mock config(与 Python 完全一致) +struct MockConfig + ic_type::String + domain_length::Float64 + pulse_center::Float64 + pulse_width::Float64 +end + +# 生成与 Python Mesh.xcc 完全相同的 x 坐标 +function generate_xcc() + xmin, xmax = 0.0, 2.0 + ncells = 40 + dx = (xmax - xmin) / ncells + xcc = Vector{Float64}(undef, ncells) + for i in 1:ncells + xcc[i] = xmin + (i - 0.5) * dx # i-1 + 0.5 → i-0.5 + end + return xcc +end + +# 主测试 +xcc = generate_xcc() + +test_cases = [ + ("step", StepFunctionIC(MockConfig("step", 2.0, 0.5, 0.1))), + ("sin", SineWaveIC(MockConfig("sin", 2.0, 0.5, 0.1))), + ("gaussian", GaussianPulseIC(MockConfig("gaussian", 2.0, 0.5, 0.1))) +] + +for (name, ic) in test_cases + u_jl = evaluate_at(ic, xcc) + u_py = npzread("../../python/u_$(name)_interior_py.npy") + + err = maximum(abs.(u_jl .- u_py)) + println("IC: $(name) | 最大误差: $(err)") + @assert err < 1e-12 "❌ $(name) 不一致!" +end + +println("✅ 所有初始条件测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01b/julia/test/test_mesh.jl b/example/1d-linear-convection/weno3/julia/01b/julia/test/test_mesh.jl new file mode 100644 index 000000000..315d2aac2 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01b/julia/test/test_mesh.jl @@ -0,0 +1,37 @@ +# julia/test/test_mesh.jl +include("../mesh.jl") + +# 创建 mesh(与 Python 完全相同) +mesh = Mesh() + +# 打印关键值(与 Python 对比) +println("xmin = ", mesh.xmin) # 0.0 +println("xmax = ", mesh.xmax) # 2.0 +println("ncells = ", mesh.ncells) # 40 +println("nnodes = ", mesh.nnodes) # 41 +println("nx = ", mesh.nx) # 40 +println("L = ", mesh.L) # 2.0 +println("dx = ", mesh.dx) # 0.05 + +# 检查 x[1] (Python x[0]) 和 x[41] (Python x[40]) +println("x[1] = ", mesh.x[1]) # 0.0 +println("x[41] = ", mesh.x[41]) # 2.0 + +# 检查 xcc[1] (Python xcc[0]) 和 xcc[40] (Python xcc[39]) +println("xcc[1] = ", mesh.xcc[1]) # 0.025 +println("xcc[40] = ", mesh.xcc[40]) # 1.975 + +# ✅ 严格断言 +@assert mesh.xmin == 0.0 +@assert mesh.xmax == 2.0 +@assert mesh.ncells == 40 +@assert mesh.nnodes == 41 +@assert mesh.nx == 40 +@assert mesh.L == 2.0 +@assert mesh.dx == 0.05 +@assert mesh.x[1] == 0.0 +@assert mesh.x[41] == 2.0 +@assert abs(mesh.xcc[1] - 0.025) < 1e-12 +@assert abs(mesh.xcc[40] - 1.975) < 1e-12 + +println("✅ Mesh 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01b/python/boundary.py b/example/1d-linear-convection/weno3/julia/01b/python/boundary.py new file mode 100644 index 000000000..6054f92de --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01b/python/boundary.py @@ -0,0 +1,103 @@ +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from factories.base_factory import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01b/python/cfd_registry.py b/example/1d-linear-convection/weno3/julia/01b/python/cfd_registry.py new file mode 100644 index 000000000..c12eb106c --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01b/python/cfd_registry.py @@ -0,0 +1,62 @@ +# cfd_registry.py +""" +CFD注册系统统一导入模块 +所有其他模块从这里导入注册系统 +""" + +import sys +import os + +# 添加当前目录到路径,确保能找到registry.py +current_dir = os.path.dirname(os.path.abspath(__file__)) +if current_dir not in sys.path: + sys.path.insert(0, current_dir) + +try: + # 尝试导入注册系统 + from registry import ComponentRegistry, register_component + REGISTRY_AVAILABLE = True +except ImportError: + # 如果找不到,提供简单的空实现 + print("⚠️ 警告: 未找到 registry.py,使用最小化回退实现") + + class ComponentRegistry: + """最小化的注册系统回退实现""" + _registries = {} + + @classmethod + def register(cls, category, name, component_class): + if category not in cls._registries: + cls._registries[category] = {} + cls._registries[category][name] = component_class + + @classmethod + def get(cls, category, name): + if category not in cls._registries: + raise ValueError(f"未知类别: {category}") + if name not in cls._registries[category]: + raise ValueError(f"未找到: {category}.{name}") + return cls._registries[category][name] + + @classmethod + def create(cls, category, name, *args, **kwargs): + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) + for cat, comps in cls._registries.items()} + + def register_component(category, name=None): + """简化的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + REGISTRY_AVAILABLE = False + +# 导出统一的接口 +__all__ = ['ComponentRegistry', 'register_component', 'REGISTRY_AVAILABLE'] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01b/python/config.py b/example/1d-linear-convection/weno3/julia/01b/python/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01b/python/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01b/python/domain.py b/example/1d-linear-convection/weno3/julia/01b/python/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01b/python/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01b/python/factories/base_factory.py b/example/1d-linear-convection/weno3/julia/01b/python/factories/base_factory.py new file mode 100644 index 000000000..7b8b6105e --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01b/python/factories/base_factory.py @@ -0,0 +1,49 @@ +# factories/base_factory.py +""" +工厂基类 - 提供统一的组件创建接口 +""" + +from cfd_registry import ComponentRegistry +from typing import Any, Optional + +class BaseFactory: + """工厂基类,提供统一的组件创建方法""" + + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + """ + 统一创建组件的方法 + + Args: + category: 组件类别(如'boundary', 'flux'等) + name: 组件名称(如'periodic', 'rusanov'等) + *args, **kwargs: 传递给构造函数的参数 + + Returns: + 创建的组件实例 + + Raises: + ValueError: 如果组件不存在 + """ + name_lower = name.lower() + + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + # 获取可用组件列表 + available = ComponentRegistry.list_all().get(category, []) + + # 构建友好的错误信息 + if available: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"可用类型:{available}") + else: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"({category}类别下没有注册任何组件)") + + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + """获取指定类别的可用组件列表""" + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01b/python/flux.py b/example/1d-linear-convection/weno3/julia/01b/python/flux.py new file mode 100644 index 000000000..beb9ed488 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01b/python/flux.py @@ -0,0 +1,73 @@ +""" +通量计算器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册,替代硬编码工厂 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象通量计算基类(统一接口) ---------------------- +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass + +# ---------------------- 2. 具体通量计算子类(使用装饰器注册) ---------------------- + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + +class FluxCalculatorFactory: + """通量计算器工厂""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD对象 + + Returns: + 通量计算器实例 + """ + from factories.base_factory import BaseFactory + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01b/python/gen_boundary_test_data.py b/example/1d-linear-convection/weno3/julia/01b/python/gen_boundary_test_data.py new file mode 100644 index 000000000..c7fc2a9c5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01b/python/gen_boundary_test_data.py @@ -0,0 +1,35 @@ +# python/gen_boundary_test_data.py +import numpy as np +from config import CfdConfig +from mesh import Mesh +from domain import Domain + +# 固定测试配置 +config = CfdConfig() +config.with_boundary("dirichlet", left_value=0.5, right_value=1.5) +config.debug = True + +mesh = Mesh() +domain = Domain(config, mesh) + +# 构造 mock CFD 对象(仅含 config + domain) +class MockCfd: + def __init__(self, config, domain): + self.config = config + self.domain = domain + +# 测试用 u:0,1,2,...,N-1 +u_input = np.arange(domain.ntcells, dtype=np.float64) +np.save("u_input.npy", u_input) + +# 测试每种边界 +from boundary import PeriodicBoundary, DirichletBoundary, NeumannBoundary + +for bc_name, bc_class in [("periodic", PeriodicBoundary), ("dirichlet", DirichletBoundary), ("neumann", NeumannBoundary)]: + u = u_input.copy() + cfd_mock = MockCfd(config, domain) + bc = bc_class(cfd_mock) + bc.apply(u) + np.save(f"u_{bc_name}_py.npy", u) + +print("✅ 测试数据已生成:u_*.npy") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01b/python/gen_ic_test_data.py b/example/1d-linear-convection/weno3/julia/01b/python/gen_ic_test_data.py new file mode 100644 index 000000000..69c2573b8 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01b/python/gen_ic_test_data.py @@ -0,0 +1,27 @@ +# python/gen_ic_test_data.py +import sys, os +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +import numpy as np +from config import CfdConfig +from mesh import Mesh +from domain import Domain +from solution import Solution + +# 固定 mesh +mesh = Mesh() +config = CfdConfig() + +# 测试三种 IC +for ic_type in ["step", "sin", "gaussian"]: + config.ic_type = ic_type + domain = Domain(config, mesh) + sol = Solution(config, domain) + + u_full = sol.u.copy() # 包含 ghost + u_interior = sol.u[domain.ist:domain.ied].copy() + + np.save(f"u_{ic_type}_full_py.npy", u_full) + np.save(f"u_{ic_type}_interior_py.npy", u_interior) + +print("✅ 初始条件测试数据已生成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01b/python/initial_condition.py b/example/1d-linear-convection/weno3/julia/01b/python/initial_condition.py new file mode 100644 index 000000000..047415b74 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01b/python/initial_condition.py @@ -0,0 +1,90 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, ic_type: str, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from factories.base_factory import BaseFactory + return BaseFactory.create_component('initial_condition', ic_type, config) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01b/python/mesh.py b/example/1d-linear-convection/weno3/julia/01b/python/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01b/python/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01b/python/plotter.py b/example/1d-linear-convection/weno3/julia/01b/python/plotter.py new file mode 100644 index 000000000..9f1a414fd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01b/python/plotter.py @@ -0,0 +1,107 @@ +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01b/python/reconstructor/__init__.py b/example/1d-linear-convection/weno3/julia/01b/python/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01b/python/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01b/python/reconstructor/base.py b/example/1d-linear-convection/weno3/julia/01b/python/reconstructor/base.py new file mode 100644 index 000000000..bbd638503 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01b/python/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def reconstruct(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01b/python/reconstructor/eno.py b/example/1d-linear-convection/weno3/julia/01b/python/reconstructor/eno.py new file mode 100644 index 000000000..c2fb385dd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01b/python/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def reconstruct(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01b/python/reconstructor/factory.py b/example/1d-linear-convection/weno3/julia/01b/python/reconstructor/factory.py new file mode 100644 index 000000000..efa4a1446 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01b/python/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from cfd_registry import ComponentRegistry + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from factories.base_factory import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01b/python/reconstructor/weno3.py b/example/1d-linear-convection/weno3/julia/01b/python/reconstructor/weno3.py new file mode 100644 index 000000000..6e8c3f230 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01b/python/reconstructor/weno3.py @@ -0,0 +1,88 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def reconstruct(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + """ + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 # smoothness indicator for stencil [v2, v3] + beta1 = (v2 - v1)**2 # smoothness indicator for stencil [v1, v2] + + d0, d1 = 2/3, 1/3 # optimal linear weights (for right value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 0.5 * v2 + 0.5 * v3 # reconstruction from [v2, v3] + q1 = -0.5 * v1 + 1.5 * v2 # reconstruction from [v1, v2] + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 + beta1 = (v2 - v1)**2 + + d0, d1 = 1/3, 2/3 # optimal linear weights (for left value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 1.5 * v2 - 0.5 * v3 # from [v2, v3] + q1 = 0.5 * v1 + 0.5 * v2 # from [v1, v2] + return w0 * q0 + w1 * q1 + """ diff --git a/example/1d-linear-convection/weno3/julia/01b/python/registry.py b/example/1d-linear-convection/weno3/julia/01b/python/registry.py new file mode 100644 index 000000000..61be90500 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01b/python/registry.py @@ -0,0 +1,75 @@ +# registry.py (改进版) +""" +CFD组件注册系统 - 简洁高效版 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表""" + + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True # 控制是否打印注册信息 + + @classmethod + def set_verbose(cls, verbose: bool): + """设置是否打印注册信息""" + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + # 检查是否已注册 + if name in cls._registries[category]: + if cls._registries[category][name] == component_class: + # 相同类重复注册,静默跳过 + return + elif cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取组件类""" + if category not in cls._registries: + available = list(cls._registries.keys()) + raise ValueError(f"❌ 未知类别: {category} (可用类别: {available})") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + @classmethod + def get_count(cls) -> int: + """获取注册组件总数""" + return sum(len(components) for components in cls._registries.values()) + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01b/python/residual.py b/example/1d-linear-convection/weno3/julia/01b/python/residual.py new file mode 100644 index 000000000..b4d4d7dcc --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01b/python/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux import FluxCalculatorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + self.reconstructor = self.cfd.reconstructor + + # 初始化通量计算器(工厂创建) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._reconstruct() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _reconstruct(self): + """私有方法:界面值重建""" + self.reconstructor.reconstruct(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01b/python/run_eno_weno.py b/example/1d-linear-convection/weno3/julia/01b/python/run_eno_weno.py new file mode 100644 index 000000000..fd7f38744 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01b/python/run_eno_weno.py @@ -0,0 +1,52 @@ +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + #mesh = Mesh(ncells=100, L=2.0) + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行ENO3求解(使用你的链式调用) + print("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + config_eno3.with_reconstruction("eno", 3) # 显式指定3阶(也可省略,ENO默认3阶) + # 可选:覆盖默认值(如dt) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + cfd_eno3.run() # 求解并生成result字典 + + # 可选:快速验证ENO3结果 + # plotter.plot_quick(cfd_eno3.result, title="ENO3 Quick Check") + + # 3. 配置并运行WENO3求解(注意:WENO默认5阶,这里显式指定3阶) + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) # 显式指定3阶(默认是5阶) + # 可选:覆盖默认值 + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + cfd_weno3.run() + + # 4. 可选:保存结果(供离线绘图) + # cfd_eno3.save_result("eno3_result.npz") + # cfd_weno3.save_result("weno3_result.npz") + + # 5. 绘制ENO/WENO对比图 + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" # 可选:保存图片 + ) + +if __name__ == "__main__": + # 主程序入口 + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01b/python/solution.py b/example/1d-linear-convection/weno3/julia/01b/python/solution.py new file mode 100644 index 000000000..92a46d3f4 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01b/python/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config.ic_type, config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01b/python/solver.py b/example/1d-linear-convection/weno3/julia/01b/python/solver.py new file mode 100644 index 000000000..0d0b442da --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01b/python/solver.py @@ -0,0 +1,86 @@ +import numpy as np +import matplotlib.pyplot as plt +import inflect +from abc import ABC, abstractmethod + + +from flux import InviscidFluxCalculator, RusanovFluxCalculator, EngquistOsherFluxCalculator, FluxCalculatorFactory + +from boundary import BoundaryCondition, PeriodicBoundary, DirichletBoundary, NeumannBoundary, BoundaryConditionFactory + +from time_integration import TimeIntegrator, RK1Integrator, RK2Integrator, RK3Integrator, TimeIntegratorFactory + +from mesh import Mesh + +from reconstructor import ReconstructorFactory + +from initial_condition import InitialCondition, StepFunctionIC, SineWaveIC, GaussianPulseIC, InitialConditionFactory + +from domain import Domain +from solution import Solution + +from config import CfdConfig +from residual import ResidualCalculator + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, config, mesh): + self.config = config + self.domain = Domain(config, mesh) + self.solution = Solution(config, self.domain) + self.reconstructor = ReconstructorFactory.create(config, self.domain) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + def exact_solution(self): + """通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界""" + x = self.domain.mesh.xcc + T = self.config.final_time + c = self.config.wave_speed + L = self.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = InitialConditionFactory.create(self.config.ic_type, self.config) + return ic.evaluate_at(x_shifted) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + #runge_kutta(self) + self.integrator.step(dt) + t += dt + config.dt = dt_old + + # 整理标准化结果 + u_numerical = self.solution.u[self.domain.ist:self.domain.ied].copy() + self.result = { + "x": domain.mesh.xcc, + "numerical": u_numerical, + "analytical": self.exact_solution(), + "config": { + "scheme": self.config.recon_scheme, + "order": self.config.spatial_order, + "rk_order": self.config.rk_order, + "final_time": self.config.final_time + } + } + + return u_numerical diff --git a/example/1d-linear-convection/weno3/julia/01b/python/time_integration.py b/example/1d-linear-convection/weno3/julia/01b/python/time_integration.py new file mode 100644 index 000000000..25ac0b4ca --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01b/python/time_integration.py @@ -0,0 +1,125 @@ +# time_integration.py + +""" +时间推进器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = cfd.residual_calculator + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(使用装饰器注册) ---------------------- + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class TimeIntegratorFactory: + """时间推进器工厂""" + + @staticmethod + def create(cfd) -> 'TimeIntegrator': + """ + 创建时间推进器实例 + + Args: + cfd: CFD对象 + + Returns: + 时间推进器实例 + """ + from factories.base_factory import BaseFactory + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01b/python/u_dirichlet_py.npy b/example/1d-linear-convection/weno3/julia/01b/python/u_dirichlet_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..3aa20bd2e395654a6e8c59bbbc9f5eaa955b345e GIT binary patch literal 480 zcmbWzxeftg6o%oW6O}@tQ2Y_`BNMTVeHmMHM59p2#LPq?$c$)2;vU?f%TQY8ZM07F zov8Zw0H1NzP(=`e8>IlQ+pS*SsNB7-1(=_si zx~l&#Cf|Elzsex&D8?~?Nlc-_G-fc1IV_-oMJ!<%D_BJnYgoqyHnD|m>|hsr*hdQo Wp*NHvjWVK9#x%-=MtSnjr}YA`88kxx literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01b/python/u_gaussian_full_py.npy b/example/1d-linear-convection/weno3/julia/01b/python/u_gaussian_full_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..b762f0fe295b52a9a176817021995ce5a561d778 GIT binary patch literal 480 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yCOVor3bhL411<(MU?Ou)P3Oi_+soE}ylQv)*=cg#Ue98-%FeWXxBn~o z-*&0B&Pq*ej`k{D0z3w9EA6|!ORWz4wA}u9Vfdl$E2r%H!VVg&w0>m2nCbZM)%@?^ zbQ@G0rY;|Sof!j>qBKc_8=pP9zb3SzgEt8=WEn__NZ z$*8-a{Z)*4!nvvF3 wqbFAl+ziF%o9|Zhkuvbx>B0B<*(1H@$~rUDs~6~AeE<2_q`W{KwBTd_0INu`7ytkO literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01b/python/u_gaussian_interior_py.npy b/example/1d-linear-convection/weno3/julia/01b/python/u_gaussian_interior_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..68af8954e7a17e69b6b2e6954b78ee542d4e96a0 GIT binary patch literal 448 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#y20EHL3bhL411=Mpb80#_p4wiv{^M1<)6Y(m^Y(fcvsHGc?YsS7$^W)X zt#wvvVso@t=@Q^Ecw1@T^<8Rp;HTyG#|y&`bzeDU-xqe!V5RjV`^8Mhf3N0$2dCSh z;xKjjP<`JzA?Co${oe+$2WBtK9e1T6?wYnbA&=>Lk?qOJ*(aWE{cN*=zyHlG=1iNl zN(soJS7TeOgcr6%N&Go&S^Ug2epV2>rCgn3z1$RY3rj}b z1?{h5%p2Bhd1c#FYWC{z!qS+GX48p_*$(R2wVEWXJEgqJq{jHxHq-S&9M{! literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01b/python/u_input.npy b/example/1d-linear-convection/weno3/julia/01b/python/u_input.npy new file mode 100644 index 0000000000000000000000000000000000000000..ef506fa75cacbbe63dfcc17332ddd6c33de5b888 GIT binary patch literal 480 zcmbWrI}gE70EXdX6O+MUFr0`uNFy$FzqBs35sSejjUF+GN-GwT_z(W5pP?kM2cBT{2ChY{-ZF ze<|^^w?*2qNlaq~vzSAPc`RTNOISezt60N2Hn52%wy=#I>|zi5IKUx}aEun(u}>5e R8pV`GF{4q;X%r6a*dIEnGEe{j literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01b/python/u_neumann_py.npy b/example/1d-linear-convection/weno3/julia/01b/python/u_neumann_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..fa723d1efce906983b6e6710379207d15afbc271 GIT binary patch literal 480 zcmbWrI}gE70EXdX6O+MUFr0`uC5^ZgRkymtMl1%CG}^`>Dy>*V;y?JqN?(i7w>|hs7?4gBy9N-W~I7U11fnvf( PG3BF}@lnkAKgRt9WDhZ? literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01b/python/u_periodic_py.npy b/example/1d-linear-convection/weno3/julia/01b/python/u_periodic_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..156aff85dfa4c84deaaed404e0dccc37937f8836 GIT binary patch literal 480 zcmbWzyAA+Jw5Jw7YF-Tj}jFJcs^aeGCKh-lzUPGOs<_`6u&g%W>?2t#0c7 zOUUOY-iJGid|Tp}#1y76g9@{l!#rA8L>o(3#tK%kh7Q)TflX{-8#~xV7kk*p0eWBW QC_@@$M5Bypl;8h*0k_X5-%Mo@PtK#WJA?o%og6P}-0bsJ$1U?l6M7O94XfzYY=Kp9oR6e-T9A{tpmy_6tDF-R}f( z-+mW}`}RZKw;$@h{Tm?e+z)l%epjeEd#FAks5zgY=FW!NlLxi;2-F?cP8 literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01c/julia/boundary.jl b/example/1d-linear-convection/weno3/julia/01c/julia/boundary.jl new file mode 100644 index 000000000..5b0baaf43 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/julia/boundary.jl @@ -0,0 +1,90 @@ +# julia/boundary.jl +# 目标:与 Python boundary.py 行为完全一致(1:1 同构) +# 前提:ist/ied 在 Julia 中按 1-based 设置(ist = nghosts + 1) + +using NPZ # 仅用于测试,实际模块可不依赖 + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象基类(Julia 风格:用函数接口) ---------------------- +# Julia 无 abstract class,用文档+约定 +# 所有子类型必须实现 apply!(bc, u) + +# ---------------------- PeriodicBoundary ---------------------- +mutable struct PeriodicBoundary + cfd::Any +end + +function apply!(self::PeriodicBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist # 1-based, e.g., 3 + ied = self.cfd.domain.ied # 1-based, e.g., 43 + + # 左 ghost: u[ist - 1 - ig] = u[ied - 1 - ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ied - 1 - ig] + end + # 右 ghost: u[ied + ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ied + ig] = u[ist + ig] + end +end + +# ---------------------- DirichletBoundary ---------------------- +mutable struct DirichletBoundary + cfd::Any +end + +function apply!(self::DirichletBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + left_value = getfield_safe(self.cfd.config, :left_boundary_value, 1.0) + right_value = getfield_safe(self.cfd.config, :right_boundary_value, 2.0) + + # 左边界 + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = left_value + end + # 右边界 + for ig in 0:(nghosts-1) + u[ied + ig] = right_value + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Dirichlet边界: 左值=$left_value, 右值=$right_value") + end +end + +# ---------------------- NeumannBoundary ---------------------- +mutable struct NeumannBoundary + cfd::Any +end + +function apply!(self::NeumannBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + # 左边界零梯度:u[ist - 1 - ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ist + ig] + end + # 右边界零梯度:u[ied + ig] = u[ied - 1 - ig] ← 注意是 ied - 1 - ig + for ig in 0:(nghosts-1) + u[ied + ig] = u[ied - 1 - ig] + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Neumann边界: 零梯度") + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01c/julia/domain.jl b/example/1d-linear-convection/weno3/julia/01c/julia/domain.jl new file mode 100644 index 000000000..818be8fa6 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/julia/domain.jl @@ -0,0 +1,61 @@ +# julia/domain.jl +""" +Domain 结构体(与 domain.py 完全同构) +- 保存 config +- nghosts 计算逻辑 1:1 +- ist/ied 为 0-based(与 Python 一致) +""" + +include("mesh.jl") + +mutable struct Domain + config::Any # 保存 config(与 Python 一致) + mesh::Mesh + nghosts::Int + ist::Int # 0-based + ied::Int # 0-based + ntcells::Int +end + +function Domain(config::Any, mesh::Mesh) + nghosts = _calc_nghosts(config) + ist = nghosts + ied = ist + mesh.ncells + ntcells = mesh.ncells + 2 * nghosts + Domain(config, mesh, nghosts, ist, ied, ntcells) +end + +function _calc_nghosts(config::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + order = getfield_safe(config, :spatial_order, 2) + + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + if scheme == "eno" + nghosts = order + elseif startswith(scheme, "weno") + nghosts = order ÷ 2 + 1 + else + error("未知重建格式 $(scheme),无法计算ghost层!") + end + + if nghosts <= 0 + error("计算得到的ghost层数量无效:$(nghosts)(阶数$(order),格式$(scheme))") + end + + return nghosts +end + +function is_physical_cell(domain::Domain, idx::Int) + return domain.ist <= idx < domain.ied +end + +function get_physical_indices(domain::Domain) + return domain.ist:(domain.ied - 1) +end + +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01c/julia/initial_condition.jl b/example/1d-linear-convection/weno3/julia/01c/julia/initial_condition.jl new file mode 100644 index 000000000..5e029e8d2 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/julia/initial_condition.jl @@ -0,0 +1,86 @@ +# julia/initial_condition.jl +""" +初始条件模块(Julia 版) +目标:与 Python initial_condition.py 行为完全一致 +""" + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象接口(Julia 无继承,用函数约定) ---------------------- +# 所有初始条件必须实现: +# evaluate_at(ic, x::AbstractVector{Float64}) -> Vector{Float64} +# apply(ic, solution) + +# ---------------------- StepFunctionIC ---------------------- +struct StepFunctionIC + config::Any +end + +function evaluate_at(ic::StepFunctionIC, x::AbstractVector{Float64}) + u0 = ones(Float64, length(x)) + for i in eachindex(x) + if 0.5 <= x[i] <= 1.0 + u0[i] = 2.0 + end + end + return u0 +end + +function apply(ic::StepFunctionIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- SineWaveIC ---------------------- +struct SineWaveIC + config::Any +end + +function evaluate_at(ic::SineWaveIC, x::AbstractVector{Float64}) + L = getfield_safe(ic.config, :domain_length, 2.0) + return sin.(2π * x / L) +end + +function apply(ic::SineWaveIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- GaussianPulseIC ---------------------- +struct GaussianPulseIC + config::Any +end + +function evaluate_at(ic::GaussianPulseIC, x::AbstractVector{Float64}) + center = getfield_safe(ic.config, :pulse_center, 0.5) + width = getfield_safe(ic.config, :pulse_width, 0.1) + return exp.(-((x .- center) ./ width).^2) +end + +function apply(ic::GaussianPulseIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- 公共辅助函数 ---------------------- +""" +将初始场 values 应用到 solution.u 的物理区域(含 ghost 的数组) +""" +function _apply_to_interior(solution, values) + domain = solution.domain + # Python: for i in range(domain.ist, domain.ied) + # Julia: for i in domain.ist:domain.ied-1 (因为 ied 是结束索引,不包含) + for i in domain.ist:(domain.ied - 1) + j = i - domain.ist + 1 # values 是 1-based,i - ist 是 0-based → +1 + solution.u[i] = values[j] + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01c/julia/mesh.jl b/example/1d-linear-convection/weno3/julia/01c/julia/mesh.jl new file mode 100644 index 000000000..1b0dd4bf9 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/julia/mesh.jl @@ -0,0 +1,45 @@ +# julia/mesh.jl +""" +Mesh 结构体(与提供的 mesh.py 完全同构) +- 字段名、初始化顺序、循环逻辑完全一致 +- 不做任何泛化或优化 +""" + +mutable struct Mesh + xmin::Float64 + xmax::Float64 + ncells::Int + nnodes::Int + nx::Int + x::Vector{Float64} + xcc::Vector{Float64} + L::Float64 # 在 init_mesh 中赋值 + dx::Float64 # 在 init_mesh 中赋值 + + function Mesh() + # 与 Python __init__ 完全一致 + xmin = 0.0 + xmax = 2.0 + ncells = 40 + nnodes = ncells + 1 + nx = ncells + x = zeros(Float64, nnodes) + xcc = zeros(Float64, ncells) + + # 调用 init_mesh(模拟 Python) + L = xmax - xmin + dx = L / ncells + + # Generate node coordinates: for i in range(self.nnodes) + for i in 1:nnodes + x[i] = xmin + (i - 1) * dx # Python i → Julia i-1 + end + + # Generate cell center coordinates + for i in 1:ncells + xcc[i] = 0.5 * (x[i] + x[i+1]) + end + + new(xmin, xmax, ncells, nnodes, nx, x, xcc, L, dx) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01c/julia/test/test_boundary.jl b/example/1d-linear-convection/weno3/julia/01c/julia/test/test_boundary.jl new file mode 100644 index 000000000..ef27d6820 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/julia/test/test_boundary.jl @@ -0,0 +1,57 @@ +# julia/test/test_boundary.jl +using NPZ +include("../boundary.jl") + +struct MockConfig + boundary_type::String + left_boundary_value::Float64 + right_boundary_value::Float64 + debug::Bool +end + +struct MockDomain + nghosts::Int + ist::Int # Julia 本地索引(1-based) + ied::Int + ntcells::Int +end + +struct MockCfd + config::MockConfig + domain::MockDomain +end + +# ===== 关键:使用 Julia 本地索引规则 ===== +nghosts = 2 +ncells = 40 +ist = nghosts + 1 # = 3 +ied = ist + ncells # = 43 +ntcells = ncells + 2 * nghosts # = 44 + +config = MockConfig("dirichlet", 0.5, 1.5, true) +domain = MockDomain(nghosts, ist, ied, ntcells) +cfd_mock = MockCfd(config, domain) + +# 加载 Python 生成的 u_input.npy +# 注意:Python u[0] → Julia u[1],所以内容完全对应 +u_input = npzread("../../python/u_input.npy") +@assert length(u_input) == ntcells "数组长度不匹配!" + +# 测试三种边界 +test_cases = [ + ("periodic", PeriodicBoundary(cfd_mock)), + ("dirichlet", DirichletBoundary(cfd_mock)), + ("neumann", NeumannBoundary(cfd_mock)) +] + +for (name, bc) in test_cases + u = copy(u_input) + apply!(bc, u) + + u_py = npzread("../../python/u_$(name)_py.npy") + err = maximum(abs.(u .- u_py)) + println("边界: $(name) | 最大误差: $(err)") + @assert err < 1e-12 "❌ $(name) 不一致!" +end + +println("✅ 所有边界条件测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01c/julia/test/test_domain.jl b/example/1d-linear-convection/weno3/julia/01c/julia/test/test_domain.jl new file mode 100644 index 000000000..7b423f774 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/julia/test/test_domain.jl @@ -0,0 +1,44 @@ +# julia/test/test_domain.jl +include("../mesh.jl") +include("../domain.jl") + +# MockConfig:模拟 Python CfdConfig +struct MockConfig + recon_scheme::String + spatial_order::Int +end + +# 测试 ENO +config_eno = MockConfig("eno", 2) +mesh = Mesh() +domain_eno = Domain(config_eno, mesh) + +println("ENO: nghosts = ", domain_eno.nghosts) # 2 +println("ENO: ist = ", domain_eno.ist) # 2 +println("ENO: ied = ", domain_eno.ied) # 42 +println("物理索引范围: ", collect(get_physical_indices(domain_eno))[1:3], " ... ", collect(get_physical_indices(domain_eno))[end-2:end]) + +# 测试 WENO(字符串 "weno") +config_weno = MockConfig("weno", 2) +domain_weno = Domain(config_weno, mesh) +println("WENO: nghosts = ", domain_weno.nghosts) # 2 + +# 测试 WENO3(字符串 "weno3") +config_weno3 = MockConfig("weno3", 2) +domain_weno3 = Domain(config_weno3, mesh) +println("WENO3: nghosts = ", domain_weno3.nghosts) # 2 + +# 测试 is_physical_cell +@assert is_physical_cell(domain_eno, 2) == true # ist=2 +@assert is_physical_cell(domain_eno, 41) == true # ied-1=41 +@assert is_physical_cell(domain_eno, 42) == false # ied=42 + +# ✅ 断言 +@assert domain_eno.nghosts == 2 +@assert domain_eno.ist == 2 +@assert domain_eno.ied == 42 +@assert domain_eno.ntcells == 44 +@assert domain_weno.nghosts == 2 +@assert domain_weno3.nghosts == 2 + +println("✅ Domain 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01c/julia/test/test_initial_condition.jl b/example/1d-linear-convection/weno3/julia/01c/julia/test/test_initial_condition.jl new file mode 100644 index 000000000..dc58691a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/julia/test/test_initial_condition.jl @@ -0,0 +1,45 @@ +# julia/test/test_initial_condition.jl +using NPZ + +# 包含初始条件模块 +include("../initial_condition.jl") + +# 构造 mock config(与 Python 完全一致) +struct MockConfig + ic_type::String + domain_length::Float64 + pulse_center::Float64 + pulse_width::Float64 +end + +# 生成与 Python Mesh.xcc 完全相同的 x 坐标 +function generate_xcc() + xmin, xmax = 0.0, 2.0 + ncells = 40 + dx = (xmax - xmin) / ncells + xcc = Vector{Float64}(undef, ncells) + for i in 1:ncells + xcc[i] = xmin + (i - 0.5) * dx # i-1 + 0.5 → i-0.5 + end + return xcc +end + +# 主测试 +xcc = generate_xcc() + +test_cases = [ + ("step", StepFunctionIC(MockConfig("step", 2.0, 0.5, 0.1))), + ("sin", SineWaveIC(MockConfig("sin", 2.0, 0.5, 0.1))), + ("gaussian", GaussianPulseIC(MockConfig("gaussian", 2.0, 0.5, 0.1))) +] + +for (name, ic) in test_cases + u_jl = evaluate_at(ic, xcc) + u_py = npzread("../../python/u_$(name)_interior_py.npy") + + err = maximum(abs.(u_jl .- u_py)) + println("IC: $(name) | 最大误差: $(err)") + @assert err < 1e-12 "❌ $(name) 不一致!" +end + +println("✅ 所有初始条件测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01c/julia/test/test_mesh.jl b/example/1d-linear-convection/weno3/julia/01c/julia/test/test_mesh.jl new file mode 100644 index 000000000..315d2aac2 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/julia/test/test_mesh.jl @@ -0,0 +1,37 @@ +# julia/test/test_mesh.jl +include("../mesh.jl") + +# 创建 mesh(与 Python 完全相同) +mesh = Mesh() + +# 打印关键值(与 Python 对比) +println("xmin = ", mesh.xmin) # 0.0 +println("xmax = ", mesh.xmax) # 2.0 +println("ncells = ", mesh.ncells) # 40 +println("nnodes = ", mesh.nnodes) # 41 +println("nx = ", mesh.nx) # 40 +println("L = ", mesh.L) # 2.0 +println("dx = ", mesh.dx) # 0.05 + +# 检查 x[1] (Python x[0]) 和 x[41] (Python x[40]) +println("x[1] = ", mesh.x[1]) # 0.0 +println("x[41] = ", mesh.x[41]) # 2.0 + +# 检查 xcc[1] (Python xcc[0]) 和 xcc[40] (Python xcc[39]) +println("xcc[1] = ", mesh.xcc[1]) # 0.025 +println("xcc[40] = ", mesh.xcc[40]) # 1.975 + +# ✅ 严格断言 +@assert mesh.xmin == 0.0 +@assert mesh.xmax == 2.0 +@assert mesh.ncells == 40 +@assert mesh.nnodes == 41 +@assert mesh.nx == 40 +@assert mesh.L == 2.0 +@assert mesh.dx == 0.05 +@assert mesh.x[1] == 0.0 +@assert mesh.x[41] == 2.0 +@assert abs(mesh.xcc[1] - 0.025) < 1e-12 +@assert abs(mesh.xcc[40] - 1.975) < 1e-12 + +println("✅ Mesh 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01c/python/boundary.py b/example/1d-linear-convection/weno3/julia/01c/python/boundary.py new file mode 100644 index 000000000..6054f92de --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/python/boundary.py @@ -0,0 +1,103 @@ +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from factories.base_factory import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01c/python/cfd_registry.py b/example/1d-linear-convection/weno3/julia/01c/python/cfd_registry.py new file mode 100644 index 000000000..c12eb106c --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/python/cfd_registry.py @@ -0,0 +1,62 @@ +# cfd_registry.py +""" +CFD注册系统统一导入模块 +所有其他模块从这里导入注册系统 +""" + +import sys +import os + +# 添加当前目录到路径,确保能找到registry.py +current_dir = os.path.dirname(os.path.abspath(__file__)) +if current_dir not in sys.path: + sys.path.insert(0, current_dir) + +try: + # 尝试导入注册系统 + from registry import ComponentRegistry, register_component + REGISTRY_AVAILABLE = True +except ImportError: + # 如果找不到,提供简单的空实现 + print("⚠️ 警告: 未找到 registry.py,使用最小化回退实现") + + class ComponentRegistry: + """最小化的注册系统回退实现""" + _registries = {} + + @classmethod + def register(cls, category, name, component_class): + if category not in cls._registries: + cls._registries[category] = {} + cls._registries[category][name] = component_class + + @classmethod + def get(cls, category, name): + if category not in cls._registries: + raise ValueError(f"未知类别: {category}") + if name not in cls._registries[category]: + raise ValueError(f"未找到: {category}.{name}") + return cls._registries[category][name] + + @classmethod + def create(cls, category, name, *args, **kwargs): + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) + for cat, comps in cls._registries.items()} + + def register_component(category, name=None): + """简化的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + REGISTRY_AVAILABLE = False + +# 导出统一的接口 +__all__ = ['ComponentRegistry', 'register_component', 'REGISTRY_AVAILABLE'] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01c/python/config.py b/example/1d-linear-convection/weno3/julia/01c/python/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/python/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01c/python/domain.py b/example/1d-linear-convection/weno3/julia/01c/python/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/python/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01c/python/factories/base_factory.py b/example/1d-linear-convection/weno3/julia/01c/python/factories/base_factory.py new file mode 100644 index 000000000..7b8b6105e --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/python/factories/base_factory.py @@ -0,0 +1,49 @@ +# factories/base_factory.py +""" +工厂基类 - 提供统一的组件创建接口 +""" + +from cfd_registry import ComponentRegistry +from typing import Any, Optional + +class BaseFactory: + """工厂基类,提供统一的组件创建方法""" + + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + """ + 统一创建组件的方法 + + Args: + category: 组件类别(如'boundary', 'flux'等) + name: 组件名称(如'periodic', 'rusanov'等) + *args, **kwargs: 传递给构造函数的参数 + + Returns: + 创建的组件实例 + + Raises: + ValueError: 如果组件不存在 + """ + name_lower = name.lower() + + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + # 获取可用组件列表 + available = ComponentRegistry.list_all().get(category, []) + + # 构建友好的错误信息 + if available: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"可用类型:{available}") + else: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"({category}类别下没有注册任何组件)") + + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + """获取指定类别的可用组件列表""" + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01c/python/flux.py b/example/1d-linear-convection/weno3/julia/01c/python/flux.py new file mode 100644 index 000000000..beb9ed488 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/python/flux.py @@ -0,0 +1,73 @@ +""" +通量计算器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册,替代硬编码工厂 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象通量计算基类(统一接口) ---------------------- +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass + +# ---------------------- 2. 具体通量计算子类(使用装饰器注册) ---------------------- + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + +class FluxCalculatorFactory: + """通量计算器工厂""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD对象 + + Returns: + 通量计算器实例 + """ + from factories.base_factory import BaseFactory + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01c/python/gen_boundary_test_data.py b/example/1d-linear-convection/weno3/julia/01c/python/gen_boundary_test_data.py new file mode 100644 index 000000000..c7fc2a9c5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/python/gen_boundary_test_data.py @@ -0,0 +1,35 @@ +# python/gen_boundary_test_data.py +import numpy as np +from config import CfdConfig +from mesh import Mesh +from domain import Domain + +# 固定测试配置 +config = CfdConfig() +config.with_boundary("dirichlet", left_value=0.5, right_value=1.5) +config.debug = True + +mesh = Mesh() +domain = Domain(config, mesh) + +# 构造 mock CFD 对象(仅含 config + domain) +class MockCfd: + def __init__(self, config, domain): + self.config = config + self.domain = domain + +# 测试用 u:0,1,2,...,N-1 +u_input = np.arange(domain.ntcells, dtype=np.float64) +np.save("u_input.npy", u_input) + +# 测试每种边界 +from boundary import PeriodicBoundary, DirichletBoundary, NeumannBoundary + +for bc_name, bc_class in [("periodic", PeriodicBoundary), ("dirichlet", DirichletBoundary), ("neumann", NeumannBoundary)]: + u = u_input.copy() + cfd_mock = MockCfd(config, domain) + bc = bc_class(cfd_mock) + bc.apply(u) + np.save(f"u_{bc_name}_py.npy", u) + +print("✅ 测试数据已生成:u_*.npy") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01c/python/gen_ic_test_data.py b/example/1d-linear-convection/weno3/julia/01c/python/gen_ic_test_data.py new file mode 100644 index 000000000..69c2573b8 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/python/gen_ic_test_data.py @@ -0,0 +1,27 @@ +# python/gen_ic_test_data.py +import sys, os +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +import numpy as np +from config import CfdConfig +from mesh import Mesh +from domain import Domain +from solution import Solution + +# 固定 mesh +mesh = Mesh() +config = CfdConfig() + +# 测试三种 IC +for ic_type in ["step", "sin", "gaussian"]: + config.ic_type = ic_type + domain = Domain(config, mesh) + sol = Solution(config, domain) + + u_full = sol.u.copy() # 包含 ghost + u_interior = sol.u[domain.ist:domain.ied].copy() + + np.save(f"u_{ic_type}_full_py.npy", u_full) + np.save(f"u_{ic_type}_interior_py.npy", u_interior) + +print("✅ 初始条件测试数据已生成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01c/python/initial_condition.py b/example/1d-linear-convection/weno3/julia/01c/python/initial_condition.py new file mode 100644 index 000000000..047415b74 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/python/initial_condition.py @@ -0,0 +1,90 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, ic_type: str, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from factories.base_factory import BaseFactory + return BaseFactory.create_component('initial_condition', ic_type, config) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01c/python/mesh.py b/example/1d-linear-convection/weno3/julia/01c/python/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/python/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01c/python/plotter.py b/example/1d-linear-convection/weno3/julia/01c/python/plotter.py new file mode 100644 index 000000000..9f1a414fd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/python/plotter.py @@ -0,0 +1,107 @@ +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01c/python/reconstructor/__init__.py b/example/1d-linear-convection/weno3/julia/01c/python/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/python/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01c/python/reconstructor/base.py b/example/1d-linear-convection/weno3/julia/01c/python/reconstructor/base.py new file mode 100644 index 000000000..bbd638503 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/python/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def reconstruct(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01c/python/reconstructor/eno.py b/example/1d-linear-convection/weno3/julia/01c/python/reconstructor/eno.py new file mode 100644 index 000000000..c2fb385dd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/python/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def reconstruct(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01c/python/reconstructor/factory.py b/example/1d-linear-convection/weno3/julia/01c/python/reconstructor/factory.py new file mode 100644 index 000000000..efa4a1446 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/python/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from cfd_registry import ComponentRegistry + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from factories.base_factory import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01c/python/reconstructor/weno3.py b/example/1d-linear-convection/weno3/julia/01c/python/reconstructor/weno3.py new file mode 100644 index 000000000..6e8c3f230 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/python/reconstructor/weno3.py @@ -0,0 +1,88 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def reconstruct(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + """ + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 # smoothness indicator for stencil [v2, v3] + beta1 = (v2 - v1)**2 # smoothness indicator for stencil [v1, v2] + + d0, d1 = 2/3, 1/3 # optimal linear weights (for right value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 0.5 * v2 + 0.5 * v3 # reconstruction from [v2, v3] + q1 = -0.5 * v1 + 1.5 * v2 # reconstruction from [v1, v2] + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 + beta1 = (v2 - v1)**2 + + d0, d1 = 1/3, 2/3 # optimal linear weights (for left value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 1.5 * v2 - 0.5 * v3 # from [v2, v3] + q1 = 0.5 * v1 + 0.5 * v2 # from [v1, v2] + return w0 * q0 + w1 * q1 + """ diff --git a/example/1d-linear-convection/weno3/julia/01c/python/registry.py b/example/1d-linear-convection/weno3/julia/01c/python/registry.py new file mode 100644 index 000000000..61be90500 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/python/registry.py @@ -0,0 +1,75 @@ +# registry.py (改进版) +""" +CFD组件注册系统 - 简洁高效版 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表""" + + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True # 控制是否打印注册信息 + + @classmethod + def set_verbose(cls, verbose: bool): + """设置是否打印注册信息""" + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + # 检查是否已注册 + if name in cls._registries[category]: + if cls._registries[category][name] == component_class: + # 相同类重复注册,静默跳过 + return + elif cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取组件类""" + if category not in cls._registries: + available = list(cls._registries.keys()) + raise ValueError(f"❌ 未知类别: {category} (可用类别: {available})") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + @classmethod + def get_count(cls) -> int: + """获取注册组件总数""" + return sum(len(components) for components in cls._registries.values()) + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01c/python/residual.py b/example/1d-linear-convection/weno3/julia/01c/python/residual.py new file mode 100644 index 000000000..b4d4d7dcc --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/python/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux import FluxCalculatorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + self.reconstructor = self.cfd.reconstructor + + # 初始化通量计算器(工厂创建) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._reconstruct() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _reconstruct(self): + """私有方法:界面值重建""" + self.reconstructor.reconstruct(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01c/python/run_eno_weno.py b/example/1d-linear-convection/weno3/julia/01c/python/run_eno_weno.py new file mode 100644 index 000000000..fd7f38744 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/python/run_eno_weno.py @@ -0,0 +1,52 @@ +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + #mesh = Mesh(ncells=100, L=2.0) + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行ENO3求解(使用你的链式调用) + print("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + config_eno3.with_reconstruction("eno", 3) # 显式指定3阶(也可省略,ENO默认3阶) + # 可选:覆盖默认值(如dt) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + cfd_eno3.run() # 求解并生成result字典 + + # 可选:快速验证ENO3结果 + # plotter.plot_quick(cfd_eno3.result, title="ENO3 Quick Check") + + # 3. 配置并运行WENO3求解(注意:WENO默认5阶,这里显式指定3阶) + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) # 显式指定3阶(默认是5阶) + # 可选:覆盖默认值 + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + cfd_weno3.run() + + # 4. 可选:保存结果(供离线绘图) + # cfd_eno3.save_result("eno3_result.npz") + # cfd_weno3.save_result("weno3_result.npz") + + # 5. 绘制ENO/WENO对比图 + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" # 可选:保存图片 + ) + +if __name__ == "__main__": + # 主程序入口 + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01c/python/solution.py b/example/1d-linear-convection/weno3/julia/01c/python/solution.py new file mode 100644 index 000000000..92a46d3f4 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/python/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config.ic_type, config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01c/python/solver.py b/example/1d-linear-convection/weno3/julia/01c/python/solver.py new file mode 100644 index 000000000..0d0b442da --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/python/solver.py @@ -0,0 +1,86 @@ +import numpy as np +import matplotlib.pyplot as plt +import inflect +from abc import ABC, abstractmethod + + +from flux import InviscidFluxCalculator, RusanovFluxCalculator, EngquistOsherFluxCalculator, FluxCalculatorFactory + +from boundary import BoundaryCondition, PeriodicBoundary, DirichletBoundary, NeumannBoundary, BoundaryConditionFactory + +from time_integration import TimeIntegrator, RK1Integrator, RK2Integrator, RK3Integrator, TimeIntegratorFactory + +from mesh import Mesh + +from reconstructor import ReconstructorFactory + +from initial_condition import InitialCondition, StepFunctionIC, SineWaveIC, GaussianPulseIC, InitialConditionFactory + +from domain import Domain +from solution import Solution + +from config import CfdConfig +from residual import ResidualCalculator + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, config, mesh): + self.config = config + self.domain = Domain(config, mesh) + self.solution = Solution(config, self.domain) + self.reconstructor = ReconstructorFactory.create(config, self.domain) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + def exact_solution(self): + """通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界""" + x = self.domain.mesh.xcc + T = self.config.final_time + c = self.config.wave_speed + L = self.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = InitialConditionFactory.create(self.config.ic_type, self.config) + return ic.evaluate_at(x_shifted) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + #runge_kutta(self) + self.integrator.step(dt) + t += dt + config.dt = dt_old + + # 整理标准化结果 + u_numerical = self.solution.u[self.domain.ist:self.domain.ied].copy() + self.result = { + "x": domain.mesh.xcc, + "numerical": u_numerical, + "analytical": self.exact_solution(), + "config": { + "scheme": self.config.recon_scheme, + "order": self.config.spatial_order, + "rk_order": self.config.rk_order, + "final_time": self.config.final_time + } + } + + return u_numerical diff --git a/example/1d-linear-convection/weno3/julia/01c/python/time_integration.py b/example/1d-linear-convection/weno3/julia/01c/python/time_integration.py new file mode 100644 index 000000000..25ac0b4ca --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01c/python/time_integration.py @@ -0,0 +1,125 @@ +# time_integration.py + +""" +时间推进器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = cfd.residual_calculator + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(使用装饰器注册) ---------------------- + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class TimeIntegratorFactory: + """时间推进器工厂""" + + @staticmethod + def create(cfd) -> 'TimeIntegrator': + """ + 创建时间推进器实例 + + Args: + cfd: CFD对象 + + Returns: + 时间推进器实例 + """ + from factories.base_factory import BaseFactory + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01c/python/u_dirichlet_py.npy b/example/1d-linear-convection/weno3/julia/01c/python/u_dirichlet_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..3aa20bd2e395654a6e8c59bbbc9f5eaa955b345e GIT binary patch literal 480 zcmbWzxeftg6o%oW6O}@tQ2Y_`BNMTVeHmMHM59p2#LPq?$c$)2;vU?f%TQY8ZM07F zov8Zw0H1NzP(=`e8>IlQ+pS*SsNB7-1(=_si zx~l&#Cf|Elzsex&D8?~?Nlc-_G-fc1IV_-oMJ!<%D_BJnYgoqyHnD|m>|hsr*hdQo Wp*NHvjWVK9#x%-=MtSnjr}YA`88kxx literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01c/python/u_gaussian_full_py.npy b/example/1d-linear-convection/weno3/julia/01c/python/u_gaussian_full_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..b762f0fe295b52a9a176817021995ce5a561d778 GIT binary patch literal 480 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yCOVor3bhL411<(MU?Ou)P3Oi_+soE}ylQv)*=cg#Ue98-%FeWXxBn~o z-*&0B&Pq*ej`k{D0z3w9EA6|!ORWz4wA}u9Vfdl$E2r%H!VVg&w0>m2nCbZM)%@?^ zbQ@G0rY;|Sof!j>qBKc_8=pP9zb3SzgEt8=WEn__NZ z$*8-a{Z)*4!nvvF3 wqbFAl+ziF%o9|Zhkuvbx>B0B<*(1H@$~rUDs~6~AeE<2_q`W{KwBTd_0INu`7ytkO literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01c/python/u_gaussian_interior_py.npy b/example/1d-linear-convection/weno3/julia/01c/python/u_gaussian_interior_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..68af8954e7a17e69b6b2e6954b78ee542d4e96a0 GIT binary patch literal 448 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#y20EHL3bhL411=Mpb80#_p4wiv{^M1<)6Y(m^Y(fcvsHGc?YsS7$^W)X zt#wvvVso@t=@Q^Ecw1@T^<8Rp;HTyG#|y&`bzeDU-xqe!V5RjV`^8Mhf3N0$2dCSh z;xKjjP<`JzA?Co${oe+$2WBtK9e1T6?wYnbA&=>Lk?qOJ*(aWE{cN*=zyHlG=1iNl zN(soJS7TeOgcr6%N&Go&S^Ug2epV2>rCgn3z1$RY3rj}b z1?{h5%p2Bhd1c#FYWC{z!qS+GX48p_*$(R2wVEWXJEgqJq{jHxHq-S&9M{! literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01c/python/u_input.npy b/example/1d-linear-convection/weno3/julia/01c/python/u_input.npy new file mode 100644 index 0000000000000000000000000000000000000000..ef506fa75cacbbe63dfcc17332ddd6c33de5b888 GIT binary patch literal 480 zcmbWrI}gE70EXdX6O+MUFr0`uNFy$FzqBs35sSejjUF+GN-GwT_z(W5pP?kM2cBT{2ChY{-ZF ze<|^^w?*2qNlaq~vzSAPc`RTNOISezt60N2Hn52%wy=#I>|zi5IKUx}aEun(u}>5e R8pV`GF{4q;X%r6a*dIEnGEe{j literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01c/python/u_neumann_py.npy b/example/1d-linear-convection/weno3/julia/01c/python/u_neumann_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..fa723d1efce906983b6e6710379207d15afbc271 GIT binary patch literal 480 zcmbWrI}gE70EXdX6O+MUFr0`uC5^ZgRkymtMl1%CG}^`>Dy>*V;y?JqN?(i7w>|hs7?4gBy9N-W~I7U11fnvf( PG3BF}@lnkAKgRt9WDhZ? literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01c/python/u_periodic_py.npy b/example/1d-linear-convection/weno3/julia/01c/python/u_periodic_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..156aff85dfa4c84deaaed404e0dccc37937f8836 GIT binary patch literal 480 zcmbWzyAA+Jw5Jw7YF-Tj}jFJcs^aeGCKh-lzUPGOs<_`6u&g%W>?2t#0c7 zOUUOY-iJGid|Tp}#1y76g9@{l!#rA8L>o(3#tK%kh7Q)TflX{-8#~xV7kk*p0eWBW QC_@@$M5Bypl;8h*0k_X5-%Mo@PtK#WJA?o%og6P}-0bsJ$1U?l6M7O94XfzYY=Kp9oR6e-T9A{tpmy_6tDF-R}f( z-+mW}`}RZKw;$@h{Tm?e+z)l%epjeEd#FAks5zgY=FW!NlLxi;2-F?cP8 literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01d/julia/boundary.jl b/example/1d-linear-convection/weno3/julia/01d/julia/boundary.jl new file mode 100644 index 000000000..5b0baaf43 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/julia/boundary.jl @@ -0,0 +1,90 @@ +# julia/boundary.jl +# 目标:与 Python boundary.py 行为完全一致(1:1 同构) +# 前提:ist/ied 在 Julia 中按 1-based 设置(ist = nghosts + 1) + +using NPZ # 仅用于测试,实际模块可不依赖 + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象基类(Julia 风格:用函数接口) ---------------------- +# Julia 无 abstract class,用文档+约定 +# 所有子类型必须实现 apply!(bc, u) + +# ---------------------- PeriodicBoundary ---------------------- +mutable struct PeriodicBoundary + cfd::Any +end + +function apply!(self::PeriodicBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist # 1-based, e.g., 3 + ied = self.cfd.domain.ied # 1-based, e.g., 43 + + # 左 ghost: u[ist - 1 - ig] = u[ied - 1 - ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ied - 1 - ig] + end + # 右 ghost: u[ied + ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ied + ig] = u[ist + ig] + end +end + +# ---------------------- DirichletBoundary ---------------------- +mutable struct DirichletBoundary + cfd::Any +end + +function apply!(self::DirichletBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + left_value = getfield_safe(self.cfd.config, :left_boundary_value, 1.0) + right_value = getfield_safe(self.cfd.config, :right_boundary_value, 2.0) + + # 左边界 + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = left_value + end + # 右边界 + for ig in 0:(nghosts-1) + u[ied + ig] = right_value + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Dirichlet边界: 左值=$left_value, 右值=$right_value") + end +end + +# ---------------------- NeumannBoundary ---------------------- +mutable struct NeumannBoundary + cfd::Any +end + +function apply!(self::NeumannBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + # 左边界零梯度:u[ist - 1 - ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ist + ig] + end + # 右边界零梯度:u[ied + ig] = u[ied - 1 - ig] ← 注意是 ied - 1 - ig + for ig in 0:(nghosts-1) + u[ied + ig] = u[ied - 1 - ig] + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Neumann边界: 零梯度") + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/julia/domain.jl b/example/1d-linear-convection/weno3/julia/01d/julia/domain.jl new file mode 100644 index 000000000..818be8fa6 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/julia/domain.jl @@ -0,0 +1,61 @@ +# julia/domain.jl +""" +Domain 结构体(与 domain.py 完全同构) +- 保存 config +- nghosts 计算逻辑 1:1 +- ist/ied 为 0-based(与 Python 一致) +""" + +include("mesh.jl") + +mutable struct Domain + config::Any # 保存 config(与 Python 一致) + mesh::Mesh + nghosts::Int + ist::Int # 0-based + ied::Int # 0-based + ntcells::Int +end + +function Domain(config::Any, mesh::Mesh) + nghosts = _calc_nghosts(config) + ist = nghosts + ied = ist + mesh.ncells + ntcells = mesh.ncells + 2 * nghosts + Domain(config, mesh, nghosts, ist, ied, ntcells) +end + +function _calc_nghosts(config::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + order = getfield_safe(config, :spatial_order, 2) + + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + if scheme == "eno" + nghosts = order + elseif startswith(scheme, "weno") + nghosts = order ÷ 2 + 1 + else + error("未知重建格式 $(scheme),无法计算ghost层!") + end + + if nghosts <= 0 + error("计算得到的ghost层数量无效:$(nghosts)(阶数$(order),格式$(scheme))") + end + + return nghosts +end + +function is_physical_cell(domain::Domain, idx::Int) + return domain.ist <= idx < domain.ied +end + +function get_physical_indices(domain::Domain) + return domain.ist:(domain.ied - 1) +end + +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/julia/initial_condition.jl b/example/1d-linear-convection/weno3/julia/01d/julia/initial_condition.jl new file mode 100644 index 000000000..5e029e8d2 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/julia/initial_condition.jl @@ -0,0 +1,86 @@ +# julia/initial_condition.jl +""" +初始条件模块(Julia 版) +目标:与 Python initial_condition.py 行为完全一致 +""" + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象接口(Julia 无继承,用函数约定) ---------------------- +# 所有初始条件必须实现: +# evaluate_at(ic, x::AbstractVector{Float64}) -> Vector{Float64} +# apply(ic, solution) + +# ---------------------- StepFunctionIC ---------------------- +struct StepFunctionIC + config::Any +end + +function evaluate_at(ic::StepFunctionIC, x::AbstractVector{Float64}) + u0 = ones(Float64, length(x)) + for i in eachindex(x) + if 0.5 <= x[i] <= 1.0 + u0[i] = 2.0 + end + end + return u0 +end + +function apply(ic::StepFunctionIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- SineWaveIC ---------------------- +struct SineWaveIC + config::Any +end + +function evaluate_at(ic::SineWaveIC, x::AbstractVector{Float64}) + L = getfield_safe(ic.config, :domain_length, 2.0) + return sin.(2π * x / L) +end + +function apply(ic::SineWaveIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- GaussianPulseIC ---------------------- +struct GaussianPulseIC + config::Any +end + +function evaluate_at(ic::GaussianPulseIC, x::AbstractVector{Float64}) + center = getfield_safe(ic.config, :pulse_center, 0.5) + width = getfield_safe(ic.config, :pulse_width, 0.1) + return exp.(-((x .- center) ./ width).^2) +end + +function apply(ic::GaussianPulseIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- 公共辅助函数 ---------------------- +""" +将初始场 values 应用到 solution.u 的物理区域(含 ghost 的数组) +""" +function _apply_to_interior(solution, values) + domain = solution.domain + # Python: for i in range(domain.ist, domain.ied) + # Julia: for i in domain.ist:domain.ied-1 (因为 ied 是结束索引,不包含) + for i in domain.ist:(domain.ied - 1) + j = i - domain.ist + 1 # values 是 1-based,i - ist 是 0-based → +1 + solution.u[i] = values[j] + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/julia/mesh.jl b/example/1d-linear-convection/weno3/julia/01d/julia/mesh.jl new file mode 100644 index 000000000..1b0dd4bf9 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/julia/mesh.jl @@ -0,0 +1,45 @@ +# julia/mesh.jl +""" +Mesh 结构体(与提供的 mesh.py 完全同构) +- 字段名、初始化顺序、循环逻辑完全一致 +- 不做任何泛化或优化 +""" + +mutable struct Mesh + xmin::Float64 + xmax::Float64 + ncells::Int + nnodes::Int + nx::Int + x::Vector{Float64} + xcc::Vector{Float64} + L::Float64 # 在 init_mesh 中赋值 + dx::Float64 # 在 init_mesh 中赋值 + + function Mesh() + # 与 Python __init__ 完全一致 + xmin = 0.0 + xmax = 2.0 + ncells = 40 + nnodes = ncells + 1 + nx = ncells + x = zeros(Float64, nnodes) + xcc = zeros(Float64, ncells) + + # 调用 init_mesh(模拟 Python) + L = xmax - xmin + dx = L / ncells + + # Generate node coordinates: for i in range(self.nnodes) + for i in 1:nnodes + x[i] = xmin + (i - 1) * dx # Python i → Julia i-1 + end + + # Generate cell center coordinates + for i in 1:ncells + xcc[i] = 0.5 * (x[i] + x[i+1]) + end + + new(xmin, xmax, ncells, nnodes, nx, x, xcc, L, dx) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/julia/solution.jl b/example/1d-linear-convection/weno3/julia/01d/julia/solution.jl new file mode 100644 index 000000000..90ca03931 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/julia/solution.jl @@ -0,0 +1,75 @@ +# julia/solution.jl +""" +Solution 结构体(与真实 solution.py 完全同构) +字段顺序、方法、逻辑 1:1 对齐 +""" + +include("mesh.jl") +include("domain.jl") +include("initial_condition.jl") + +mutable struct Solution + domain::Domain + q_face_left::Vector{Float64} + q_face_right::Vector{Float64} + flux::Vector{Float64} + res::Vector{Float64} + u::Vector{Float64} + un::Vector{Float64} + + function Solution(config::Any, domain::Domain) + mesh = domain.mesh + + q_face_left = zeros(Float64, mesh.nnodes) + q_face_right = zeros(Float64, mesh.nnodes) + flux = zeros(Float64, mesh.nnodes) + res = zeros(Float64, mesh.ncells) + u = zeros(Float64, domain.ntcells) + un = zeros(Float64, domain.ntcells) + + sol = new(domain, q_face_left, q_face_right, flux, res, u, un) + initialize_from_config(sol, config) + return sol + end +end + +""" +重置解数组为初始状态 +""" +function reset_solution(sol::Solution) + fill!(sol.u, 0.0) + fill!(sol.un, 0.0) +end + +""" +根据配置初始化场 +注:暂用硬编码替代 InitialConditionFactory +""" +function initialize_from_config(sol::Solution, config::Any) + ic_type = getfield_safe(config, :ic_type, "step") + + # 硬编码创建 IC(替代 InitialConditionFactory) + ic = if ic_type == "step" + StepFunctionIC(config) + elseif ic_type == "sin" + SineWaveIC(config) + elseif ic_type == "gaussian" + GaussianPulseIC(config) + else + error("未知初始条件类型: $ic_type") + end + + apply(ic, sol) # 调用 IC.apply +end + +""" +更新旧场:un = u +""" +function update_old_field(sol::Solution) + sol.un .= sol.u # 等价于 Python 的 un[:] = u[:] +end + +# ---------------------- 辅助函数 ---------------------- +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/julia/test/test_boundary.jl b/example/1d-linear-convection/weno3/julia/01d/julia/test/test_boundary.jl new file mode 100644 index 000000000..ef27d6820 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/julia/test/test_boundary.jl @@ -0,0 +1,57 @@ +# julia/test/test_boundary.jl +using NPZ +include("../boundary.jl") + +struct MockConfig + boundary_type::String + left_boundary_value::Float64 + right_boundary_value::Float64 + debug::Bool +end + +struct MockDomain + nghosts::Int + ist::Int # Julia 本地索引(1-based) + ied::Int + ntcells::Int +end + +struct MockCfd + config::MockConfig + domain::MockDomain +end + +# ===== 关键:使用 Julia 本地索引规则 ===== +nghosts = 2 +ncells = 40 +ist = nghosts + 1 # = 3 +ied = ist + ncells # = 43 +ntcells = ncells + 2 * nghosts # = 44 + +config = MockConfig("dirichlet", 0.5, 1.5, true) +domain = MockDomain(nghosts, ist, ied, ntcells) +cfd_mock = MockCfd(config, domain) + +# 加载 Python 生成的 u_input.npy +# 注意:Python u[0] → Julia u[1],所以内容完全对应 +u_input = npzread("../../python/u_input.npy") +@assert length(u_input) == ntcells "数组长度不匹配!" + +# 测试三种边界 +test_cases = [ + ("periodic", PeriodicBoundary(cfd_mock)), + ("dirichlet", DirichletBoundary(cfd_mock)), + ("neumann", NeumannBoundary(cfd_mock)) +] + +for (name, bc) in test_cases + u = copy(u_input) + apply!(bc, u) + + u_py = npzread("../../python/u_$(name)_py.npy") + err = maximum(abs.(u .- u_py)) + println("边界: $(name) | 最大误差: $(err)") + @assert err < 1e-12 "❌ $(name) 不一致!" +end + +println("✅ 所有边界条件测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/julia/test/test_domain.jl b/example/1d-linear-convection/weno3/julia/01d/julia/test/test_domain.jl new file mode 100644 index 000000000..7b423f774 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/julia/test/test_domain.jl @@ -0,0 +1,44 @@ +# julia/test/test_domain.jl +include("../mesh.jl") +include("../domain.jl") + +# MockConfig:模拟 Python CfdConfig +struct MockConfig + recon_scheme::String + spatial_order::Int +end + +# 测试 ENO +config_eno = MockConfig("eno", 2) +mesh = Mesh() +domain_eno = Domain(config_eno, mesh) + +println("ENO: nghosts = ", domain_eno.nghosts) # 2 +println("ENO: ist = ", domain_eno.ist) # 2 +println("ENO: ied = ", domain_eno.ied) # 42 +println("物理索引范围: ", collect(get_physical_indices(domain_eno))[1:3], " ... ", collect(get_physical_indices(domain_eno))[end-2:end]) + +# 测试 WENO(字符串 "weno") +config_weno = MockConfig("weno", 2) +domain_weno = Domain(config_weno, mesh) +println("WENO: nghosts = ", domain_weno.nghosts) # 2 + +# 测试 WENO3(字符串 "weno3") +config_weno3 = MockConfig("weno3", 2) +domain_weno3 = Domain(config_weno3, mesh) +println("WENO3: nghosts = ", domain_weno3.nghosts) # 2 + +# 测试 is_physical_cell +@assert is_physical_cell(domain_eno, 2) == true # ist=2 +@assert is_physical_cell(domain_eno, 41) == true # ied-1=41 +@assert is_physical_cell(domain_eno, 42) == false # ied=42 + +# ✅ 断言 +@assert domain_eno.nghosts == 2 +@assert domain_eno.ist == 2 +@assert domain_eno.ied == 42 +@assert domain_eno.ntcells == 44 +@assert domain_weno.nghosts == 2 +@assert domain_weno3.nghosts == 2 + +println("✅ Domain 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/julia/test/test_initial_condition.jl b/example/1d-linear-convection/weno3/julia/01d/julia/test/test_initial_condition.jl new file mode 100644 index 000000000..dc58691a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/julia/test/test_initial_condition.jl @@ -0,0 +1,45 @@ +# julia/test/test_initial_condition.jl +using NPZ + +# 包含初始条件模块 +include("../initial_condition.jl") + +# 构造 mock config(与 Python 完全一致) +struct MockConfig + ic_type::String + domain_length::Float64 + pulse_center::Float64 + pulse_width::Float64 +end + +# 生成与 Python Mesh.xcc 完全相同的 x 坐标 +function generate_xcc() + xmin, xmax = 0.0, 2.0 + ncells = 40 + dx = (xmax - xmin) / ncells + xcc = Vector{Float64}(undef, ncells) + for i in 1:ncells + xcc[i] = xmin + (i - 0.5) * dx # i-1 + 0.5 → i-0.5 + end + return xcc +end + +# 主测试 +xcc = generate_xcc() + +test_cases = [ + ("step", StepFunctionIC(MockConfig("step", 2.0, 0.5, 0.1))), + ("sin", SineWaveIC(MockConfig("sin", 2.0, 0.5, 0.1))), + ("gaussian", GaussianPulseIC(MockConfig("gaussian", 2.0, 0.5, 0.1))) +] + +for (name, ic) in test_cases + u_jl = evaluate_at(ic, xcc) + u_py = npzread("../../python/u_$(name)_interior_py.npy") + + err = maximum(abs.(u_jl .- u_py)) + println("IC: $(name) | 最大误差: $(err)") + @assert err < 1e-12 "❌ $(name) 不一致!" +end + +println("✅ 所有初始条件测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/julia/test/test_mesh.jl b/example/1d-linear-convection/weno3/julia/01d/julia/test/test_mesh.jl new file mode 100644 index 000000000..315d2aac2 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/julia/test/test_mesh.jl @@ -0,0 +1,37 @@ +# julia/test/test_mesh.jl +include("../mesh.jl") + +# 创建 mesh(与 Python 完全相同) +mesh = Mesh() + +# 打印关键值(与 Python 对比) +println("xmin = ", mesh.xmin) # 0.0 +println("xmax = ", mesh.xmax) # 2.0 +println("ncells = ", mesh.ncells) # 40 +println("nnodes = ", mesh.nnodes) # 41 +println("nx = ", mesh.nx) # 40 +println("L = ", mesh.L) # 2.0 +println("dx = ", mesh.dx) # 0.05 + +# 检查 x[1] (Python x[0]) 和 x[41] (Python x[40]) +println("x[1] = ", mesh.x[1]) # 0.0 +println("x[41] = ", mesh.x[41]) # 2.0 + +# 检查 xcc[1] (Python xcc[0]) 和 xcc[40] (Python xcc[39]) +println("xcc[1] = ", mesh.xcc[1]) # 0.025 +println("xcc[40] = ", mesh.xcc[40]) # 1.975 + +# ✅ 严格断言 +@assert mesh.xmin == 0.0 +@assert mesh.xmax == 2.0 +@assert mesh.ncells == 40 +@assert mesh.nnodes == 41 +@assert mesh.nx == 40 +@assert mesh.L == 2.0 +@assert mesh.dx == 0.05 +@assert mesh.x[1] == 0.0 +@assert mesh.x[41] == 2.0 +@assert abs(mesh.xcc[1] - 0.025) < 1e-12 +@assert abs(mesh.xcc[40] - 1.975) < 1e-12 + +println("✅ Mesh 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/julia/test/test_solution.jl b/example/1d-linear-convection/weno3/julia/01d/julia/test/test_solution.jl new file mode 100644 index 000000000..f44ff0f0e --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/julia/test/test_solution.jl @@ -0,0 +1,42 @@ +# julia/test/test_solution.jl +include("../mesh.jl") +include("../domain.jl") +include("../solution.jl") + +# MockConfig +struct MockConfig + recon_scheme::String + spatial_order::Int + ic_type::String + domain_length::Float64 + pulse_center::Float64 + pulse_width::Float64 +end + +# 创建 solution +config = MockConfig("eno", 2, "step", 2.0, 0.5, 0.1) +mesh = Mesh() +domain = Domain(config, mesh) +sol = Solution(config, domain) + +# 检查字段尺寸 +@assert length(sol.q_face_left) == mesh.nnodes # 41 +@assert length(sol.flux) == mesh.nnodes # 41 +@assert length(sol.res) == mesh.ncells # 40 +@assert length(sol.u) == domain.ntcells # 44 + +# 检查初始场 +println("u[3] (物理起始): ", sol.u[3]) # 应为 1.0 +println("u[23] (x=1.0): ", sol.u[23]) # 应为 2.0 + +# 测试 update_old_field +sol.u[3] = 999.0 +update_old_field(sol) +@assert sol.un[3] == 999.0 + +# 测试 reset_solution +reset_solution(sol) +@assert sol.u[3] == 0.0 +@assert sol.un[3] == 0.0 + +println("✅ Solution 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/python/boundary.py b/example/1d-linear-convection/weno3/julia/01d/python/boundary.py new file mode 100644 index 000000000..6054f92de --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/python/boundary.py @@ -0,0 +1,103 @@ +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from factories.base_factory import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/python/cfd_registry.py b/example/1d-linear-convection/weno3/julia/01d/python/cfd_registry.py new file mode 100644 index 000000000..c12eb106c --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/python/cfd_registry.py @@ -0,0 +1,62 @@ +# cfd_registry.py +""" +CFD注册系统统一导入模块 +所有其他模块从这里导入注册系统 +""" + +import sys +import os + +# 添加当前目录到路径,确保能找到registry.py +current_dir = os.path.dirname(os.path.abspath(__file__)) +if current_dir not in sys.path: + sys.path.insert(0, current_dir) + +try: + # 尝试导入注册系统 + from registry import ComponentRegistry, register_component + REGISTRY_AVAILABLE = True +except ImportError: + # 如果找不到,提供简单的空实现 + print("⚠️ 警告: 未找到 registry.py,使用最小化回退实现") + + class ComponentRegistry: + """最小化的注册系统回退实现""" + _registries = {} + + @classmethod + def register(cls, category, name, component_class): + if category not in cls._registries: + cls._registries[category] = {} + cls._registries[category][name] = component_class + + @classmethod + def get(cls, category, name): + if category not in cls._registries: + raise ValueError(f"未知类别: {category}") + if name not in cls._registries[category]: + raise ValueError(f"未找到: {category}.{name}") + return cls._registries[category][name] + + @classmethod + def create(cls, category, name, *args, **kwargs): + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) + for cat, comps in cls._registries.items()} + + def register_component(category, name=None): + """简化的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + REGISTRY_AVAILABLE = False + +# 导出统一的接口 +__all__ = ['ComponentRegistry', 'register_component', 'REGISTRY_AVAILABLE'] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/python/config.py b/example/1d-linear-convection/weno3/julia/01d/python/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/python/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/python/domain.py b/example/1d-linear-convection/weno3/julia/01d/python/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/python/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/python/factories/base_factory.py b/example/1d-linear-convection/weno3/julia/01d/python/factories/base_factory.py new file mode 100644 index 000000000..7b8b6105e --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/python/factories/base_factory.py @@ -0,0 +1,49 @@ +# factories/base_factory.py +""" +工厂基类 - 提供统一的组件创建接口 +""" + +from cfd_registry import ComponentRegistry +from typing import Any, Optional + +class BaseFactory: + """工厂基类,提供统一的组件创建方法""" + + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + """ + 统一创建组件的方法 + + Args: + category: 组件类别(如'boundary', 'flux'等) + name: 组件名称(如'periodic', 'rusanov'等) + *args, **kwargs: 传递给构造函数的参数 + + Returns: + 创建的组件实例 + + Raises: + ValueError: 如果组件不存在 + """ + name_lower = name.lower() + + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + # 获取可用组件列表 + available = ComponentRegistry.list_all().get(category, []) + + # 构建友好的错误信息 + if available: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"可用类型:{available}") + else: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"({category}类别下没有注册任何组件)") + + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + """获取指定类别的可用组件列表""" + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/python/flux.py b/example/1d-linear-convection/weno3/julia/01d/python/flux.py new file mode 100644 index 000000000..beb9ed488 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/python/flux.py @@ -0,0 +1,73 @@ +""" +通量计算器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册,替代硬编码工厂 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象通量计算基类(统一接口) ---------------------- +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass + +# ---------------------- 2. 具体通量计算子类(使用装饰器注册) ---------------------- + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + +class FluxCalculatorFactory: + """通量计算器工厂""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD对象 + + Returns: + 通量计算器实例 + """ + from factories.base_factory import BaseFactory + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/python/gen_boundary_test_data.py b/example/1d-linear-convection/weno3/julia/01d/python/gen_boundary_test_data.py new file mode 100644 index 000000000..c7fc2a9c5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/python/gen_boundary_test_data.py @@ -0,0 +1,35 @@ +# python/gen_boundary_test_data.py +import numpy as np +from config import CfdConfig +from mesh import Mesh +from domain import Domain + +# 固定测试配置 +config = CfdConfig() +config.with_boundary("dirichlet", left_value=0.5, right_value=1.5) +config.debug = True + +mesh = Mesh() +domain = Domain(config, mesh) + +# 构造 mock CFD 对象(仅含 config + domain) +class MockCfd: + def __init__(self, config, domain): + self.config = config + self.domain = domain + +# 测试用 u:0,1,2,...,N-1 +u_input = np.arange(domain.ntcells, dtype=np.float64) +np.save("u_input.npy", u_input) + +# 测试每种边界 +from boundary import PeriodicBoundary, DirichletBoundary, NeumannBoundary + +for bc_name, bc_class in [("periodic", PeriodicBoundary), ("dirichlet", DirichletBoundary), ("neumann", NeumannBoundary)]: + u = u_input.copy() + cfd_mock = MockCfd(config, domain) + bc = bc_class(cfd_mock) + bc.apply(u) + np.save(f"u_{bc_name}_py.npy", u) + +print("✅ 测试数据已生成:u_*.npy") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/python/gen_ic_test_data.py b/example/1d-linear-convection/weno3/julia/01d/python/gen_ic_test_data.py new file mode 100644 index 000000000..69c2573b8 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/python/gen_ic_test_data.py @@ -0,0 +1,27 @@ +# python/gen_ic_test_data.py +import sys, os +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +import numpy as np +from config import CfdConfig +from mesh import Mesh +from domain import Domain +from solution import Solution + +# 固定 mesh +mesh = Mesh() +config = CfdConfig() + +# 测试三种 IC +for ic_type in ["step", "sin", "gaussian"]: + config.ic_type = ic_type + domain = Domain(config, mesh) + sol = Solution(config, domain) + + u_full = sol.u.copy() # 包含 ghost + u_interior = sol.u[domain.ist:domain.ied].copy() + + np.save(f"u_{ic_type}_full_py.npy", u_full) + np.save(f"u_{ic_type}_interior_py.npy", u_interior) + +print("✅ 初始条件测试数据已生成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/python/initial_condition.py b/example/1d-linear-convection/weno3/julia/01d/python/initial_condition.py new file mode 100644 index 000000000..047415b74 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/python/initial_condition.py @@ -0,0 +1,90 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, ic_type: str, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from factories.base_factory import BaseFactory + return BaseFactory.create_component('initial_condition', ic_type, config) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/python/mesh.py b/example/1d-linear-convection/weno3/julia/01d/python/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/python/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/python/plotter.py b/example/1d-linear-convection/weno3/julia/01d/python/plotter.py new file mode 100644 index 000000000..9f1a414fd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/python/plotter.py @@ -0,0 +1,107 @@ +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/python/reconstructor/__init__.py b/example/1d-linear-convection/weno3/julia/01d/python/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/python/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/python/reconstructor/base.py b/example/1d-linear-convection/weno3/julia/01d/python/reconstructor/base.py new file mode 100644 index 000000000..bbd638503 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/python/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def reconstruct(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/python/reconstructor/eno.py b/example/1d-linear-convection/weno3/julia/01d/python/reconstructor/eno.py new file mode 100644 index 000000000..c2fb385dd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/python/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def reconstruct(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/python/reconstructor/factory.py b/example/1d-linear-convection/weno3/julia/01d/python/reconstructor/factory.py new file mode 100644 index 000000000..efa4a1446 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/python/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from cfd_registry import ComponentRegistry + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from factories.base_factory import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/python/reconstructor/weno3.py b/example/1d-linear-convection/weno3/julia/01d/python/reconstructor/weno3.py new file mode 100644 index 000000000..6e8c3f230 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/python/reconstructor/weno3.py @@ -0,0 +1,88 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def reconstruct(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + """ + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 # smoothness indicator for stencil [v2, v3] + beta1 = (v2 - v1)**2 # smoothness indicator for stencil [v1, v2] + + d0, d1 = 2/3, 1/3 # optimal linear weights (for right value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 0.5 * v2 + 0.5 * v3 # reconstruction from [v2, v3] + q1 = -0.5 * v1 + 1.5 * v2 # reconstruction from [v1, v2] + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 + beta1 = (v2 - v1)**2 + + d0, d1 = 1/3, 2/3 # optimal linear weights (for left value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 1.5 * v2 - 0.5 * v3 # from [v2, v3] + q1 = 0.5 * v1 + 0.5 * v2 # from [v1, v2] + return w0 * q0 + w1 * q1 + """ diff --git a/example/1d-linear-convection/weno3/julia/01d/python/registry.py b/example/1d-linear-convection/weno3/julia/01d/python/registry.py new file mode 100644 index 000000000..61be90500 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/python/registry.py @@ -0,0 +1,75 @@ +# registry.py (改进版) +""" +CFD组件注册系统 - 简洁高效版 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表""" + + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True # 控制是否打印注册信息 + + @classmethod + def set_verbose(cls, verbose: bool): + """设置是否打印注册信息""" + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + # 检查是否已注册 + if name in cls._registries[category]: + if cls._registries[category][name] == component_class: + # 相同类重复注册,静默跳过 + return + elif cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取组件类""" + if category not in cls._registries: + available = list(cls._registries.keys()) + raise ValueError(f"❌ 未知类别: {category} (可用类别: {available})") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + @classmethod + def get_count(cls) -> int: + """获取注册组件总数""" + return sum(len(components) for components in cls._registries.values()) + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/python/residual.py b/example/1d-linear-convection/weno3/julia/01d/python/residual.py new file mode 100644 index 000000000..b4d4d7dcc --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/python/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux import FluxCalculatorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + self.reconstructor = self.cfd.reconstructor + + # 初始化通量计算器(工厂创建) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._reconstruct() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _reconstruct(self): + """私有方法:界面值重建""" + self.reconstructor.reconstruct(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/python/run_eno_weno.py b/example/1d-linear-convection/weno3/julia/01d/python/run_eno_weno.py new file mode 100644 index 000000000..fd7f38744 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/python/run_eno_weno.py @@ -0,0 +1,52 @@ +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + #mesh = Mesh(ncells=100, L=2.0) + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行ENO3求解(使用你的链式调用) + print("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + config_eno3.with_reconstruction("eno", 3) # 显式指定3阶(也可省略,ENO默认3阶) + # 可选:覆盖默认值(如dt) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + cfd_eno3.run() # 求解并生成result字典 + + # 可选:快速验证ENO3结果 + # plotter.plot_quick(cfd_eno3.result, title="ENO3 Quick Check") + + # 3. 配置并运行WENO3求解(注意:WENO默认5阶,这里显式指定3阶) + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) # 显式指定3阶(默认是5阶) + # 可选:覆盖默认值 + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + cfd_weno3.run() + + # 4. 可选:保存结果(供离线绘图) + # cfd_eno3.save_result("eno3_result.npz") + # cfd_weno3.save_result("weno3_result.npz") + + # 5. 绘制ENO/WENO对比图 + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" # 可选:保存图片 + ) + +if __name__ == "__main__": + # 主程序入口 + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/python/solution.py b/example/1d-linear-convection/weno3/julia/01d/python/solution.py new file mode 100644 index 000000000..92a46d3f4 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/python/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config.ic_type, config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/python/solver.py b/example/1d-linear-convection/weno3/julia/01d/python/solver.py new file mode 100644 index 000000000..0d0b442da --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/python/solver.py @@ -0,0 +1,86 @@ +import numpy as np +import matplotlib.pyplot as plt +import inflect +from abc import ABC, abstractmethod + + +from flux import InviscidFluxCalculator, RusanovFluxCalculator, EngquistOsherFluxCalculator, FluxCalculatorFactory + +from boundary import BoundaryCondition, PeriodicBoundary, DirichletBoundary, NeumannBoundary, BoundaryConditionFactory + +from time_integration import TimeIntegrator, RK1Integrator, RK2Integrator, RK3Integrator, TimeIntegratorFactory + +from mesh import Mesh + +from reconstructor import ReconstructorFactory + +from initial_condition import InitialCondition, StepFunctionIC, SineWaveIC, GaussianPulseIC, InitialConditionFactory + +from domain import Domain +from solution import Solution + +from config import CfdConfig +from residual import ResidualCalculator + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, config, mesh): + self.config = config + self.domain = Domain(config, mesh) + self.solution = Solution(config, self.domain) + self.reconstructor = ReconstructorFactory.create(config, self.domain) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + def exact_solution(self): + """通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界""" + x = self.domain.mesh.xcc + T = self.config.final_time + c = self.config.wave_speed + L = self.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = InitialConditionFactory.create(self.config.ic_type, self.config) + return ic.evaluate_at(x_shifted) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + #runge_kutta(self) + self.integrator.step(dt) + t += dt + config.dt = dt_old + + # 整理标准化结果 + u_numerical = self.solution.u[self.domain.ist:self.domain.ied].copy() + self.result = { + "x": domain.mesh.xcc, + "numerical": u_numerical, + "analytical": self.exact_solution(), + "config": { + "scheme": self.config.recon_scheme, + "order": self.config.spatial_order, + "rk_order": self.config.rk_order, + "final_time": self.config.final_time + } + } + + return u_numerical diff --git a/example/1d-linear-convection/weno3/julia/01d/python/time_integration.py b/example/1d-linear-convection/weno3/julia/01d/python/time_integration.py new file mode 100644 index 000000000..25ac0b4ca --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01d/python/time_integration.py @@ -0,0 +1,125 @@ +# time_integration.py + +""" +时间推进器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = cfd.residual_calculator + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(使用装饰器注册) ---------------------- + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class TimeIntegratorFactory: + """时间推进器工厂""" + + @staticmethod + def create(cfd) -> 'TimeIntegrator': + """ + 创建时间推进器实例 + + Args: + cfd: CFD对象 + + Returns: + 时间推进器实例 + """ + from factories.base_factory import BaseFactory + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01d/python/u_dirichlet_py.npy b/example/1d-linear-convection/weno3/julia/01d/python/u_dirichlet_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..3aa20bd2e395654a6e8c59bbbc9f5eaa955b345e GIT binary patch literal 480 zcmbWzxeftg6o%oW6O}@tQ2Y_`BNMTVeHmMHM59p2#LPq?$c$)2;vU?f%TQY8ZM07F zov8Zw0H1NzP(=`e8>IlQ+pS*SsNB7-1(=_si zx~l&#Cf|Elzsex&D8?~?Nlc-_G-fc1IV_-oMJ!<%D_BJnYgoqyHnD|m>|hsr*hdQo Wp*NHvjWVK9#x%-=MtSnjr}YA`88kxx literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01d/python/u_gaussian_full_py.npy b/example/1d-linear-convection/weno3/julia/01d/python/u_gaussian_full_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..b762f0fe295b52a9a176817021995ce5a561d778 GIT binary patch literal 480 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yCOVor3bhL411<(MU?Ou)P3Oi_+soE}ylQv)*=cg#Ue98-%FeWXxBn~o z-*&0B&Pq*ej`k{D0z3w9EA6|!ORWz4wA}u9Vfdl$E2r%H!VVg&w0>m2nCbZM)%@?^ zbQ@G0rY;|Sof!j>qBKc_8=pP9zb3SzgEt8=WEn__NZ z$*8-a{Z)*4!nvvF3 wqbFAl+ziF%o9|Zhkuvbx>B0B<*(1H@$~rUDs~6~AeE<2_q`W{KwBTd_0INu`7ytkO literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01d/python/u_gaussian_interior_py.npy b/example/1d-linear-convection/weno3/julia/01d/python/u_gaussian_interior_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..68af8954e7a17e69b6b2e6954b78ee542d4e96a0 GIT binary patch literal 448 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#y20EHL3bhL411=Mpb80#_p4wiv{^M1<)6Y(m^Y(fcvsHGc?YsS7$^W)X zt#wvvVso@t=@Q^Ecw1@T^<8Rp;HTyG#|y&`bzeDU-xqe!V5RjV`^8Mhf3N0$2dCSh z;xKjjP<`JzA?Co${oe+$2WBtK9e1T6?wYnbA&=>Lk?qOJ*(aWE{cN*=zyHlG=1iNl zN(soJS7TeOgcr6%N&Go&S^Ug2epV2>rCgn3z1$RY3rj}b z1?{h5%p2Bhd1c#FYWC{z!qS+GX48p_*$(R2wVEWXJEgqJq{jHxHq-S&9M{! literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01d/python/u_input.npy b/example/1d-linear-convection/weno3/julia/01d/python/u_input.npy new file mode 100644 index 0000000000000000000000000000000000000000..ef506fa75cacbbe63dfcc17332ddd6c33de5b888 GIT binary patch literal 480 zcmbWrI}gE70EXdX6O+MUFr0`uNFy$FzqBs35sSejjUF+GN-GwT_z(W5pP?kM2cBT{2ChY{-ZF ze<|^^w?*2qNlaq~vzSAPc`RTNOISezt60N2Hn52%wy=#I>|zi5IKUx}aEun(u}>5e R8pV`GF{4q;X%r6a*dIEnGEe{j literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01d/python/u_neumann_py.npy b/example/1d-linear-convection/weno3/julia/01d/python/u_neumann_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..fa723d1efce906983b6e6710379207d15afbc271 GIT binary patch literal 480 zcmbWrI}gE70EXdX6O+MUFr0`uC5^ZgRkymtMl1%CG}^`>Dy>*V;y?JqN?(i7w>|hs7?4gBy9N-W~I7U11fnvf( PG3BF}@lnkAKgRt9WDhZ? literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01d/python/u_periodic_py.npy b/example/1d-linear-convection/weno3/julia/01d/python/u_periodic_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..156aff85dfa4c84deaaed404e0dccc37937f8836 GIT binary patch literal 480 zcmbWzyAA+Jw5Jw7YF-Tj}jFJcs^aeGCKh-lzUPGOs<_`6u&g%W>?2t#0c7 zOUUOY-iJGid|Tp}#1y76g9@{l!#rA8L>o(3#tK%kh7Q)TflX{-8#~xV7kk*p0eWBW QC_@@$M5Bypl;8h*0k_X5-%Mo@PtK#WJA?o%og6P}-0bsJ$1U?l6M7O94XfzYY=Kp9oR6e-T9A{tpmy_6tDF-R}f( z-+mW}`}RZKw;$@h{Tm?e+z)l%epjeEd#FAks5zgY=FW!NlLxi;2-F?cP8 literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01e/julia/boundary.jl b/example/1d-linear-convection/weno3/julia/01e/julia/boundary.jl new file mode 100644 index 000000000..5b0baaf43 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/julia/boundary.jl @@ -0,0 +1,90 @@ +# julia/boundary.jl +# 目标:与 Python boundary.py 行为完全一致(1:1 同构) +# 前提:ist/ied 在 Julia 中按 1-based 设置(ist = nghosts + 1) + +using NPZ # 仅用于测试,实际模块可不依赖 + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象基类(Julia 风格:用函数接口) ---------------------- +# Julia 无 abstract class,用文档+约定 +# 所有子类型必须实现 apply!(bc, u) + +# ---------------------- PeriodicBoundary ---------------------- +mutable struct PeriodicBoundary + cfd::Any +end + +function apply!(self::PeriodicBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist # 1-based, e.g., 3 + ied = self.cfd.domain.ied # 1-based, e.g., 43 + + # 左 ghost: u[ist - 1 - ig] = u[ied - 1 - ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ied - 1 - ig] + end + # 右 ghost: u[ied + ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ied + ig] = u[ist + ig] + end +end + +# ---------------------- DirichletBoundary ---------------------- +mutable struct DirichletBoundary + cfd::Any +end + +function apply!(self::DirichletBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + left_value = getfield_safe(self.cfd.config, :left_boundary_value, 1.0) + right_value = getfield_safe(self.cfd.config, :right_boundary_value, 2.0) + + # 左边界 + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = left_value + end + # 右边界 + for ig in 0:(nghosts-1) + u[ied + ig] = right_value + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Dirichlet边界: 左值=$left_value, 右值=$right_value") + end +end + +# ---------------------- NeumannBoundary ---------------------- +mutable struct NeumannBoundary + cfd::Any +end + +function apply!(self::NeumannBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + # 左边界零梯度:u[ist - 1 - ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ist + ig] + end + # 右边界零梯度:u[ied + ig] = u[ied - 1 - ig] ← 注意是 ied - 1 - ig + for ig in 0:(nghosts-1) + u[ied + ig] = u[ied - 1 - ig] + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Neumann边界: 零梯度") + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/julia/domain.jl b/example/1d-linear-convection/weno3/julia/01e/julia/domain.jl new file mode 100644 index 000000000..818be8fa6 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/julia/domain.jl @@ -0,0 +1,61 @@ +# julia/domain.jl +""" +Domain 结构体(与 domain.py 完全同构) +- 保存 config +- nghosts 计算逻辑 1:1 +- ist/ied 为 0-based(与 Python 一致) +""" + +include("mesh.jl") + +mutable struct Domain + config::Any # 保存 config(与 Python 一致) + mesh::Mesh + nghosts::Int + ist::Int # 0-based + ied::Int # 0-based + ntcells::Int +end + +function Domain(config::Any, mesh::Mesh) + nghosts = _calc_nghosts(config) + ist = nghosts + ied = ist + mesh.ncells + ntcells = mesh.ncells + 2 * nghosts + Domain(config, mesh, nghosts, ist, ied, ntcells) +end + +function _calc_nghosts(config::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + order = getfield_safe(config, :spatial_order, 2) + + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + if scheme == "eno" + nghosts = order + elseif startswith(scheme, "weno") + nghosts = order ÷ 2 + 1 + else + error("未知重建格式 $(scheme),无法计算ghost层!") + end + + if nghosts <= 0 + error("计算得到的ghost层数量无效:$(nghosts)(阶数$(order),格式$(scheme))") + end + + return nghosts +end + +function is_physical_cell(domain::Domain, idx::Int) + return domain.ist <= idx < domain.ied +end + +function get_physical_indices(domain::Domain) + return domain.ist:(domain.ied - 1) +end + +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/julia/flux.jl b/example/1d-linear-convection/weno3/julia/01e/julia/flux.jl new file mode 100644 index 000000000..047598f7a --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/julia/flux.jl @@ -0,0 +1,70 @@ +# julia/flux.jl +""" +通量计算器模块(与 flux.py 完全同构) +- 抽象基类 + 具体实现 +- 字段:cfd, config, mesh, wave_speed +""" + +include("mesh.jl") + +# ---------------------- 抽象基类 ---------------------- +""" +InviscidFluxCalculator 抽象类型 +Julia 无 ABC,用文档约定 +所有子类型必须实现 compute! +""" +abstract type InviscidFluxCalculator end + +# ---------------------- RusanovFluxCalculator ---------------------- +mutable struct RusanovFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function RusanovFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::RusanovFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = calc.wave_speed + c_R = calc.wave_speed + F_L = c_L * u_L + F_R = c_R * u_R + Smax = max(abs(c_L), abs(c_R)) + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + end +end + +# ---------------------- EngquistOsherFluxCalculator ---------------------- +mutable struct EngquistOsherFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function EngquistOsherFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::EngquistOsherFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + c = calc.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/julia/initial_condition.jl b/example/1d-linear-convection/weno3/julia/01e/julia/initial_condition.jl new file mode 100644 index 000000000..5e029e8d2 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/julia/initial_condition.jl @@ -0,0 +1,86 @@ +# julia/initial_condition.jl +""" +初始条件模块(Julia 版) +目标:与 Python initial_condition.py 行为完全一致 +""" + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象接口(Julia 无继承,用函数约定) ---------------------- +# 所有初始条件必须实现: +# evaluate_at(ic, x::AbstractVector{Float64}) -> Vector{Float64} +# apply(ic, solution) + +# ---------------------- StepFunctionIC ---------------------- +struct StepFunctionIC + config::Any +end + +function evaluate_at(ic::StepFunctionIC, x::AbstractVector{Float64}) + u0 = ones(Float64, length(x)) + for i in eachindex(x) + if 0.5 <= x[i] <= 1.0 + u0[i] = 2.0 + end + end + return u0 +end + +function apply(ic::StepFunctionIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- SineWaveIC ---------------------- +struct SineWaveIC + config::Any +end + +function evaluate_at(ic::SineWaveIC, x::AbstractVector{Float64}) + L = getfield_safe(ic.config, :domain_length, 2.0) + return sin.(2π * x / L) +end + +function apply(ic::SineWaveIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- GaussianPulseIC ---------------------- +struct GaussianPulseIC + config::Any +end + +function evaluate_at(ic::GaussianPulseIC, x::AbstractVector{Float64}) + center = getfield_safe(ic.config, :pulse_center, 0.5) + width = getfield_safe(ic.config, :pulse_width, 0.1) + return exp.(-((x .- center) ./ width).^2) +end + +function apply(ic::GaussianPulseIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- 公共辅助函数 ---------------------- +""" +将初始场 values 应用到 solution.u 的物理区域(含 ghost 的数组) +""" +function _apply_to_interior(solution, values) + domain = solution.domain + # Python: for i in range(domain.ist, domain.ied) + # Julia: for i in domain.ist:domain.ied-1 (因为 ied 是结束索引,不包含) + for i in domain.ist:(domain.ied - 1) + j = i - domain.ist + 1 # values 是 1-based,i - ist 是 0-based → +1 + solution.u[i] = values[j] + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/julia/mesh.jl b/example/1d-linear-convection/weno3/julia/01e/julia/mesh.jl new file mode 100644 index 000000000..1b0dd4bf9 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/julia/mesh.jl @@ -0,0 +1,45 @@ +# julia/mesh.jl +""" +Mesh 结构体(与提供的 mesh.py 完全同构) +- 字段名、初始化顺序、循环逻辑完全一致 +- 不做任何泛化或优化 +""" + +mutable struct Mesh + xmin::Float64 + xmax::Float64 + ncells::Int + nnodes::Int + nx::Int + x::Vector{Float64} + xcc::Vector{Float64} + L::Float64 # 在 init_mesh 中赋值 + dx::Float64 # 在 init_mesh 中赋值 + + function Mesh() + # 与 Python __init__ 完全一致 + xmin = 0.0 + xmax = 2.0 + ncells = 40 + nnodes = ncells + 1 + nx = ncells + x = zeros(Float64, nnodes) + xcc = zeros(Float64, ncells) + + # 调用 init_mesh(模拟 Python) + L = xmax - xmin + dx = L / ncells + + # Generate node coordinates: for i in range(self.nnodes) + for i in 1:nnodes + x[i] = xmin + (i - 1) * dx # Python i → Julia i-1 + end + + # Generate cell center coordinates + for i in 1:ncells + xcc[i] = 0.5 * (x[i] + x[i+1]) + end + + new(xmin, xmax, ncells, nnodes, nx, x, xcc, L, dx) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/julia/solution.jl b/example/1d-linear-convection/weno3/julia/01e/julia/solution.jl new file mode 100644 index 000000000..90ca03931 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/julia/solution.jl @@ -0,0 +1,75 @@ +# julia/solution.jl +""" +Solution 结构体(与真实 solution.py 完全同构) +字段顺序、方法、逻辑 1:1 对齐 +""" + +include("mesh.jl") +include("domain.jl") +include("initial_condition.jl") + +mutable struct Solution + domain::Domain + q_face_left::Vector{Float64} + q_face_right::Vector{Float64} + flux::Vector{Float64} + res::Vector{Float64} + u::Vector{Float64} + un::Vector{Float64} + + function Solution(config::Any, domain::Domain) + mesh = domain.mesh + + q_face_left = zeros(Float64, mesh.nnodes) + q_face_right = zeros(Float64, mesh.nnodes) + flux = zeros(Float64, mesh.nnodes) + res = zeros(Float64, mesh.ncells) + u = zeros(Float64, domain.ntcells) + un = zeros(Float64, domain.ntcells) + + sol = new(domain, q_face_left, q_face_right, flux, res, u, un) + initialize_from_config(sol, config) + return sol + end +end + +""" +重置解数组为初始状态 +""" +function reset_solution(sol::Solution) + fill!(sol.u, 0.0) + fill!(sol.un, 0.0) +end + +""" +根据配置初始化场 +注:暂用硬编码替代 InitialConditionFactory +""" +function initialize_from_config(sol::Solution, config::Any) + ic_type = getfield_safe(config, :ic_type, "step") + + # 硬编码创建 IC(替代 InitialConditionFactory) + ic = if ic_type == "step" + StepFunctionIC(config) + elseif ic_type == "sin" + SineWaveIC(config) + elseif ic_type == "gaussian" + GaussianPulseIC(config) + else + error("未知初始条件类型: $ic_type") + end + + apply(ic, sol) # 调用 IC.apply +end + +""" +更新旧场:un = u +""" +function update_old_field(sol::Solution) + sol.un .= sol.u # 等价于 Python 的 un[:] = u[:] +end + +# ---------------------- 辅助函数 ---------------------- +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/julia/test/test_boundary.jl b/example/1d-linear-convection/weno3/julia/01e/julia/test/test_boundary.jl new file mode 100644 index 000000000..ef27d6820 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/julia/test/test_boundary.jl @@ -0,0 +1,57 @@ +# julia/test/test_boundary.jl +using NPZ +include("../boundary.jl") + +struct MockConfig + boundary_type::String + left_boundary_value::Float64 + right_boundary_value::Float64 + debug::Bool +end + +struct MockDomain + nghosts::Int + ist::Int # Julia 本地索引(1-based) + ied::Int + ntcells::Int +end + +struct MockCfd + config::MockConfig + domain::MockDomain +end + +# ===== 关键:使用 Julia 本地索引规则 ===== +nghosts = 2 +ncells = 40 +ist = nghosts + 1 # = 3 +ied = ist + ncells # = 43 +ntcells = ncells + 2 * nghosts # = 44 + +config = MockConfig("dirichlet", 0.5, 1.5, true) +domain = MockDomain(nghosts, ist, ied, ntcells) +cfd_mock = MockCfd(config, domain) + +# 加载 Python 生成的 u_input.npy +# 注意:Python u[0] → Julia u[1],所以内容完全对应 +u_input = npzread("../../python/u_input.npy") +@assert length(u_input) == ntcells "数组长度不匹配!" + +# 测试三种边界 +test_cases = [ + ("periodic", PeriodicBoundary(cfd_mock)), + ("dirichlet", DirichletBoundary(cfd_mock)), + ("neumann", NeumannBoundary(cfd_mock)) +] + +for (name, bc) in test_cases + u = copy(u_input) + apply!(bc, u) + + u_py = npzread("../../python/u_$(name)_py.npy") + err = maximum(abs.(u .- u_py)) + println("边界: $(name) | 最大误差: $(err)") + @assert err < 1e-12 "❌ $(name) 不一致!" +end + +println("✅ 所有边界条件测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/julia/test/test_domain.jl b/example/1d-linear-convection/weno3/julia/01e/julia/test/test_domain.jl new file mode 100644 index 000000000..7b423f774 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/julia/test/test_domain.jl @@ -0,0 +1,44 @@ +# julia/test/test_domain.jl +include("../mesh.jl") +include("../domain.jl") + +# MockConfig:模拟 Python CfdConfig +struct MockConfig + recon_scheme::String + spatial_order::Int +end + +# 测试 ENO +config_eno = MockConfig("eno", 2) +mesh = Mesh() +domain_eno = Domain(config_eno, mesh) + +println("ENO: nghosts = ", domain_eno.nghosts) # 2 +println("ENO: ist = ", domain_eno.ist) # 2 +println("ENO: ied = ", domain_eno.ied) # 42 +println("物理索引范围: ", collect(get_physical_indices(domain_eno))[1:3], " ... ", collect(get_physical_indices(domain_eno))[end-2:end]) + +# 测试 WENO(字符串 "weno") +config_weno = MockConfig("weno", 2) +domain_weno = Domain(config_weno, mesh) +println("WENO: nghosts = ", domain_weno.nghosts) # 2 + +# 测试 WENO3(字符串 "weno3") +config_weno3 = MockConfig("weno3", 2) +domain_weno3 = Domain(config_weno3, mesh) +println("WENO3: nghosts = ", domain_weno3.nghosts) # 2 + +# 测试 is_physical_cell +@assert is_physical_cell(domain_eno, 2) == true # ist=2 +@assert is_physical_cell(domain_eno, 41) == true # ied-1=41 +@assert is_physical_cell(domain_eno, 42) == false # ied=42 + +# ✅ 断言 +@assert domain_eno.nghosts == 2 +@assert domain_eno.ist == 2 +@assert domain_eno.ied == 42 +@assert domain_eno.ntcells == 44 +@assert domain_weno.nghosts == 2 +@assert domain_weno3.nghosts == 2 + +println("✅ Domain 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/julia/test/test_flux.jl b/example/1d-linear-convection/weno3/julia/01e/julia/test/test_flux.jl new file mode 100644 index 000000000..0ebc1dc05 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/julia/test/test_flux.jl @@ -0,0 +1,53 @@ +# julia/test/test_flux.jl +include("../mesh.jl") +include("../flux.jl") + +# Mock 对象 +struct MockConfig + flux_type::String + wave_speed::Float64 +end + +struct MockDomain + mesh::Mesh +end + +struct MockCfd + config::MockConfig + domain::MockDomain +end + +# 创建 mock +mesh = Mesh() +config = MockConfig("rusanov", 1.0) +domain = MockDomain(mesh) +cfd = MockCfd(config, domain) + +# 测试 Rusanov +rusanov = RusanovFluxCalculator(cfd) +N = mesh.nnodes +qL = [1.0, 2.0, 3.0, 4.0, 5.0, zeros(Float64, N-5)...] +qR = [2.0, 3.0, 4.0, 5.0, 6.0, zeros(Float64, N-5)...] +flux_rus = zeros(Float64, N) +compute!(rusanov, qL, qR, flux_rus) + +println("Rusanov flux[1] = ", flux_rus[1]) # 应为 1.0 +@assert abs(flux_rus[1] - 1.0) < 1e-12 + +# 测试 Engquist-Osher +eo = EngquistOsherFluxCalculator(cfd) +flux_eo = zeros(Float64, N) +compute!(eo, qL, qR, flux_eo) + +println("EO flux[1] = ", flux_eo[1]) # c=1 → cp=1, cm=0 → flux=1*1 + 0*2 = 1.0 +@assert abs(flux_eo[1] - 1.0) < 1e-12 + +# 测试 c = -1.0 +config_neg = MockConfig("rusanov", -1.0) +cfd_neg = MockCfd(config_neg, domain) +rusanov_neg = RusanovFluxCalculator(cfd_neg) +compute!(rusanov_neg, qL, qR, flux_rus) +println("Rusanov (c=-1) flux[1] = ", flux_rus[1]) # F_L=-1, F_R=-2, Smax=1 → flux = -1.5 -0.5*(-1) = -1.0 +@assert abs(flux_rus[1] - (-2.0)) < 1e-12 # ← 修正:-2.0 而非 -1.0 + +println("✅ Flux 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/julia/test/test_initial_condition.jl b/example/1d-linear-convection/weno3/julia/01e/julia/test/test_initial_condition.jl new file mode 100644 index 000000000..dc58691a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/julia/test/test_initial_condition.jl @@ -0,0 +1,45 @@ +# julia/test/test_initial_condition.jl +using NPZ + +# 包含初始条件模块 +include("../initial_condition.jl") + +# 构造 mock config(与 Python 完全一致) +struct MockConfig + ic_type::String + domain_length::Float64 + pulse_center::Float64 + pulse_width::Float64 +end + +# 生成与 Python Mesh.xcc 完全相同的 x 坐标 +function generate_xcc() + xmin, xmax = 0.0, 2.0 + ncells = 40 + dx = (xmax - xmin) / ncells + xcc = Vector{Float64}(undef, ncells) + for i in 1:ncells + xcc[i] = xmin + (i - 0.5) * dx # i-1 + 0.5 → i-0.5 + end + return xcc +end + +# 主测试 +xcc = generate_xcc() + +test_cases = [ + ("step", StepFunctionIC(MockConfig("step", 2.0, 0.5, 0.1))), + ("sin", SineWaveIC(MockConfig("sin", 2.0, 0.5, 0.1))), + ("gaussian", GaussianPulseIC(MockConfig("gaussian", 2.0, 0.5, 0.1))) +] + +for (name, ic) in test_cases + u_jl = evaluate_at(ic, xcc) + u_py = npzread("../../python/u_$(name)_interior_py.npy") + + err = maximum(abs.(u_jl .- u_py)) + println("IC: $(name) | 最大误差: $(err)") + @assert err < 1e-12 "❌ $(name) 不一致!" +end + +println("✅ 所有初始条件测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/julia/test/test_mesh.jl b/example/1d-linear-convection/weno3/julia/01e/julia/test/test_mesh.jl new file mode 100644 index 000000000..315d2aac2 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/julia/test/test_mesh.jl @@ -0,0 +1,37 @@ +# julia/test/test_mesh.jl +include("../mesh.jl") + +# 创建 mesh(与 Python 完全相同) +mesh = Mesh() + +# 打印关键值(与 Python 对比) +println("xmin = ", mesh.xmin) # 0.0 +println("xmax = ", mesh.xmax) # 2.0 +println("ncells = ", mesh.ncells) # 40 +println("nnodes = ", mesh.nnodes) # 41 +println("nx = ", mesh.nx) # 40 +println("L = ", mesh.L) # 2.0 +println("dx = ", mesh.dx) # 0.05 + +# 检查 x[1] (Python x[0]) 和 x[41] (Python x[40]) +println("x[1] = ", mesh.x[1]) # 0.0 +println("x[41] = ", mesh.x[41]) # 2.0 + +# 检查 xcc[1] (Python xcc[0]) 和 xcc[40] (Python xcc[39]) +println("xcc[1] = ", mesh.xcc[1]) # 0.025 +println("xcc[40] = ", mesh.xcc[40]) # 1.975 + +# ✅ 严格断言 +@assert mesh.xmin == 0.0 +@assert mesh.xmax == 2.0 +@assert mesh.ncells == 40 +@assert mesh.nnodes == 41 +@assert mesh.nx == 40 +@assert mesh.L == 2.0 +@assert mesh.dx == 0.05 +@assert mesh.x[1] == 0.0 +@assert mesh.x[41] == 2.0 +@assert abs(mesh.xcc[1] - 0.025) < 1e-12 +@assert abs(mesh.xcc[40] - 1.975) < 1e-12 + +println("✅ Mesh 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/julia/test/test_solution.jl b/example/1d-linear-convection/weno3/julia/01e/julia/test/test_solution.jl new file mode 100644 index 000000000..f44ff0f0e --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/julia/test/test_solution.jl @@ -0,0 +1,42 @@ +# julia/test/test_solution.jl +include("../mesh.jl") +include("../domain.jl") +include("../solution.jl") + +# MockConfig +struct MockConfig + recon_scheme::String + spatial_order::Int + ic_type::String + domain_length::Float64 + pulse_center::Float64 + pulse_width::Float64 +end + +# 创建 solution +config = MockConfig("eno", 2, "step", 2.0, 0.5, 0.1) +mesh = Mesh() +domain = Domain(config, mesh) +sol = Solution(config, domain) + +# 检查字段尺寸 +@assert length(sol.q_face_left) == mesh.nnodes # 41 +@assert length(sol.flux) == mesh.nnodes # 41 +@assert length(sol.res) == mesh.ncells # 40 +@assert length(sol.u) == domain.ntcells # 44 + +# 检查初始场 +println("u[3] (物理起始): ", sol.u[3]) # 应为 1.0 +println("u[23] (x=1.0): ", sol.u[23]) # 应为 2.0 + +# 测试 update_old_field +sol.u[3] = 999.0 +update_old_field(sol) +@assert sol.un[3] == 999.0 + +# 测试 reset_solution +reset_solution(sol) +@assert sol.u[3] == 0.0 +@assert sol.un[3] == 0.0 + +println("✅ Solution 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/python/boundary.py b/example/1d-linear-convection/weno3/julia/01e/python/boundary.py new file mode 100644 index 000000000..6054f92de --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/python/boundary.py @@ -0,0 +1,103 @@ +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from factories.base_factory import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/python/cfd_registry.py b/example/1d-linear-convection/weno3/julia/01e/python/cfd_registry.py new file mode 100644 index 000000000..c12eb106c --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/python/cfd_registry.py @@ -0,0 +1,62 @@ +# cfd_registry.py +""" +CFD注册系统统一导入模块 +所有其他模块从这里导入注册系统 +""" + +import sys +import os + +# 添加当前目录到路径,确保能找到registry.py +current_dir = os.path.dirname(os.path.abspath(__file__)) +if current_dir not in sys.path: + sys.path.insert(0, current_dir) + +try: + # 尝试导入注册系统 + from registry import ComponentRegistry, register_component + REGISTRY_AVAILABLE = True +except ImportError: + # 如果找不到,提供简单的空实现 + print("⚠️ 警告: 未找到 registry.py,使用最小化回退实现") + + class ComponentRegistry: + """最小化的注册系统回退实现""" + _registries = {} + + @classmethod + def register(cls, category, name, component_class): + if category not in cls._registries: + cls._registries[category] = {} + cls._registries[category][name] = component_class + + @classmethod + def get(cls, category, name): + if category not in cls._registries: + raise ValueError(f"未知类别: {category}") + if name not in cls._registries[category]: + raise ValueError(f"未找到: {category}.{name}") + return cls._registries[category][name] + + @classmethod + def create(cls, category, name, *args, **kwargs): + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) + for cat, comps in cls._registries.items()} + + def register_component(category, name=None): + """简化的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + REGISTRY_AVAILABLE = False + +# 导出统一的接口 +__all__ = ['ComponentRegistry', 'register_component', 'REGISTRY_AVAILABLE'] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/python/config.py b/example/1d-linear-convection/weno3/julia/01e/python/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/python/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/python/domain.py b/example/1d-linear-convection/weno3/julia/01e/python/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/python/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/python/factories/base_factory.py b/example/1d-linear-convection/weno3/julia/01e/python/factories/base_factory.py new file mode 100644 index 000000000..7b8b6105e --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/python/factories/base_factory.py @@ -0,0 +1,49 @@ +# factories/base_factory.py +""" +工厂基类 - 提供统一的组件创建接口 +""" + +from cfd_registry import ComponentRegistry +from typing import Any, Optional + +class BaseFactory: + """工厂基类,提供统一的组件创建方法""" + + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + """ + 统一创建组件的方法 + + Args: + category: 组件类别(如'boundary', 'flux'等) + name: 组件名称(如'periodic', 'rusanov'等) + *args, **kwargs: 传递给构造函数的参数 + + Returns: + 创建的组件实例 + + Raises: + ValueError: 如果组件不存在 + """ + name_lower = name.lower() + + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + # 获取可用组件列表 + available = ComponentRegistry.list_all().get(category, []) + + # 构建友好的错误信息 + if available: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"可用类型:{available}") + else: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"({category}类别下没有注册任何组件)") + + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + """获取指定类别的可用组件列表""" + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/python/flux.py b/example/1d-linear-convection/weno3/julia/01e/python/flux.py new file mode 100644 index 000000000..5ac73aa8d --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/python/flux.py @@ -0,0 +1,74 @@ +# flux.py +""" +通量计算器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册,替代硬编码工厂 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象通量计算基类(统一接口) ---------------------- +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass + +# ---------------------- 2. 具体通量计算子类(使用装饰器注册) ---------------------- + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + +class FluxCalculatorFactory: + """通量计算器工厂""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD对象 + + Returns: + 通量计算器实例 + """ + from factories.base_factory import BaseFactory + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/python/gen_boundary_test_data.py b/example/1d-linear-convection/weno3/julia/01e/python/gen_boundary_test_data.py new file mode 100644 index 000000000..c7fc2a9c5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/python/gen_boundary_test_data.py @@ -0,0 +1,35 @@ +# python/gen_boundary_test_data.py +import numpy as np +from config import CfdConfig +from mesh import Mesh +from domain import Domain + +# 固定测试配置 +config = CfdConfig() +config.with_boundary("dirichlet", left_value=0.5, right_value=1.5) +config.debug = True + +mesh = Mesh() +domain = Domain(config, mesh) + +# 构造 mock CFD 对象(仅含 config + domain) +class MockCfd: + def __init__(self, config, domain): + self.config = config + self.domain = domain + +# 测试用 u:0,1,2,...,N-1 +u_input = np.arange(domain.ntcells, dtype=np.float64) +np.save("u_input.npy", u_input) + +# 测试每种边界 +from boundary import PeriodicBoundary, DirichletBoundary, NeumannBoundary + +for bc_name, bc_class in [("periodic", PeriodicBoundary), ("dirichlet", DirichletBoundary), ("neumann", NeumannBoundary)]: + u = u_input.copy() + cfd_mock = MockCfd(config, domain) + bc = bc_class(cfd_mock) + bc.apply(u) + np.save(f"u_{bc_name}_py.npy", u) + +print("✅ 测试数据已生成:u_*.npy") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/python/gen_ic_test_data.py b/example/1d-linear-convection/weno3/julia/01e/python/gen_ic_test_data.py new file mode 100644 index 000000000..69c2573b8 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/python/gen_ic_test_data.py @@ -0,0 +1,27 @@ +# python/gen_ic_test_data.py +import sys, os +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +import numpy as np +from config import CfdConfig +from mesh import Mesh +from domain import Domain +from solution import Solution + +# 固定 mesh +mesh = Mesh() +config = CfdConfig() + +# 测试三种 IC +for ic_type in ["step", "sin", "gaussian"]: + config.ic_type = ic_type + domain = Domain(config, mesh) + sol = Solution(config, domain) + + u_full = sol.u.copy() # 包含 ghost + u_interior = sol.u[domain.ist:domain.ied].copy() + + np.save(f"u_{ic_type}_full_py.npy", u_full) + np.save(f"u_{ic_type}_interior_py.npy", u_interior) + +print("✅ 初始条件测试数据已生成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/python/initial_condition.py b/example/1d-linear-convection/weno3/julia/01e/python/initial_condition.py new file mode 100644 index 000000000..047415b74 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/python/initial_condition.py @@ -0,0 +1,90 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, ic_type: str, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from factories.base_factory import BaseFactory + return BaseFactory.create_component('initial_condition', ic_type, config) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/python/mesh.py b/example/1d-linear-convection/weno3/julia/01e/python/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/python/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/python/plotter.py b/example/1d-linear-convection/weno3/julia/01e/python/plotter.py new file mode 100644 index 000000000..9f1a414fd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/python/plotter.py @@ -0,0 +1,107 @@ +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/python/reconstructor/__init__.py b/example/1d-linear-convection/weno3/julia/01e/python/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/python/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/python/reconstructor/base.py b/example/1d-linear-convection/weno3/julia/01e/python/reconstructor/base.py new file mode 100644 index 000000000..bbd638503 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/python/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def reconstruct(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/python/reconstructor/eno.py b/example/1d-linear-convection/weno3/julia/01e/python/reconstructor/eno.py new file mode 100644 index 000000000..c2fb385dd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/python/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def reconstruct(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/python/reconstructor/factory.py b/example/1d-linear-convection/weno3/julia/01e/python/reconstructor/factory.py new file mode 100644 index 000000000..efa4a1446 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/python/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from cfd_registry import ComponentRegistry + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from factories.base_factory import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/python/reconstructor/weno3.py b/example/1d-linear-convection/weno3/julia/01e/python/reconstructor/weno3.py new file mode 100644 index 000000000..6e8c3f230 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/python/reconstructor/weno3.py @@ -0,0 +1,88 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def reconstruct(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + """ + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 # smoothness indicator for stencil [v2, v3] + beta1 = (v2 - v1)**2 # smoothness indicator for stencil [v1, v2] + + d0, d1 = 2/3, 1/3 # optimal linear weights (for right value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 0.5 * v2 + 0.5 * v3 # reconstruction from [v2, v3] + q1 = -0.5 * v1 + 1.5 * v2 # reconstruction from [v1, v2] + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 + beta1 = (v2 - v1)**2 + + d0, d1 = 1/3, 2/3 # optimal linear weights (for left value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 1.5 * v2 - 0.5 * v3 # from [v2, v3] + q1 = 0.5 * v1 + 0.5 * v2 # from [v1, v2] + return w0 * q0 + w1 * q1 + """ diff --git a/example/1d-linear-convection/weno3/julia/01e/python/registry.py b/example/1d-linear-convection/weno3/julia/01e/python/registry.py new file mode 100644 index 000000000..61be90500 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/python/registry.py @@ -0,0 +1,75 @@ +# registry.py (改进版) +""" +CFD组件注册系统 - 简洁高效版 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表""" + + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True # 控制是否打印注册信息 + + @classmethod + def set_verbose(cls, verbose: bool): + """设置是否打印注册信息""" + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + # 检查是否已注册 + if name in cls._registries[category]: + if cls._registries[category][name] == component_class: + # 相同类重复注册,静默跳过 + return + elif cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取组件类""" + if category not in cls._registries: + available = list(cls._registries.keys()) + raise ValueError(f"❌ 未知类别: {category} (可用类别: {available})") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + @classmethod + def get_count(cls) -> int: + """获取注册组件总数""" + return sum(len(components) for components in cls._registries.values()) + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/python/residual.py b/example/1d-linear-convection/weno3/julia/01e/python/residual.py new file mode 100644 index 000000000..b4d4d7dcc --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/python/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux import FluxCalculatorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + self.reconstructor = self.cfd.reconstructor + + # 初始化通量计算器(工厂创建) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._reconstruct() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _reconstruct(self): + """私有方法:界面值重建""" + self.reconstructor.reconstruct(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/python/run_eno_weno.py b/example/1d-linear-convection/weno3/julia/01e/python/run_eno_weno.py new file mode 100644 index 000000000..fd7f38744 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/python/run_eno_weno.py @@ -0,0 +1,52 @@ +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + #mesh = Mesh(ncells=100, L=2.0) + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行ENO3求解(使用你的链式调用) + print("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + config_eno3.with_reconstruction("eno", 3) # 显式指定3阶(也可省略,ENO默认3阶) + # 可选:覆盖默认值(如dt) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + cfd_eno3.run() # 求解并生成result字典 + + # 可选:快速验证ENO3结果 + # plotter.plot_quick(cfd_eno3.result, title="ENO3 Quick Check") + + # 3. 配置并运行WENO3求解(注意:WENO默认5阶,这里显式指定3阶) + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) # 显式指定3阶(默认是5阶) + # 可选:覆盖默认值 + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + cfd_weno3.run() + + # 4. 可选:保存结果(供离线绘图) + # cfd_eno3.save_result("eno3_result.npz") + # cfd_weno3.save_result("weno3_result.npz") + + # 5. 绘制ENO/WENO对比图 + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" # 可选:保存图片 + ) + +if __name__ == "__main__": + # 主程序入口 + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/python/solution.py b/example/1d-linear-convection/weno3/julia/01e/python/solution.py new file mode 100644 index 000000000..92a46d3f4 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/python/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config.ic_type, config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/python/solver.py b/example/1d-linear-convection/weno3/julia/01e/python/solver.py new file mode 100644 index 000000000..0d0b442da --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/python/solver.py @@ -0,0 +1,86 @@ +import numpy as np +import matplotlib.pyplot as plt +import inflect +from abc import ABC, abstractmethod + + +from flux import InviscidFluxCalculator, RusanovFluxCalculator, EngquistOsherFluxCalculator, FluxCalculatorFactory + +from boundary import BoundaryCondition, PeriodicBoundary, DirichletBoundary, NeumannBoundary, BoundaryConditionFactory + +from time_integration import TimeIntegrator, RK1Integrator, RK2Integrator, RK3Integrator, TimeIntegratorFactory + +from mesh import Mesh + +from reconstructor import ReconstructorFactory + +from initial_condition import InitialCondition, StepFunctionIC, SineWaveIC, GaussianPulseIC, InitialConditionFactory + +from domain import Domain +from solution import Solution + +from config import CfdConfig +from residual import ResidualCalculator + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, config, mesh): + self.config = config + self.domain = Domain(config, mesh) + self.solution = Solution(config, self.domain) + self.reconstructor = ReconstructorFactory.create(config, self.domain) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + def exact_solution(self): + """通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界""" + x = self.domain.mesh.xcc + T = self.config.final_time + c = self.config.wave_speed + L = self.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = InitialConditionFactory.create(self.config.ic_type, self.config) + return ic.evaluate_at(x_shifted) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + #runge_kutta(self) + self.integrator.step(dt) + t += dt + config.dt = dt_old + + # 整理标准化结果 + u_numerical = self.solution.u[self.domain.ist:self.domain.ied].copy() + self.result = { + "x": domain.mesh.xcc, + "numerical": u_numerical, + "analytical": self.exact_solution(), + "config": { + "scheme": self.config.recon_scheme, + "order": self.config.spatial_order, + "rk_order": self.config.rk_order, + "final_time": self.config.final_time + } + } + + return u_numerical diff --git a/example/1d-linear-convection/weno3/julia/01e/python/time_integration.py b/example/1d-linear-convection/weno3/julia/01e/python/time_integration.py new file mode 100644 index 000000000..25ac0b4ca --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01e/python/time_integration.py @@ -0,0 +1,125 @@ +# time_integration.py + +""" +时间推进器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = cfd.residual_calculator + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(使用装饰器注册) ---------------------- + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class TimeIntegratorFactory: + """时间推进器工厂""" + + @staticmethod + def create(cfd) -> 'TimeIntegrator': + """ + 创建时间推进器实例 + + Args: + cfd: CFD对象 + + Returns: + 时间推进器实例 + """ + from factories.base_factory import BaseFactory + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01e/python/u_dirichlet_py.npy b/example/1d-linear-convection/weno3/julia/01e/python/u_dirichlet_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..3aa20bd2e395654a6e8c59bbbc9f5eaa955b345e GIT binary patch literal 480 zcmbWzxeftg6o%oW6O}@tQ2Y_`BNMTVeHmMHM59p2#LPq?$c$)2;vU?f%TQY8ZM07F zov8Zw0H1NzP(=`e8>IlQ+pS*SsNB7-1(=_si zx~l&#Cf|Elzsex&D8?~?Nlc-_G-fc1IV_-oMJ!<%D_BJnYgoqyHnD|m>|hsr*hdQo Wp*NHvjWVK9#x%-=MtSnjr}YA`88kxx literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01e/python/u_gaussian_full_py.npy b/example/1d-linear-convection/weno3/julia/01e/python/u_gaussian_full_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..b762f0fe295b52a9a176817021995ce5a561d778 GIT binary patch literal 480 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yCOVor3bhL411<(MU?Ou)P3Oi_+soE}ylQv)*=cg#Ue98-%FeWXxBn~o z-*&0B&Pq*ej`k{D0z3w9EA6|!ORWz4wA}u9Vfdl$E2r%H!VVg&w0>m2nCbZM)%@?^ zbQ@G0rY;|Sof!j>qBKc_8=pP9zb3SzgEt8=WEn__NZ z$*8-a{Z)*4!nvvF3 wqbFAl+ziF%o9|Zhkuvbx>B0B<*(1H@$~rUDs~6~AeE<2_q`W{KwBTd_0INu`7ytkO literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01e/python/u_gaussian_interior_py.npy b/example/1d-linear-convection/weno3/julia/01e/python/u_gaussian_interior_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..68af8954e7a17e69b6b2e6954b78ee542d4e96a0 GIT binary patch literal 448 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#y20EHL3bhL411=Mpb80#_p4wiv{^M1<)6Y(m^Y(fcvsHGc?YsS7$^W)X zt#wvvVso@t=@Q^Ecw1@T^<8Rp;HTyG#|y&`bzeDU-xqe!V5RjV`^8Mhf3N0$2dCSh z;xKjjP<`JzA?Co${oe+$2WBtK9e1T6?wYnbA&=>Lk?qOJ*(aWE{cN*=zyHlG=1iNl zN(soJS7TeOgcr6%N&Go&S^Ug2epV2>rCgn3z1$RY3rj}b z1?{h5%p2Bhd1c#FYWC{z!qS+GX48p_*$(R2wVEWXJEgqJq{jHxHq-S&9M{! literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01e/python/u_input.npy b/example/1d-linear-convection/weno3/julia/01e/python/u_input.npy new file mode 100644 index 0000000000000000000000000000000000000000..ef506fa75cacbbe63dfcc17332ddd6c33de5b888 GIT binary patch literal 480 zcmbWrI}gE70EXdX6O+MUFr0`uNFy$FzqBs35sSejjUF+GN-GwT_z(W5pP?kM2cBT{2ChY{-ZF ze<|^^w?*2qNlaq~vzSAPc`RTNOISezt60N2Hn52%wy=#I>|zi5IKUx}aEun(u}>5e R8pV`GF{4q;X%r6a*dIEnGEe{j literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01e/python/u_neumann_py.npy b/example/1d-linear-convection/weno3/julia/01e/python/u_neumann_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..fa723d1efce906983b6e6710379207d15afbc271 GIT binary patch literal 480 zcmbWrI}gE70EXdX6O+MUFr0`uC5^ZgRkymtMl1%CG}^`>Dy>*V;y?JqN?(i7w>|hs7?4gBy9N-W~I7U11fnvf( PG3BF}@lnkAKgRt9WDhZ? literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01e/python/u_periodic_py.npy b/example/1d-linear-convection/weno3/julia/01e/python/u_periodic_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..156aff85dfa4c84deaaed404e0dccc37937f8836 GIT binary patch literal 480 zcmbWzyAA+Jw5Jw7YF-Tj}jFJcs^aeGCKh-lzUPGOs<_`6u&g%W>?2t#0c7 zOUUOY-iJGid|Tp}#1y76g9@{l!#rA8L>o(3#tK%kh7Q)TflX{-8#~xV7kk*p0eWBW QC_@@$M5Bypl;8h*0k_X5-%Mo@PtK#WJA?o%og6P}-0bsJ$1U?l6M7O94XfzYY=Kp9oR6e-T9A{tpmy_6tDF-R}f( z-+mW}`}RZKw;$@h{Tm?e+z)l%epjeEd#FAks5zgY=FW!NlLxi;2-F?cP8 literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01f/julia/boundary.jl b/example/1d-linear-convection/weno3/julia/01f/julia/boundary.jl new file mode 100644 index 000000000..5b0baaf43 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/julia/boundary.jl @@ -0,0 +1,90 @@ +# julia/boundary.jl +# 目标:与 Python boundary.py 行为完全一致(1:1 同构) +# 前提:ist/ied 在 Julia 中按 1-based 设置(ist = nghosts + 1) + +using NPZ # 仅用于测试,实际模块可不依赖 + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象基类(Julia 风格:用函数接口) ---------------------- +# Julia 无 abstract class,用文档+约定 +# 所有子类型必须实现 apply!(bc, u) + +# ---------------------- PeriodicBoundary ---------------------- +mutable struct PeriodicBoundary + cfd::Any +end + +function apply!(self::PeriodicBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist # 1-based, e.g., 3 + ied = self.cfd.domain.ied # 1-based, e.g., 43 + + # 左 ghost: u[ist - 1 - ig] = u[ied - 1 - ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ied - 1 - ig] + end + # 右 ghost: u[ied + ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ied + ig] = u[ist + ig] + end +end + +# ---------------------- DirichletBoundary ---------------------- +mutable struct DirichletBoundary + cfd::Any +end + +function apply!(self::DirichletBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + left_value = getfield_safe(self.cfd.config, :left_boundary_value, 1.0) + right_value = getfield_safe(self.cfd.config, :right_boundary_value, 2.0) + + # 左边界 + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = left_value + end + # 右边界 + for ig in 0:(nghosts-1) + u[ied + ig] = right_value + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Dirichlet边界: 左值=$left_value, 右值=$right_value") + end +end + +# ---------------------- NeumannBoundary ---------------------- +mutable struct NeumannBoundary + cfd::Any +end + +function apply!(self::NeumannBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + # 左边界零梯度:u[ist - 1 - ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ist + ig] + end + # 右边界零梯度:u[ied + ig] = u[ied - 1 - ig] ← 注意是 ied - 1 - ig + for ig in 0:(nghosts-1) + u[ied + ig] = u[ied - 1 - ig] + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Neumann边界: 零梯度") + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/julia/domain.jl b/example/1d-linear-convection/weno3/julia/01f/julia/domain.jl new file mode 100644 index 000000000..818be8fa6 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/julia/domain.jl @@ -0,0 +1,61 @@ +# julia/domain.jl +""" +Domain 结构体(与 domain.py 完全同构) +- 保存 config +- nghosts 计算逻辑 1:1 +- ist/ied 为 0-based(与 Python 一致) +""" + +include("mesh.jl") + +mutable struct Domain + config::Any # 保存 config(与 Python 一致) + mesh::Mesh + nghosts::Int + ist::Int # 0-based + ied::Int # 0-based + ntcells::Int +end + +function Domain(config::Any, mesh::Mesh) + nghosts = _calc_nghosts(config) + ist = nghosts + ied = ist + mesh.ncells + ntcells = mesh.ncells + 2 * nghosts + Domain(config, mesh, nghosts, ist, ied, ntcells) +end + +function _calc_nghosts(config::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + order = getfield_safe(config, :spatial_order, 2) + + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + if scheme == "eno" + nghosts = order + elseif startswith(scheme, "weno") + nghosts = order ÷ 2 + 1 + else + error("未知重建格式 $(scheme),无法计算ghost层!") + end + + if nghosts <= 0 + error("计算得到的ghost层数量无效:$(nghosts)(阶数$(order),格式$(scheme))") + end + + return nghosts +end + +function is_physical_cell(domain::Domain, idx::Int) + return domain.ist <= idx < domain.ied +end + +function get_physical_indices(domain::Domain) + return domain.ist:(domain.ied - 1) +end + +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/julia/flux.jl b/example/1d-linear-convection/weno3/julia/01f/julia/flux.jl new file mode 100644 index 000000000..047598f7a --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/julia/flux.jl @@ -0,0 +1,70 @@ +# julia/flux.jl +""" +通量计算器模块(与 flux.py 完全同构) +- 抽象基类 + 具体实现 +- 字段:cfd, config, mesh, wave_speed +""" + +include("mesh.jl") + +# ---------------------- 抽象基类 ---------------------- +""" +InviscidFluxCalculator 抽象类型 +Julia 无 ABC,用文档约定 +所有子类型必须实现 compute! +""" +abstract type InviscidFluxCalculator end + +# ---------------------- RusanovFluxCalculator ---------------------- +mutable struct RusanovFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function RusanovFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::RusanovFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = calc.wave_speed + c_R = calc.wave_speed + F_L = c_L * u_L + F_R = c_R * u_R + Smax = max(abs(c_L), abs(c_R)) + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + end +end + +# ---------------------- EngquistOsherFluxCalculator ---------------------- +mutable struct EngquistOsherFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function EngquistOsherFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::EngquistOsherFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + c = calc.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/julia/initial_condition.jl b/example/1d-linear-convection/weno3/julia/01f/julia/initial_condition.jl new file mode 100644 index 000000000..5e029e8d2 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/julia/initial_condition.jl @@ -0,0 +1,86 @@ +# julia/initial_condition.jl +""" +初始条件模块(Julia 版) +目标:与 Python initial_condition.py 行为完全一致 +""" + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象接口(Julia 无继承,用函数约定) ---------------------- +# 所有初始条件必须实现: +# evaluate_at(ic, x::AbstractVector{Float64}) -> Vector{Float64} +# apply(ic, solution) + +# ---------------------- StepFunctionIC ---------------------- +struct StepFunctionIC + config::Any +end + +function evaluate_at(ic::StepFunctionIC, x::AbstractVector{Float64}) + u0 = ones(Float64, length(x)) + for i in eachindex(x) + if 0.5 <= x[i] <= 1.0 + u0[i] = 2.0 + end + end + return u0 +end + +function apply(ic::StepFunctionIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- SineWaveIC ---------------------- +struct SineWaveIC + config::Any +end + +function evaluate_at(ic::SineWaveIC, x::AbstractVector{Float64}) + L = getfield_safe(ic.config, :domain_length, 2.0) + return sin.(2π * x / L) +end + +function apply(ic::SineWaveIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- GaussianPulseIC ---------------------- +struct GaussianPulseIC + config::Any +end + +function evaluate_at(ic::GaussianPulseIC, x::AbstractVector{Float64}) + center = getfield_safe(ic.config, :pulse_center, 0.5) + width = getfield_safe(ic.config, :pulse_width, 0.1) + return exp.(-((x .- center) ./ width).^2) +end + +function apply(ic::GaussianPulseIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- 公共辅助函数 ---------------------- +""" +将初始场 values 应用到 solution.u 的物理区域(含 ghost 的数组) +""" +function _apply_to_interior(solution, values) + domain = solution.domain + # Python: for i in range(domain.ist, domain.ied) + # Julia: for i in domain.ist:domain.ied-1 (因为 ied 是结束索引,不包含) + for i in domain.ist:(domain.ied - 1) + j = i - domain.ist + 1 # values 是 1-based,i - ist 是 0-based → +1 + solution.u[i] = values[j] + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/julia/mesh.jl b/example/1d-linear-convection/weno3/julia/01f/julia/mesh.jl new file mode 100644 index 000000000..1b0dd4bf9 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/julia/mesh.jl @@ -0,0 +1,45 @@ +# julia/mesh.jl +""" +Mesh 结构体(与提供的 mesh.py 完全同构) +- 字段名、初始化顺序、循环逻辑完全一致 +- 不做任何泛化或优化 +""" + +mutable struct Mesh + xmin::Float64 + xmax::Float64 + ncells::Int + nnodes::Int + nx::Int + x::Vector{Float64} + xcc::Vector{Float64} + L::Float64 # 在 init_mesh 中赋值 + dx::Float64 # 在 init_mesh 中赋值 + + function Mesh() + # 与 Python __init__ 完全一致 + xmin = 0.0 + xmax = 2.0 + ncells = 40 + nnodes = ncells + 1 + nx = ncells + x = zeros(Float64, nnodes) + xcc = zeros(Float64, ncells) + + # 调用 init_mesh(模拟 Python) + L = xmax - xmin + dx = L / ncells + + # Generate node coordinates: for i in range(self.nnodes) + for i in 1:nnodes + x[i] = xmin + (i - 1) * dx # Python i → Julia i-1 + end + + # Generate cell center coordinates + for i in 1:ncells + xcc[i] = 0.5 * (x[i] + x[i+1]) + end + + new(xmin, xmax, ncells, nnodes, nx, x, xcc, L, dx) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/julia/residual.jl b/example/1d-linear-convection/weno3/julia/01f/julia/residual.jl new file mode 100644 index 000000000..15cc93878 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/julia/residual.jl @@ -0,0 +1,65 @@ +# julia/residual.jl +""" +残差计算器(与 residual.py 完全同构) +- 封装重建→通量→散度完整流程 +- 依赖 cfd 的多个字段 +""" + +include("mesh.jl") + +mutable struct ResidualCalculator + cfd::Any + config::Any + domain::Any + solution::Any + mesh::Mesh + reconstructor::Any + flux_calculator::Any # 通量计算器(外部传入,替代工厂) + + function ResidualCalculator(cfd::Any, flux_calculator::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + mesh = domain.mesh + reconstructor = cfd.reconstructor + + new(cfd, config, domain, solution, mesh, reconstructor, flux_calculator) + end +end + +""" +计算完整残差(对外唯一接口) +""" +function compute!(calc::ResidualCalculator) + #_reconstruct(calc) + _compute_inviscid_flux(calc) + _compute_flux_divergence(calc) +end + +""" +私有方法:界面值重建 +""" +function _reconstruct(calc::ResidualCalculator) + reconstruct(calc.reconstructor, calc.solution.u, calc.cfd) +end + +""" +私有方法:计算无粘通量 +""" +function _compute_inviscid_flux(calc::ResidualCalculator) + compute!(calc.flux_calculator, + calc.solution.q_face_left, + calc.solution.q_face_right, + calc.solution.flux) +end + +""" +私有方法:计算通量散度(残差 = -dF/dx) +""" +function _compute_flux_divergence(calc::ResidualCalculator) + solution = calc.solution + mesh = calc.mesh + for i in 1:mesh.ncells + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / mesh.dx + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/julia/solution.jl b/example/1d-linear-convection/weno3/julia/01f/julia/solution.jl new file mode 100644 index 000000000..90ca03931 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/julia/solution.jl @@ -0,0 +1,75 @@ +# julia/solution.jl +""" +Solution 结构体(与真实 solution.py 完全同构) +字段顺序、方法、逻辑 1:1 对齐 +""" + +include("mesh.jl") +include("domain.jl") +include("initial_condition.jl") + +mutable struct Solution + domain::Domain + q_face_left::Vector{Float64} + q_face_right::Vector{Float64} + flux::Vector{Float64} + res::Vector{Float64} + u::Vector{Float64} + un::Vector{Float64} + + function Solution(config::Any, domain::Domain) + mesh = domain.mesh + + q_face_left = zeros(Float64, mesh.nnodes) + q_face_right = zeros(Float64, mesh.nnodes) + flux = zeros(Float64, mesh.nnodes) + res = zeros(Float64, mesh.ncells) + u = zeros(Float64, domain.ntcells) + un = zeros(Float64, domain.ntcells) + + sol = new(domain, q_face_left, q_face_right, flux, res, u, un) + initialize_from_config(sol, config) + return sol + end +end + +""" +重置解数组为初始状态 +""" +function reset_solution(sol::Solution) + fill!(sol.u, 0.0) + fill!(sol.un, 0.0) +end + +""" +根据配置初始化场 +注:暂用硬编码替代 InitialConditionFactory +""" +function initialize_from_config(sol::Solution, config::Any) + ic_type = getfield_safe(config, :ic_type, "step") + + # 硬编码创建 IC(替代 InitialConditionFactory) + ic = if ic_type == "step" + StepFunctionIC(config) + elseif ic_type == "sin" + SineWaveIC(config) + elseif ic_type == "gaussian" + GaussianPulseIC(config) + else + error("未知初始条件类型: $ic_type") + end + + apply(ic, sol) # 调用 IC.apply +end + +""" +更新旧场:un = u +""" +function update_old_field(sol::Solution) + sol.un .= sol.u # 等价于 Python 的 un[:] = u[:] +end + +# ---------------------- 辅助函数 ---------------------- +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/julia/test/test_boundary.jl b/example/1d-linear-convection/weno3/julia/01f/julia/test/test_boundary.jl new file mode 100644 index 000000000..ef27d6820 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/julia/test/test_boundary.jl @@ -0,0 +1,57 @@ +# julia/test/test_boundary.jl +using NPZ +include("../boundary.jl") + +struct MockConfig + boundary_type::String + left_boundary_value::Float64 + right_boundary_value::Float64 + debug::Bool +end + +struct MockDomain + nghosts::Int + ist::Int # Julia 本地索引(1-based) + ied::Int + ntcells::Int +end + +struct MockCfd + config::MockConfig + domain::MockDomain +end + +# ===== 关键:使用 Julia 本地索引规则 ===== +nghosts = 2 +ncells = 40 +ist = nghosts + 1 # = 3 +ied = ist + ncells # = 43 +ntcells = ncells + 2 * nghosts # = 44 + +config = MockConfig("dirichlet", 0.5, 1.5, true) +domain = MockDomain(nghosts, ist, ied, ntcells) +cfd_mock = MockCfd(config, domain) + +# 加载 Python 生成的 u_input.npy +# 注意:Python u[0] → Julia u[1],所以内容完全对应 +u_input = npzread("../../python/u_input.npy") +@assert length(u_input) == ntcells "数组长度不匹配!" + +# 测试三种边界 +test_cases = [ + ("periodic", PeriodicBoundary(cfd_mock)), + ("dirichlet", DirichletBoundary(cfd_mock)), + ("neumann", NeumannBoundary(cfd_mock)) +] + +for (name, bc) in test_cases + u = copy(u_input) + apply!(bc, u) + + u_py = npzread("../../python/u_$(name)_py.npy") + err = maximum(abs.(u .- u_py)) + println("边界: $(name) | 最大误差: $(err)") + @assert err < 1e-12 "❌ $(name) 不一致!" +end + +println("✅ 所有边界条件测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/julia/test/test_domain.jl b/example/1d-linear-convection/weno3/julia/01f/julia/test/test_domain.jl new file mode 100644 index 000000000..7b423f774 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/julia/test/test_domain.jl @@ -0,0 +1,44 @@ +# julia/test/test_domain.jl +include("../mesh.jl") +include("../domain.jl") + +# MockConfig:模拟 Python CfdConfig +struct MockConfig + recon_scheme::String + spatial_order::Int +end + +# 测试 ENO +config_eno = MockConfig("eno", 2) +mesh = Mesh() +domain_eno = Domain(config_eno, mesh) + +println("ENO: nghosts = ", domain_eno.nghosts) # 2 +println("ENO: ist = ", domain_eno.ist) # 2 +println("ENO: ied = ", domain_eno.ied) # 42 +println("物理索引范围: ", collect(get_physical_indices(domain_eno))[1:3], " ... ", collect(get_physical_indices(domain_eno))[end-2:end]) + +# 测试 WENO(字符串 "weno") +config_weno = MockConfig("weno", 2) +domain_weno = Domain(config_weno, mesh) +println("WENO: nghosts = ", domain_weno.nghosts) # 2 + +# 测试 WENO3(字符串 "weno3") +config_weno3 = MockConfig("weno3", 2) +domain_weno3 = Domain(config_weno3, mesh) +println("WENO3: nghosts = ", domain_weno3.nghosts) # 2 + +# 测试 is_physical_cell +@assert is_physical_cell(domain_eno, 2) == true # ist=2 +@assert is_physical_cell(domain_eno, 41) == true # ied-1=41 +@assert is_physical_cell(domain_eno, 42) == false # ied=42 + +# ✅ 断言 +@assert domain_eno.nghosts == 2 +@assert domain_eno.ist == 2 +@assert domain_eno.ied == 42 +@assert domain_eno.ntcells == 44 +@assert domain_weno.nghosts == 2 +@assert domain_weno3.nghosts == 2 + +println("✅ Domain 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/julia/test/test_flux.jl b/example/1d-linear-convection/weno3/julia/01f/julia/test/test_flux.jl new file mode 100644 index 000000000..0ebc1dc05 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/julia/test/test_flux.jl @@ -0,0 +1,53 @@ +# julia/test/test_flux.jl +include("../mesh.jl") +include("../flux.jl") + +# Mock 对象 +struct MockConfig + flux_type::String + wave_speed::Float64 +end + +struct MockDomain + mesh::Mesh +end + +struct MockCfd + config::MockConfig + domain::MockDomain +end + +# 创建 mock +mesh = Mesh() +config = MockConfig("rusanov", 1.0) +domain = MockDomain(mesh) +cfd = MockCfd(config, domain) + +# 测试 Rusanov +rusanov = RusanovFluxCalculator(cfd) +N = mesh.nnodes +qL = [1.0, 2.0, 3.0, 4.0, 5.0, zeros(Float64, N-5)...] +qR = [2.0, 3.0, 4.0, 5.0, 6.0, zeros(Float64, N-5)...] +flux_rus = zeros(Float64, N) +compute!(rusanov, qL, qR, flux_rus) + +println("Rusanov flux[1] = ", flux_rus[1]) # 应为 1.0 +@assert abs(flux_rus[1] - 1.0) < 1e-12 + +# 测试 Engquist-Osher +eo = EngquistOsherFluxCalculator(cfd) +flux_eo = zeros(Float64, N) +compute!(eo, qL, qR, flux_eo) + +println("EO flux[1] = ", flux_eo[1]) # c=1 → cp=1, cm=0 → flux=1*1 + 0*2 = 1.0 +@assert abs(flux_eo[1] - 1.0) < 1e-12 + +# 测试 c = -1.0 +config_neg = MockConfig("rusanov", -1.0) +cfd_neg = MockCfd(config_neg, domain) +rusanov_neg = RusanovFluxCalculator(cfd_neg) +compute!(rusanov_neg, qL, qR, flux_rus) +println("Rusanov (c=-1) flux[1] = ", flux_rus[1]) # F_L=-1, F_R=-2, Smax=1 → flux = -1.5 -0.5*(-1) = -1.0 +@assert abs(flux_rus[1] - (-2.0)) < 1e-12 # ← 修正:-2.0 而非 -1.0 + +println("✅ Flux 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/julia/test/test_initial_condition.jl b/example/1d-linear-convection/weno3/julia/01f/julia/test/test_initial_condition.jl new file mode 100644 index 000000000..dc58691a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/julia/test/test_initial_condition.jl @@ -0,0 +1,45 @@ +# julia/test/test_initial_condition.jl +using NPZ + +# 包含初始条件模块 +include("../initial_condition.jl") + +# 构造 mock config(与 Python 完全一致) +struct MockConfig + ic_type::String + domain_length::Float64 + pulse_center::Float64 + pulse_width::Float64 +end + +# 生成与 Python Mesh.xcc 完全相同的 x 坐标 +function generate_xcc() + xmin, xmax = 0.0, 2.0 + ncells = 40 + dx = (xmax - xmin) / ncells + xcc = Vector{Float64}(undef, ncells) + for i in 1:ncells + xcc[i] = xmin + (i - 0.5) * dx # i-1 + 0.5 → i-0.5 + end + return xcc +end + +# 主测试 +xcc = generate_xcc() + +test_cases = [ + ("step", StepFunctionIC(MockConfig("step", 2.0, 0.5, 0.1))), + ("sin", SineWaveIC(MockConfig("sin", 2.0, 0.5, 0.1))), + ("gaussian", GaussianPulseIC(MockConfig("gaussian", 2.0, 0.5, 0.1))) +] + +for (name, ic) in test_cases + u_jl = evaluate_at(ic, xcc) + u_py = npzread("../../python/u_$(name)_interior_py.npy") + + err = maximum(abs.(u_jl .- u_py)) + println("IC: $(name) | 最大误差: $(err)") + @assert err < 1e-12 "❌ $(name) 不一致!" +end + +println("✅ 所有初始条件测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/julia/test/test_mesh.jl b/example/1d-linear-convection/weno3/julia/01f/julia/test/test_mesh.jl new file mode 100644 index 000000000..315d2aac2 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/julia/test/test_mesh.jl @@ -0,0 +1,37 @@ +# julia/test/test_mesh.jl +include("../mesh.jl") + +# 创建 mesh(与 Python 完全相同) +mesh = Mesh() + +# 打印关键值(与 Python 对比) +println("xmin = ", mesh.xmin) # 0.0 +println("xmax = ", mesh.xmax) # 2.0 +println("ncells = ", mesh.ncells) # 40 +println("nnodes = ", mesh.nnodes) # 41 +println("nx = ", mesh.nx) # 40 +println("L = ", mesh.L) # 2.0 +println("dx = ", mesh.dx) # 0.05 + +# 检查 x[1] (Python x[0]) 和 x[41] (Python x[40]) +println("x[1] = ", mesh.x[1]) # 0.0 +println("x[41] = ", mesh.x[41]) # 2.0 + +# 检查 xcc[1] (Python xcc[0]) 和 xcc[40] (Python xcc[39]) +println("xcc[1] = ", mesh.xcc[1]) # 0.025 +println("xcc[40] = ", mesh.xcc[40]) # 1.975 + +# ✅ 严格断言 +@assert mesh.xmin == 0.0 +@assert mesh.xmax == 2.0 +@assert mesh.ncells == 40 +@assert mesh.nnodes == 41 +@assert mesh.nx == 40 +@assert mesh.L == 2.0 +@assert mesh.dx == 0.05 +@assert mesh.x[1] == 0.0 +@assert mesh.x[41] == 2.0 +@assert abs(mesh.xcc[1] - 0.025) < 1e-12 +@assert abs(mesh.xcc[40] - 1.975) < 1e-12 + +println("✅ Mesh 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/julia/test/test_residual.jl b/example/1d-linear-convection/weno3/julia/01f/julia/test/test_residual.jl new file mode 100644 index 000000000..e37e8d2bd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/julia/test/test_residual.jl @@ -0,0 +1,53 @@ +# julia/test/test_residual.jl +include("../mesh.jl") +include("../domain.jl") +include("../solution.jl") +include("../flux.jl") +include("../residual.jl") + +# ===== Dummy Reconstructor ===== +struct DummyReconstructor end + +# ===== Mock Config ===== +struct MockConfig + flux_type::String + wave_speed::Float64 +end + +# ===== Mock CFD (必须包含 reconstructor 字段) ===== +struct MockCfd + config::MockConfig + domain::Domain + solution::Solution + reconstructor::DummyReconstructor # ← 关键:添加此字段 +end + +# ===== 主测试 ===== +config = MockConfig("rusanov", 1.0) +mesh = Mesh() +domain = Domain((recon_scheme="eno", spatial_order=2), mesh) +solution = Solution((ic_type="step",), domain) + +# 手动设置界面值(跳过重建) +for i in 1:mesh.nnodes + solution.q_face_left[i] = Float64(i) * 0.1 + solution.q_face_right[i] = Float64(i) * 0.1 +end + +flux_calc = RusanovFluxCalculator((config=config, domain=domain)) +dummy_recon = DummyReconstructor() +cfd = MockCfd(config, domain, solution, dummy_recon) + +# 创建残差计算器 +res_calc = ResidualCalculator(cfd, flux_calc) + +# 计算残差(注意:_reconstruct 仍被注释) +compute!(res_calc) + +println("flux[1] = ", solution.flux[1]) +println("res[1] = ", solution.res[1]) + +@assert abs(solution.flux[1] - 0.1) < 1e-12 +@assert abs(solution.res[1] - (-2.0)) < 1e-12 + +println("✅ Residual 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/julia/test/test_solution.jl b/example/1d-linear-convection/weno3/julia/01f/julia/test/test_solution.jl new file mode 100644 index 000000000..f44ff0f0e --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/julia/test/test_solution.jl @@ -0,0 +1,42 @@ +# julia/test/test_solution.jl +include("../mesh.jl") +include("../domain.jl") +include("../solution.jl") + +# MockConfig +struct MockConfig + recon_scheme::String + spatial_order::Int + ic_type::String + domain_length::Float64 + pulse_center::Float64 + pulse_width::Float64 +end + +# 创建 solution +config = MockConfig("eno", 2, "step", 2.0, 0.5, 0.1) +mesh = Mesh() +domain = Domain(config, mesh) +sol = Solution(config, domain) + +# 检查字段尺寸 +@assert length(sol.q_face_left) == mesh.nnodes # 41 +@assert length(sol.flux) == mesh.nnodes # 41 +@assert length(sol.res) == mesh.ncells # 40 +@assert length(sol.u) == domain.ntcells # 44 + +# 检查初始场 +println("u[3] (物理起始): ", sol.u[3]) # 应为 1.0 +println("u[23] (x=1.0): ", sol.u[23]) # 应为 2.0 + +# 测试 update_old_field +sol.u[3] = 999.0 +update_old_field(sol) +@assert sol.un[3] == 999.0 + +# 测试 reset_solution +reset_solution(sol) +@assert sol.u[3] == 0.0 +@assert sol.un[3] == 0.0 + +println("✅ Solution 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/julia/test_residual.jl b/example/1d-linear-convection/weno3/julia/01f/julia/test_residual.jl new file mode 100644 index 000000000..315d2aac2 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/julia/test_residual.jl @@ -0,0 +1,37 @@ +# julia/test/test_mesh.jl +include("../mesh.jl") + +# 创建 mesh(与 Python 完全相同) +mesh = Mesh() + +# 打印关键值(与 Python 对比) +println("xmin = ", mesh.xmin) # 0.0 +println("xmax = ", mesh.xmax) # 2.0 +println("ncells = ", mesh.ncells) # 40 +println("nnodes = ", mesh.nnodes) # 41 +println("nx = ", mesh.nx) # 40 +println("L = ", mesh.L) # 2.0 +println("dx = ", mesh.dx) # 0.05 + +# 检查 x[1] (Python x[0]) 和 x[41] (Python x[40]) +println("x[1] = ", mesh.x[1]) # 0.0 +println("x[41] = ", mesh.x[41]) # 2.0 + +# 检查 xcc[1] (Python xcc[0]) 和 xcc[40] (Python xcc[39]) +println("xcc[1] = ", mesh.xcc[1]) # 0.025 +println("xcc[40] = ", mesh.xcc[40]) # 1.975 + +# ✅ 严格断言 +@assert mesh.xmin == 0.0 +@assert mesh.xmax == 2.0 +@assert mesh.ncells == 40 +@assert mesh.nnodes == 41 +@assert mesh.nx == 40 +@assert mesh.L == 2.0 +@assert mesh.dx == 0.05 +@assert mesh.x[1] == 0.0 +@assert mesh.x[41] == 2.0 +@assert abs(mesh.xcc[1] - 0.025) < 1e-12 +@assert abs(mesh.xcc[40] - 1.975) < 1e-12 + +println("✅ Mesh 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/python/boundary.py b/example/1d-linear-convection/weno3/julia/01f/python/boundary.py new file mode 100644 index 000000000..6054f92de --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/python/boundary.py @@ -0,0 +1,103 @@ +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from factories.base_factory import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/python/cfd_registry.py b/example/1d-linear-convection/weno3/julia/01f/python/cfd_registry.py new file mode 100644 index 000000000..c12eb106c --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/python/cfd_registry.py @@ -0,0 +1,62 @@ +# cfd_registry.py +""" +CFD注册系统统一导入模块 +所有其他模块从这里导入注册系统 +""" + +import sys +import os + +# 添加当前目录到路径,确保能找到registry.py +current_dir = os.path.dirname(os.path.abspath(__file__)) +if current_dir not in sys.path: + sys.path.insert(0, current_dir) + +try: + # 尝试导入注册系统 + from registry import ComponentRegistry, register_component + REGISTRY_AVAILABLE = True +except ImportError: + # 如果找不到,提供简单的空实现 + print("⚠️ 警告: 未找到 registry.py,使用最小化回退实现") + + class ComponentRegistry: + """最小化的注册系统回退实现""" + _registries = {} + + @classmethod + def register(cls, category, name, component_class): + if category not in cls._registries: + cls._registries[category] = {} + cls._registries[category][name] = component_class + + @classmethod + def get(cls, category, name): + if category not in cls._registries: + raise ValueError(f"未知类别: {category}") + if name not in cls._registries[category]: + raise ValueError(f"未找到: {category}.{name}") + return cls._registries[category][name] + + @classmethod + def create(cls, category, name, *args, **kwargs): + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) + for cat, comps in cls._registries.items()} + + def register_component(category, name=None): + """简化的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + REGISTRY_AVAILABLE = False + +# 导出统一的接口 +__all__ = ['ComponentRegistry', 'register_component', 'REGISTRY_AVAILABLE'] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/python/config.py b/example/1d-linear-convection/weno3/julia/01f/python/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/python/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/python/domain.py b/example/1d-linear-convection/weno3/julia/01f/python/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/python/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/python/factories/base_factory.py b/example/1d-linear-convection/weno3/julia/01f/python/factories/base_factory.py new file mode 100644 index 000000000..7b8b6105e --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/python/factories/base_factory.py @@ -0,0 +1,49 @@ +# factories/base_factory.py +""" +工厂基类 - 提供统一的组件创建接口 +""" + +from cfd_registry import ComponentRegistry +from typing import Any, Optional + +class BaseFactory: + """工厂基类,提供统一的组件创建方法""" + + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + """ + 统一创建组件的方法 + + Args: + category: 组件类别(如'boundary', 'flux'等) + name: 组件名称(如'periodic', 'rusanov'等) + *args, **kwargs: 传递给构造函数的参数 + + Returns: + 创建的组件实例 + + Raises: + ValueError: 如果组件不存在 + """ + name_lower = name.lower() + + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + # 获取可用组件列表 + available = ComponentRegistry.list_all().get(category, []) + + # 构建友好的错误信息 + if available: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"可用类型:{available}") + else: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"({category}类别下没有注册任何组件)") + + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + """获取指定类别的可用组件列表""" + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/python/flux.py b/example/1d-linear-convection/weno3/julia/01f/python/flux.py new file mode 100644 index 000000000..5ac73aa8d --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/python/flux.py @@ -0,0 +1,74 @@ +# flux.py +""" +通量计算器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册,替代硬编码工厂 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象通量计算基类(统一接口) ---------------------- +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass + +# ---------------------- 2. 具体通量计算子类(使用装饰器注册) ---------------------- + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + +class FluxCalculatorFactory: + """通量计算器工厂""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD对象 + + Returns: + 通量计算器实例 + """ + from factories.base_factory import BaseFactory + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/python/gen_boundary_test_data.py b/example/1d-linear-convection/weno3/julia/01f/python/gen_boundary_test_data.py new file mode 100644 index 000000000..c7fc2a9c5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/python/gen_boundary_test_data.py @@ -0,0 +1,35 @@ +# python/gen_boundary_test_data.py +import numpy as np +from config import CfdConfig +from mesh import Mesh +from domain import Domain + +# 固定测试配置 +config = CfdConfig() +config.with_boundary("dirichlet", left_value=0.5, right_value=1.5) +config.debug = True + +mesh = Mesh() +domain = Domain(config, mesh) + +# 构造 mock CFD 对象(仅含 config + domain) +class MockCfd: + def __init__(self, config, domain): + self.config = config + self.domain = domain + +# 测试用 u:0,1,2,...,N-1 +u_input = np.arange(domain.ntcells, dtype=np.float64) +np.save("u_input.npy", u_input) + +# 测试每种边界 +from boundary import PeriodicBoundary, DirichletBoundary, NeumannBoundary + +for bc_name, bc_class in [("periodic", PeriodicBoundary), ("dirichlet", DirichletBoundary), ("neumann", NeumannBoundary)]: + u = u_input.copy() + cfd_mock = MockCfd(config, domain) + bc = bc_class(cfd_mock) + bc.apply(u) + np.save(f"u_{bc_name}_py.npy", u) + +print("✅ 测试数据已生成:u_*.npy") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/python/gen_ic_test_data.py b/example/1d-linear-convection/weno3/julia/01f/python/gen_ic_test_data.py new file mode 100644 index 000000000..69c2573b8 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/python/gen_ic_test_data.py @@ -0,0 +1,27 @@ +# python/gen_ic_test_data.py +import sys, os +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +import numpy as np +from config import CfdConfig +from mesh import Mesh +from domain import Domain +from solution import Solution + +# 固定 mesh +mesh = Mesh() +config = CfdConfig() + +# 测试三种 IC +for ic_type in ["step", "sin", "gaussian"]: + config.ic_type = ic_type + domain = Domain(config, mesh) + sol = Solution(config, domain) + + u_full = sol.u.copy() # 包含 ghost + u_interior = sol.u[domain.ist:domain.ied].copy() + + np.save(f"u_{ic_type}_full_py.npy", u_full) + np.save(f"u_{ic_type}_interior_py.npy", u_interior) + +print("✅ 初始条件测试数据已生成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/python/initial_condition.py b/example/1d-linear-convection/weno3/julia/01f/python/initial_condition.py new file mode 100644 index 000000000..047415b74 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/python/initial_condition.py @@ -0,0 +1,90 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, ic_type: str, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from factories.base_factory import BaseFactory + return BaseFactory.create_component('initial_condition', ic_type, config) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/python/mesh.py b/example/1d-linear-convection/weno3/julia/01f/python/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/python/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/python/plotter.py b/example/1d-linear-convection/weno3/julia/01f/python/plotter.py new file mode 100644 index 000000000..9f1a414fd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/python/plotter.py @@ -0,0 +1,107 @@ +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/python/reconstructor/__init__.py b/example/1d-linear-convection/weno3/julia/01f/python/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/python/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/python/reconstructor/base.py b/example/1d-linear-convection/weno3/julia/01f/python/reconstructor/base.py new file mode 100644 index 000000000..bbd638503 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/python/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def reconstruct(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/python/reconstructor/eno.py b/example/1d-linear-convection/weno3/julia/01f/python/reconstructor/eno.py new file mode 100644 index 000000000..c2fb385dd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/python/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def reconstruct(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/python/reconstructor/factory.py b/example/1d-linear-convection/weno3/julia/01f/python/reconstructor/factory.py new file mode 100644 index 000000000..efa4a1446 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/python/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from cfd_registry import ComponentRegistry + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from factories.base_factory import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/python/reconstructor/weno3.py b/example/1d-linear-convection/weno3/julia/01f/python/reconstructor/weno3.py new file mode 100644 index 000000000..6e8c3f230 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/python/reconstructor/weno3.py @@ -0,0 +1,88 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def reconstruct(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + """ + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 # smoothness indicator for stencil [v2, v3] + beta1 = (v2 - v1)**2 # smoothness indicator for stencil [v1, v2] + + d0, d1 = 2/3, 1/3 # optimal linear weights (for right value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 0.5 * v2 + 0.5 * v3 # reconstruction from [v2, v3] + q1 = -0.5 * v1 + 1.5 * v2 # reconstruction from [v1, v2] + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 + beta1 = (v2 - v1)**2 + + d0, d1 = 1/3, 2/3 # optimal linear weights (for left value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 1.5 * v2 - 0.5 * v3 # from [v2, v3] + q1 = 0.5 * v1 + 0.5 * v2 # from [v1, v2] + return w0 * q0 + w1 * q1 + """ diff --git a/example/1d-linear-convection/weno3/julia/01f/python/registry.py b/example/1d-linear-convection/weno3/julia/01f/python/registry.py new file mode 100644 index 000000000..61be90500 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/python/registry.py @@ -0,0 +1,75 @@ +# registry.py (改进版) +""" +CFD组件注册系统 - 简洁高效版 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表""" + + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True # 控制是否打印注册信息 + + @classmethod + def set_verbose(cls, verbose: bool): + """设置是否打印注册信息""" + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + # 检查是否已注册 + if name in cls._registries[category]: + if cls._registries[category][name] == component_class: + # 相同类重复注册,静默跳过 + return + elif cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取组件类""" + if category not in cls._registries: + available = list(cls._registries.keys()) + raise ValueError(f"❌ 未知类别: {category} (可用类别: {available})") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + @classmethod + def get_count(cls) -> int: + """获取注册组件总数""" + return sum(len(components) for components in cls._registries.values()) + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/python/residual.py b/example/1d-linear-convection/weno3/julia/01f/python/residual.py new file mode 100644 index 000000000..b4d4d7dcc --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/python/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux import FluxCalculatorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + self.reconstructor = self.cfd.reconstructor + + # 初始化通量计算器(工厂创建) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._reconstruct() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _reconstruct(self): + """私有方法:界面值重建""" + self.reconstructor.reconstruct(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/python/run_eno_weno.py b/example/1d-linear-convection/weno3/julia/01f/python/run_eno_weno.py new file mode 100644 index 000000000..fd7f38744 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/python/run_eno_weno.py @@ -0,0 +1,52 @@ +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + #mesh = Mesh(ncells=100, L=2.0) + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行ENO3求解(使用你的链式调用) + print("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + config_eno3.with_reconstruction("eno", 3) # 显式指定3阶(也可省略,ENO默认3阶) + # 可选:覆盖默认值(如dt) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + cfd_eno3.run() # 求解并生成result字典 + + # 可选:快速验证ENO3结果 + # plotter.plot_quick(cfd_eno3.result, title="ENO3 Quick Check") + + # 3. 配置并运行WENO3求解(注意:WENO默认5阶,这里显式指定3阶) + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) # 显式指定3阶(默认是5阶) + # 可选:覆盖默认值 + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + cfd_weno3.run() + + # 4. 可选:保存结果(供离线绘图) + # cfd_eno3.save_result("eno3_result.npz") + # cfd_weno3.save_result("weno3_result.npz") + + # 5. 绘制ENO/WENO对比图 + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" # 可选:保存图片 + ) + +if __name__ == "__main__": + # 主程序入口 + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/python/solution.py b/example/1d-linear-convection/weno3/julia/01f/python/solution.py new file mode 100644 index 000000000..92a46d3f4 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/python/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config.ic_type, config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/python/solver.py b/example/1d-linear-convection/weno3/julia/01f/python/solver.py new file mode 100644 index 000000000..0d0b442da --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/python/solver.py @@ -0,0 +1,86 @@ +import numpy as np +import matplotlib.pyplot as plt +import inflect +from abc import ABC, abstractmethod + + +from flux import InviscidFluxCalculator, RusanovFluxCalculator, EngquistOsherFluxCalculator, FluxCalculatorFactory + +from boundary import BoundaryCondition, PeriodicBoundary, DirichletBoundary, NeumannBoundary, BoundaryConditionFactory + +from time_integration import TimeIntegrator, RK1Integrator, RK2Integrator, RK3Integrator, TimeIntegratorFactory + +from mesh import Mesh + +from reconstructor import ReconstructorFactory + +from initial_condition import InitialCondition, StepFunctionIC, SineWaveIC, GaussianPulseIC, InitialConditionFactory + +from domain import Domain +from solution import Solution + +from config import CfdConfig +from residual import ResidualCalculator + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, config, mesh): + self.config = config + self.domain = Domain(config, mesh) + self.solution = Solution(config, self.domain) + self.reconstructor = ReconstructorFactory.create(config, self.domain) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + def exact_solution(self): + """通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界""" + x = self.domain.mesh.xcc + T = self.config.final_time + c = self.config.wave_speed + L = self.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = InitialConditionFactory.create(self.config.ic_type, self.config) + return ic.evaluate_at(x_shifted) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + #runge_kutta(self) + self.integrator.step(dt) + t += dt + config.dt = dt_old + + # 整理标准化结果 + u_numerical = self.solution.u[self.domain.ist:self.domain.ied].copy() + self.result = { + "x": domain.mesh.xcc, + "numerical": u_numerical, + "analytical": self.exact_solution(), + "config": { + "scheme": self.config.recon_scheme, + "order": self.config.spatial_order, + "rk_order": self.config.rk_order, + "final_time": self.config.final_time + } + } + + return u_numerical diff --git a/example/1d-linear-convection/weno3/julia/01f/python/time_integration.py b/example/1d-linear-convection/weno3/julia/01f/python/time_integration.py new file mode 100644 index 000000000..25ac0b4ca --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01f/python/time_integration.py @@ -0,0 +1,125 @@ +# time_integration.py + +""" +时间推进器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = cfd.residual_calculator + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(使用装饰器注册) ---------------------- + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class TimeIntegratorFactory: + """时间推进器工厂""" + + @staticmethod + def create(cfd) -> 'TimeIntegrator': + """ + 创建时间推进器实例 + + Args: + cfd: CFD对象 + + Returns: + 时间推进器实例 + """ + from factories.base_factory import BaseFactory + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01f/python/u_dirichlet_py.npy b/example/1d-linear-convection/weno3/julia/01f/python/u_dirichlet_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..3aa20bd2e395654a6e8c59bbbc9f5eaa955b345e GIT binary patch literal 480 zcmbWzxeftg6o%oW6O}@tQ2Y_`BNMTVeHmMHM59p2#LPq?$c$)2;vU?f%TQY8ZM07F zov8Zw0H1NzP(=`e8>IlQ+pS*SsNB7-1(=_si zx~l&#Cf|Elzsex&D8?~?Nlc-_G-fc1IV_-oMJ!<%D_BJnYgoqyHnD|m>|hsr*hdQo Wp*NHvjWVK9#x%-=MtSnjr}YA`88kxx literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01f/python/u_gaussian_full_py.npy b/example/1d-linear-convection/weno3/julia/01f/python/u_gaussian_full_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..b762f0fe295b52a9a176817021995ce5a561d778 GIT binary patch literal 480 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yCOVor3bhL411<(MU?Ou)P3Oi_+soE}ylQv)*=cg#Ue98-%FeWXxBn~o z-*&0B&Pq*ej`k{D0z3w9EA6|!ORWz4wA}u9Vfdl$E2r%H!VVg&w0>m2nCbZM)%@?^ zbQ@G0rY;|Sof!j>qBKc_8=pP9zb3SzgEt8=WEn__NZ z$*8-a{Z)*4!nvvF3 wqbFAl+ziF%o9|Zhkuvbx>B0B<*(1H@$~rUDs~6~AeE<2_q`W{KwBTd_0INu`7ytkO literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01f/python/u_gaussian_interior_py.npy b/example/1d-linear-convection/weno3/julia/01f/python/u_gaussian_interior_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..68af8954e7a17e69b6b2e6954b78ee542d4e96a0 GIT binary patch literal 448 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#y20EHL3bhL411=Mpb80#_p4wiv{^M1<)6Y(m^Y(fcvsHGc?YsS7$^W)X zt#wvvVso@t=@Q^Ecw1@T^<8Rp;HTyG#|y&`bzeDU-xqe!V5RjV`^8Mhf3N0$2dCSh z;xKjjP<`JzA?Co${oe+$2WBtK9e1T6?wYnbA&=>Lk?qOJ*(aWE{cN*=zyHlG=1iNl zN(soJS7TeOgcr6%N&Go&S^Ug2epV2>rCgn3z1$RY3rj}b z1?{h5%p2Bhd1c#FYWC{z!qS+GX48p_*$(R2wVEWXJEgqJq{jHxHq-S&9M{! literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01f/python/u_input.npy b/example/1d-linear-convection/weno3/julia/01f/python/u_input.npy new file mode 100644 index 0000000000000000000000000000000000000000..ef506fa75cacbbe63dfcc17332ddd6c33de5b888 GIT binary patch literal 480 zcmbWrI}gE70EXdX6O+MUFr0`uNFy$FzqBs35sSejjUF+GN-GwT_z(W5pP?kM2cBT{2ChY{-ZF ze<|^^w?*2qNlaq~vzSAPc`RTNOISezt60N2Hn52%wy=#I>|zi5IKUx}aEun(u}>5e R8pV`GF{4q;X%r6a*dIEnGEe{j literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01f/python/u_neumann_py.npy b/example/1d-linear-convection/weno3/julia/01f/python/u_neumann_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..fa723d1efce906983b6e6710379207d15afbc271 GIT binary patch literal 480 zcmbWrI}gE70EXdX6O+MUFr0`uC5^ZgRkymtMl1%CG}^`>Dy>*V;y?JqN?(i7w>|hs7?4gBy9N-W~I7U11fnvf( PG3BF}@lnkAKgRt9WDhZ? literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01f/python/u_periodic_py.npy b/example/1d-linear-convection/weno3/julia/01f/python/u_periodic_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..156aff85dfa4c84deaaed404e0dccc37937f8836 GIT binary patch literal 480 zcmbWzyAA+Jw5Jw7YF-Tj}jFJcs^aeGCKh-lzUPGOs<_`6u&g%W>?2t#0c7 zOUUOY-iJGid|Tp}#1y76g9@{l!#rA8L>o(3#tK%kh7Q)TflX{-8#~xV7kk*p0eWBW QC_@@$M5Bypl;8h*0k_X5-%Mo@PtK#WJA?o%og6P}-0bsJ$1U?l6M7O94XfzYY=Kp9oR6e-T9A{tpmy_6tDF-R}f( z-+mW}`}RZKw;$@h{Tm?e+z)l%epjeEd#FAks5zgY=FW!NlLxi;2-F?cP8 literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01g/julia/boundary.jl b/example/1d-linear-convection/weno3/julia/01g/julia/boundary.jl new file mode 100644 index 000000000..5b0baaf43 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/julia/boundary.jl @@ -0,0 +1,90 @@ +# julia/boundary.jl +# 目标:与 Python boundary.py 行为完全一致(1:1 同构) +# 前提:ist/ied 在 Julia 中按 1-based 设置(ist = nghosts + 1) + +using NPZ # 仅用于测试,实际模块可不依赖 + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象基类(Julia 风格:用函数接口) ---------------------- +# Julia 无 abstract class,用文档+约定 +# 所有子类型必须实现 apply!(bc, u) + +# ---------------------- PeriodicBoundary ---------------------- +mutable struct PeriodicBoundary + cfd::Any +end + +function apply!(self::PeriodicBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist # 1-based, e.g., 3 + ied = self.cfd.domain.ied # 1-based, e.g., 43 + + # 左 ghost: u[ist - 1 - ig] = u[ied - 1 - ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ied - 1 - ig] + end + # 右 ghost: u[ied + ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ied + ig] = u[ist + ig] + end +end + +# ---------------------- DirichletBoundary ---------------------- +mutable struct DirichletBoundary + cfd::Any +end + +function apply!(self::DirichletBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + left_value = getfield_safe(self.cfd.config, :left_boundary_value, 1.0) + right_value = getfield_safe(self.cfd.config, :right_boundary_value, 2.0) + + # 左边界 + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = left_value + end + # 右边界 + for ig in 0:(nghosts-1) + u[ied + ig] = right_value + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Dirichlet边界: 左值=$left_value, 右值=$right_value") + end +end + +# ---------------------- NeumannBoundary ---------------------- +mutable struct NeumannBoundary + cfd::Any +end + +function apply!(self::NeumannBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + # 左边界零梯度:u[ist - 1 - ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ist + ig] + end + # 右边界零梯度:u[ied + ig] = u[ied - 1 - ig] ← 注意是 ied - 1 - ig + for ig in 0:(nghosts-1) + u[ied + ig] = u[ied - 1 - ig] + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Neumann边界: 零梯度") + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/julia/domain.jl b/example/1d-linear-convection/weno3/julia/01g/julia/domain.jl new file mode 100644 index 000000000..a7edc2260 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/julia/domain.jl @@ -0,0 +1,61 @@ +# julia/domain.jl +""" +Domain 结构体(与 domain.py 完全同构) +- 保存 config +- nghosts 计算逻辑 1:1 +- ist/ied 为 0-based(与 Python 一致) +""" + +include("mesh.jl") + +mutable struct Domain + config::Any # 保存 config(与 Python 一致) + mesh::Mesh + nghosts::Int + ist::Int # 0-based + ied::Int # 0-based + ntcells::Int +end + +function Domain(config::Any, mesh::Mesh) + nghosts = _calc_nghosts(config) + ist = nghosts + 1 # ← 1-based 起始索引 + ied = ist + mesh.ncells # ← 1-based 结束索引(不包含) + ntcells = mesh.ncells + 2 * nghosts + Domain(config, mesh, nghosts, ist, ied, ntcells) +end + +function _calc_nghosts(config::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + order = getfield_safe(config, :spatial_order, 2) + + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + if scheme == "eno" + nghosts = order + elseif startswith(scheme, "weno") + nghosts = order ÷ 2 + 1 + else + error("未知重建格式 $(scheme),无法计算ghost层!") + end + + if nghosts <= 0 + error("计算得到的ghost层数量无效:$(nghosts)(阶数$(order),格式$(scheme))") + end + + return nghosts +end + +function is_physical_cell(domain::Domain, idx::Int) + return domain.ist <= idx < domain.ied +end + +function get_physical_indices(domain::Domain) + return domain.ist:(domain.ied - 1) +end + +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/julia/flux.jl b/example/1d-linear-convection/weno3/julia/01g/julia/flux.jl new file mode 100644 index 000000000..047598f7a --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/julia/flux.jl @@ -0,0 +1,70 @@ +# julia/flux.jl +""" +通量计算器模块(与 flux.py 完全同构) +- 抽象基类 + 具体实现 +- 字段:cfd, config, mesh, wave_speed +""" + +include("mesh.jl") + +# ---------------------- 抽象基类 ---------------------- +""" +InviscidFluxCalculator 抽象类型 +Julia 无 ABC,用文档约定 +所有子类型必须实现 compute! +""" +abstract type InviscidFluxCalculator end + +# ---------------------- RusanovFluxCalculator ---------------------- +mutable struct RusanovFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function RusanovFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::RusanovFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = calc.wave_speed + c_R = calc.wave_speed + F_L = c_L * u_L + F_R = c_R * u_R + Smax = max(abs(c_L), abs(c_R)) + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + end +end + +# ---------------------- EngquistOsherFluxCalculator ---------------------- +mutable struct EngquistOsherFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function EngquistOsherFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::EngquistOsherFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + c = calc.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/julia/initial_condition.jl b/example/1d-linear-convection/weno3/julia/01g/julia/initial_condition.jl new file mode 100644 index 000000000..5e029e8d2 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/julia/initial_condition.jl @@ -0,0 +1,86 @@ +# julia/initial_condition.jl +""" +初始条件模块(Julia 版) +目标:与 Python initial_condition.py 行为完全一致 +""" + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象接口(Julia 无继承,用函数约定) ---------------------- +# 所有初始条件必须实现: +# evaluate_at(ic, x::AbstractVector{Float64}) -> Vector{Float64} +# apply(ic, solution) + +# ---------------------- StepFunctionIC ---------------------- +struct StepFunctionIC + config::Any +end + +function evaluate_at(ic::StepFunctionIC, x::AbstractVector{Float64}) + u0 = ones(Float64, length(x)) + for i in eachindex(x) + if 0.5 <= x[i] <= 1.0 + u0[i] = 2.0 + end + end + return u0 +end + +function apply(ic::StepFunctionIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- SineWaveIC ---------------------- +struct SineWaveIC + config::Any +end + +function evaluate_at(ic::SineWaveIC, x::AbstractVector{Float64}) + L = getfield_safe(ic.config, :domain_length, 2.0) + return sin.(2π * x / L) +end + +function apply(ic::SineWaveIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- GaussianPulseIC ---------------------- +struct GaussianPulseIC + config::Any +end + +function evaluate_at(ic::GaussianPulseIC, x::AbstractVector{Float64}) + center = getfield_safe(ic.config, :pulse_center, 0.5) + width = getfield_safe(ic.config, :pulse_width, 0.1) + return exp.(-((x .- center) ./ width).^2) +end + +function apply(ic::GaussianPulseIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- 公共辅助函数 ---------------------- +""" +将初始场 values 应用到 solution.u 的物理区域(含 ghost 的数组) +""" +function _apply_to_interior(solution, values) + domain = solution.domain + # Python: for i in range(domain.ist, domain.ied) + # Julia: for i in domain.ist:domain.ied-1 (因为 ied 是结束索引,不包含) + for i in domain.ist:(domain.ied - 1) + j = i - domain.ist + 1 # values 是 1-based,i - ist 是 0-based → +1 + solution.u[i] = values[j] + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/julia/mesh.jl b/example/1d-linear-convection/weno3/julia/01g/julia/mesh.jl new file mode 100644 index 000000000..1b0dd4bf9 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/julia/mesh.jl @@ -0,0 +1,45 @@ +# julia/mesh.jl +""" +Mesh 结构体(与提供的 mesh.py 完全同构) +- 字段名、初始化顺序、循环逻辑完全一致 +- 不做任何泛化或优化 +""" + +mutable struct Mesh + xmin::Float64 + xmax::Float64 + ncells::Int + nnodes::Int + nx::Int + x::Vector{Float64} + xcc::Vector{Float64} + L::Float64 # 在 init_mesh 中赋值 + dx::Float64 # 在 init_mesh 中赋值 + + function Mesh() + # 与 Python __init__ 完全一致 + xmin = 0.0 + xmax = 2.0 + ncells = 40 + nnodes = ncells + 1 + nx = ncells + x = zeros(Float64, nnodes) + xcc = zeros(Float64, ncells) + + # 调用 init_mesh(模拟 Python) + L = xmax - xmin + dx = L / ncells + + # Generate node coordinates: for i in range(self.nnodes) + for i in 1:nnodes + x[i] = xmin + (i - 1) * dx # Python i → Julia i-1 + end + + # Generate cell center coordinates + for i in 1:ncells + xcc[i] = 0.5 * (x[i] + x[i+1]) + end + + new(xmin, xmax, ncells, nnodes, nx, x, xcc, L, dx) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/julia/residual.jl b/example/1d-linear-convection/weno3/julia/01g/julia/residual.jl new file mode 100644 index 000000000..15cc93878 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/julia/residual.jl @@ -0,0 +1,65 @@ +# julia/residual.jl +""" +残差计算器(与 residual.py 完全同构) +- 封装重建→通量→散度完整流程 +- 依赖 cfd 的多个字段 +""" + +include("mesh.jl") + +mutable struct ResidualCalculator + cfd::Any + config::Any + domain::Any + solution::Any + mesh::Mesh + reconstructor::Any + flux_calculator::Any # 通量计算器(外部传入,替代工厂) + + function ResidualCalculator(cfd::Any, flux_calculator::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + mesh = domain.mesh + reconstructor = cfd.reconstructor + + new(cfd, config, domain, solution, mesh, reconstructor, flux_calculator) + end +end + +""" +计算完整残差(对外唯一接口) +""" +function compute!(calc::ResidualCalculator) + #_reconstruct(calc) + _compute_inviscid_flux(calc) + _compute_flux_divergence(calc) +end + +""" +私有方法:界面值重建 +""" +function _reconstruct(calc::ResidualCalculator) + reconstruct(calc.reconstructor, calc.solution.u, calc.cfd) +end + +""" +私有方法:计算无粘通量 +""" +function _compute_inviscid_flux(calc::ResidualCalculator) + compute!(calc.flux_calculator, + calc.solution.q_face_left, + calc.solution.q_face_right, + calc.solution.flux) +end + +""" +私有方法:计算通量散度(残差 = -dF/dx) +""" +function _compute_flux_divergence(calc::ResidualCalculator) + solution = calc.solution + mesh = calc.mesh + for i in 1:mesh.ncells + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / mesh.dx + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/julia/solution.jl b/example/1d-linear-convection/weno3/julia/01g/julia/solution.jl new file mode 100644 index 000000000..90ca03931 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/julia/solution.jl @@ -0,0 +1,75 @@ +# julia/solution.jl +""" +Solution 结构体(与真实 solution.py 完全同构) +字段顺序、方法、逻辑 1:1 对齐 +""" + +include("mesh.jl") +include("domain.jl") +include("initial_condition.jl") + +mutable struct Solution + domain::Domain + q_face_left::Vector{Float64} + q_face_right::Vector{Float64} + flux::Vector{Float64} + res::Vector{Float64} + u::Vector{Float64} + un::Vector{Float64} + + function Solution(config::Any, domain::Domain) + mesh = domain.mesh + + q_face_left = zeros(Float64, mesh.nnodes) + q_face_right = zeros(Float64, mesh.nnodes) + flux = zeros(Float64, mesh.nnodes) + res = zeros(Float64, mesh.ncells) + u = zeros(Float64, domain.ntcells) + un = zeros(Float64, domain.ntcells) + + sol = new(domain, q_face_left, q_face_right, flux, res, u, un) + initialize_from_config(sol, config) + return sol + end +end + +""" +重置解数组为初始状态 +""" +function reset_solution(sol::Solution) + fill!(sol.u, 0.0) + fill!(sol.un, 0.0) +end + +""" +根据配置初始化场 +注:暂用硬编码替代 InitialConditionFactory +""" +function initialize_from_config(sol::Solution, config::Any) + ic_type = getfield_safe(config, :ic_type, "step") + + # 硬编码创建 IC(替代 InitialConditionFactory) + ic = if ic_type == "step" + StepFunctionIC(config) + elseif ic_type == "sin" + SineWaveIC(config) + elseif ic_type == "gaussian" + GaussianPulseIC(config) + else + error("未知初始条件类型: $ic_type") + end + + apply(ic, sol) # 调用 IC.apply +end + +""" +更新旧场:un = u +""" +function update_old_field(sol::Solution) + sol.un .= sol.u # 等价于 Python 的 un[:] = u[:] +end + +# ---------------------- 辅助函数 ---------------------- +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/julia/test/test_boundary.jl b/example/1d-linear-convection/weno3/julia/01g/julia/test/test_boundary.jl new file mode 100644 index 000000000..ef27d6820 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/julia/test/test_boundary.jl @@ -0,0 +1,57 @@ +# julia/test/test_boundary.jl +using NPZ +include("../boundary.jl") + +struct MockConfig + boundary_type::String + left_boundary_value::Float64 + right_boundary_value::Float64 + debug::Bool +end + +struct MockDomain + nghosts::Int + ist::Int # Julia 本地索引(1-based) + ied::Int + ntcells::Int +end + +struct MockCfd + config::MockConfig + domain::MockDomain +end + +# ===== 关键:使用 Julia 本地索引规则 ===== +nghosts = 2 +ncells = 40 +ist = nghosts + 1 # = 3 +ied = ist + ncells # = 43 +ntcells = ncells + 2 * nghosts # = 44 + +config = MockConfig("dirichlet", 0.5, 1.5, true) +domain = MockDomain(nghosts, ist, ied, ntcells) +cfd_mock = MockCfd(config, domain) + +# 加载 Python 生成的 u_input.npy +# 注意:Python u[0] → Julia u[1],所以内容完全对应 +u_input = npzread("../../python/u_input.npy") +@assert length(u_input) == ntcells "数组长度不匹配!" + +# 测试三种边界 +test_cases = [ + ("periodic", PeriodicBoundary(cfd_mock)), + ("dirichlet", DirichletBoundary(cfd_mock)), + ("neumann", NeumannBoundary(cfd_mock)) +] + +for (name, bc) in test_cases + u = copy(u_input) + apply!(bc, u) + + u_py = npzread("../../python/u_$(name)_py.npy") + err = maximum(abs.(u .- u_py)) + println("边界: $(name) | 最大误差: $(err)") + @assert err < 1e-12 "❌ $(name) 不一致!" +end + +println("✅ 所有边界条件测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/julia/test/test_domain.jl b/example/1d-linear-convection/weno3/julia/01g/julia/test/test_domain.jl new file mode 100644 index 000000000..7b423f774 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/julia/test/test_domain.jl @@ -0,0 +1,44 @@ +# julia/test/test_domain.jl +include("../mesh.jl") +include("../domain.jl") + +# MockConfig:模拟 Python CfdConfig +struct MockConfig + recon_scheme::String + spatial_order::Int +end + +# 测试 ENO +config_eno = MockConfig("eno", 2) +mesh = Mesh() +domain_eno = Domain(config_eno, mesh) + +println("ENO: nghosts = ", domain_eno.nghosts) # 2 +println("ENO: ist = ", domain_eno.ist) # 2 +println("ENO: ied = ", domain_eno.ied) # 42 +println("物理索引范围: ", collect(get_physical_indices(domain_eno))[1:3], " ... ", collect(get_physical_indices(domain_eno))[end-2:end]) + +# 测试 WENO(字符串 "weno") +config_weno = MockConfig("weno", 2) +domain_weno = Domain(config_weno, mesh) +println("WENO: nghosts = ", domain_weno.nghosts) # 2 + +# 测试 WENO3(字符串 "weno3") +config_weno3 = MockConfig("weno3", 2) +domain_weno3 = Domain(config_weno3, mesh) +println("WENO3: nghosts = ", domain_weno3.nghosts) # 2 + +# 测试 is_physical_cell +@assert is_physical_cell(domain_eno, 2) == true # ist=2 +@assert is_physical_cell(domain_eno, 41) == true # ied-1=41 +@assert is_physical_cell(domain_eno, 42) == false # ied=42 + +# ✅ 断言 +@assert domain_eno.nghosts == 2 +@assert domain_eno.ist == 2 +@assert domain_eno.ied == 42 +@assert domain_eno.ntcells == 44 +@assert domain_weno.nghosts == 2 +@assert domain_weno3.nghosts == 2 + +println("✅ Domain 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/julia/test/test_flux.jl b/example/1d-linear-convection/weno3/julia/01g/julia/test/test_flux.jl new file mode 100644 index 000000000..0ebc1dc05 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/julia/test/test_flux.jl @@ -0,0 +1,53 @@ +# julia/test/test_flux.jl +include("../mesh.jl") +include("../flux.jl") + +# Mock 对象 +struct MockConfig + flux_type::String + wave_speed::Float64 +end + +struct MockDomain + mesh::Mesh +end + +struct MockCfd + config::MockConfig + domain::MockDomain +end + +# 创建 mock +mesh = Mesh() +config = MockConfig("rusanov", 1.0) +domain = MockDomain(mesh) +cfd = MockCfd(config, domain) + +# 测试 Rusanov +rusanov = RusanovFluxCalculator(cfd) +N = mesh.nnodes +qL = [1.0, 2.0, 3.0, 4.0, 5.0, zeros(Float64, N-5)...] +qR = [2.0, 3.0, 4.0, 5.0, 6.0, zeros(Float64, N-5)...] +flux_rus = zeros(Float64, N) +compute!(rusanov, qL, qR, flux_rus) + +println("Rusanov flux[1] = ", flux_rus[1]) # 应为 1.0 +@assert abs(flux_rus[1] - 1.0) < 1e-12 + +# 测试 Engquist-Osher +eo = EngquistOsherFluxCalculator(cfd) +flux_eo = zeros(Float64, N) +compute!(eo, qL, qR, flux_eo) + +println("EO flux[1] = ", flux_eo[1]) # c=1 → cp=1, cm=0 → flux=1*1 + 0*2 = 1.0 +@assert abs(flux_eo[1] - 1.0) < 1e-12 + +# 测试 c = -1.0 +config_neg = MockConfig("rusanov", -1.0) +cfd_neg = MockCfd(config_neg, domain) +rusanov_neg = RusanovFluxCalculator(cfd_neg) +compute!(rusanov_neg, qL, qR, flux_rus) +println("Rusanov (c=-1) flux[1] = ", flux_rus[1]) # F_L=-1, F_R=-2, Smax=1 → flux = -1.5 -0.5*(-1) = -1.0 +@assert abs(flux_rus[1] - (-2.0)) < 1e-12 # ← 修正:-2.0 而非 -1.0 + +println("✅ Flux 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/julia/test/test_initial_condition.jl b/example/1d-linear-convection/weno3/julia/01g/julia/test/test_initial_condition.jl new file mode 100644 index 000000000..dc58691a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/julia/test/test_initial_condition.jl @@ -0,0 +1,45 @@ +# julia/test/test_initial_condition.jl +using NPZ + +# 包含初始条件模块 +include("../initial_condition.jl") + +# 构造 mock config(与 Python 完全一致) +struct MockConfig + ic_type::String + domain_length::Float64 + pulse_center::Float64 + pulse_width::Float64 +end + +# 生成与 Python Mesh.xcc 完全相同的 x 坐标 +function generate_xcc() + xmin, xmax = 0.0, 2.0 + ncells = 40 + dx = (xmax - xmin) / ncells + xcc = Vector{Float64}(undef, ncells) + for i in 1:ncells + xcc[i] = xmin + (i - 0.5) * dx # i-1 + 0.5 → i-0.5 + end + return xcc +end + +# 主测试 +xcc = generate_xcc() + +test_cases = [ + ("step", StepFunctionIC(MockConfig("step", 2.0, 0.5, 0.1))), + ("sin", SineWaveIC(MockConfig("sin", 2.0, 0.5, 0.1))), + ("gaussian", GaussianPulseIC(MockConfig("gaussian", 2.0, 0.5, 0.1))) +] + +for (name, ic) in test_cases + u_jl = evaluate_at(ic, xcc) + u_py = npzread("../../python/u_$(name)_interior_py.npy") + + err = maximum(abs.(u_jl .- u_py)) + println("IC: $(name) | 最大误差: $(err)") + @assert err < 1e-12 "❌ $(name) 不一致!" +end + +println("✅ 所有初始条件测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/julia/test/test_mesh.jl b/example/1d-linear-convection/weno3/julia/01g/julia/test/test_mesh.jl new file mode 100644 index 000000000..315d2aac2 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/julia/test/test_mesh.jl @@ -0,0 +1,37 @@ +# julia/test/test_mesh.jl +include("../mesh.jl") + +# 创建 mesh(与 Python 完全相同) +mesh = Mesh() + +# 打印关键值(与 Python 对比) +println("xmin = ", mesh.xmin) # 0.0 +println("xmax = ", mesh.xmax) # 2.0 +println("ncells = ", mesh.ncells) # 40 +println("nnodes = ", mesh.nnodes) # 41 +println("nx = ", mesh.nx) # 40 +println("L = ", mesh.L) # 2.0 +println("dx = ", mesh.dx) # 0.05 + +# 检查 x[1] (Python x[0]) 和 x[41] (Python x[40]) +println("x[1] = ", mesh.x[1]) # 0.0 +println("x[41] = ", mesh.x[41]) # 2.0 + +# 检查 xcc[1] (Python xcc[0]) 和 xcc[40] (Python xcc[39]) +println("xcc[1] = ", mesh.xcc[1]) # 0.025 +println("xcc[40] = ", mesh.xcc[40]) # 1.975 + +# ✅ 严格断言 +@assert mesh.xmin == 0.0 +@assert mesh.xmax == 2.0 +@assert mesh.ncells == 40 +@assert mesh.nnodes == 41 +@assert mesh.nx == 40 +@assert mesh.L == 2.0 +@assert mesh.dx == 0.05 +@assert mesh.x[1] == 0.0 +@assert mesh.x[41] == 2.0 +@assert abs(mesh.xcc[1] - 0.025) < 1e-12 +@assert abs(mesh.xcc[40] - 1.975) < 1e-12 + +println("✅ Mesh 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/julia/test/test_residual.jl b/example/1d-linear-convection/weno3/julia/01g/julia/test/test_residual.jl new file mode 100644 index 000000000..e37e8d2bd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/julia/test/test_residual.jl @@ -0,0 +1,53 @@ +# julia/test/test_residual.jl +include("../mesh.jl") +include("../domain.jl") +include("../solution.jl") +include("../flux.jl") +include("../residual.jl") + +# ===== Dummy Reconstructor ===== +struct DummyReconstructor end + +# ===== Mock Config ===== +struct MockConfig + flux_type::String + wave_speed::Float64 +end + +# ===== Mock CFD (必须包含 reconstructor 字段) ===== +struct MockCfd + config::MockConfig + domain::Domain + solution::Solution + reconstructor::DummyReconstructor # ← 关键:添加此字段 +end + +# ===== 主测试 ===== +config = MockConfig("rusanov", 1.0) +mesh = Mesh() +domain = Domain((recon_scheme="eno", spatial_order=2), mesh) +solution = Solution((ic_type="step",), domain) + +# 手动设置界面值(跳过重建) +for i in 1:mesh.nnodes + solution.q_face_left[i] = Float64(i) * 0.1 + solution.q_face_right[i] = Float64(i) * 0.1 +end + +flux_calc = RusanovFluxCalculator((config=config, domain=domain)) +dummy_recon = DummyReconstructor() +cfd = MockCfd(config, domain, solution, dummy_recon) + +# 创建残差计算器 +res_calc = ResidualCalculator(cfd, flux_calc) + +# 计算残差(注意:_reconstruct 仍被注释) +compute!(res_calc) + +println("flux[1] = ", solution.flux[1]) +println("res[1] = ", solution.res[1]) + +@assert abs(solution.flux[1] - 0.1) < 1e-12 +@assert abs(solution.res[1] - (-2.0)) < 1e-12 + +println("✅ Residual 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/julia/test/test_solution.jl b/example/1d-linear-convection/weno3/julia/01g/julia/test/test_solution.jl new file mode 100644 index 000000000..f44ff0f0e --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/julia/test/test_solution.jl @@ -0,0 +1,42 @@ +# julia/test/test_solution.jl +include("../mesh.jl") +include("../domain.jl") +include("../solution.jl") + +# MockConfig +struct MockConfig + recon_scheme::String + spatial_order::Int + ic_type::String + domain_length::Float64 + pulse_center::Float64 + pulse_width::Float64 +end + +# 创建 solution +config = MockConfig("eno", 2, "step", 2.0, 0.5, 0.1) +mesh = Mesh() +domain = Domain(config, mesh) +sol = Solution(config, domain) + +# 检查字段尺寸 +@assert length(sol.q_face_left) == mesh.nnodes # 41 +@assert length(sol.flux) == mesh.nnodes # 41 +@assert length(sol.res) == mesh.ncells # 40 +@assert length(sol.u) == domain.ntcells # 44 + +# 检查初始场 +println("u[3] (物理起始): ", sol.u[3]) # 应为 1.0 +println("u[23] (x=1.0): ", sol.u[23]) # 应为 2.0 + +# 测试 update_old_field +sol.u[3] = 999.0 +update_old_field(sol) +@assert sol.un[3] == 999.0 + +# 测试 reset_solution +reset_solution(sol) +@assert sol.u[3] == 0.0 +@assert sol.un[3] == 0.0 + +println("✅ Solution 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/julia/test/test_time_integration.jl b/example/1d-linear-convection/weno3/julia/01g/julia/test/test_time_integration.jl new file mode 100644 index 000000000..091a8637c --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/julia/test/test_time_integration.jl @@ -0,0 +1,53 @@ +# julia/test/test_time_integration.jl +include("../time_integration.jl") +include("../flux.jl") +include("../boundary.jl") + +# Mock CFD 对象 +struct MockCfd + config::Any + domain::Domain + solution::Solution + residual_calculator::Any + boundary_condition::Any +end + +# 创建完整链路 +mesh = Mesh() +domain = Domain((recon_scheme="eno", spatial_order=2), mesh) +solution = Solution((ic_type="step",), domain) + +# 手动设置界面值(跳过 reconstructor) +for i in 1:mesh.nnodes + solution.q_face_left[i] = Float64(i) * 0.1 + solution.q_face_right[i] = Float64(i) * 0.1 +end + +# Mock components +flux_calc = RusanovFluxCalculator((config=(wave_speed=1.0,), domain=domain)) +res_calc = ResidualCalculator((config=nothing, domain=domain, solution=solution, reconstructor=nothing), flux_calc) +bc = PeriodicBoundary((domain=domain, config=(debug=false,))) +cfd = MockCfd((rk_order=1,), domain, solution, res_calc, bc) + +# 测试 RK1 +rk1 = RK1Integrator(cfd) +step(rk1, 0.025) + +println("RK1 step completed") +@assert solution.u[3] != 0.0 # 应已更新 + +# 测试 RK2 +cfd2 = MockCfd((rk_order=2,), domain, solution, res_calc, bc) +rk2 = RK2Integrator(cfd2) +step(rk2, 0.025) + +println("RK2 step completed") + +# 测试 RK3 +cfd3 = MockCfd((rk_order=3,), domain, solution, res_calc, bc) +rk3 = RK3Integrator(cfd3) +step(rk3, 0.025) + +println("RK3 step completed") + +println("✅ Time Integration 逻辑测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/julia/test_residual.jl b/example/1d-linear-convection/weno3/julia/01g/julia/test_residual.jl new file mode 100644 index 000000000..315d2aac2 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/julia/test_residual.jl @@ -0,0 +1,37 @@ +# julia/test/test_mesh.jl +include("../mesh.jl") + +# 创建 mesh(与 Python 完全相同) +mesh = Mesh() + +# 打印关键值(与 Python 对比) +println("xmin = ", mesh.xmin) # 0.0 +println("xmax = ", mesh.xmax) # 2.0 +println("ncells = ", mesh.ncells) # 40 +println("nnodes = ", mesh.nnodes) # 41 +println("nx = ", mesh.nx) # 40 +println("L = ", mesh.L) # 2.0 +println("dx = ", mesh.dx) # 0.05 + +# 检查 x[1] (Python x[0]) 和 x[41] (Python x[40]) +println("x[1] = ", mesh.x[1]) # 0.0 +println("x[41] = ", mesh.x[41]) # 2.0 + +# 检查 xcc[1] (Python xcc[0]) 和 xcc[40] (Python xcc[39]) +println("xcc[1] = ", mesh.xcc[1]) # 0.025 +println("xcc[40] = ", mesh.xcc[40]) # 1.975 + +# ✅ 严格断言 +@assert mesh.xmin == 0.0 +@assert mesh.xmax == 2.0 +@assert mesh.ncells == 40 +@assert mesh.nnodes == 41 +@assert mesh.nx == 40 +@assert mesh.L == 2.0 +@assert mesh.dx == 0.05 +@assert mesh.x[1] == 0.0 +@assert mesh.x[41] == 2.0 +@assert abs(mesh.xcc[1] - 0.025) < 1e-12 +@assert abs(mesh.xcc[40] - 1.975) < 1e-12 + +println("✅ Mesh 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/julia/time_integration.jl b/example/1d-linear-convection/weno3/julia/01g/julia/time_integration.jl new file mode 100644 index 000000000..95e5cd67f --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/julia/time_integration.jl @@ -0,0 +1,130 @@ +# julia/time_integration.jl +""" +时间推进器模块(与 time_integration.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("residual.jl") +include("boundary.jl") + +# ---------------------- 抽象时间推进器基类 ---------------------- +abstract type TimeIntegrator end + +mutable struct TimeIntegratorBase <: TimeIntegrator + cfd::Any + config::Any + domain::Domain + solution::Solution + residual_calculator::Any # ResidualCalculator +end + +function TimeIntegratorBase(cfd::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + residual_calculator = cfd.residual_calculator + TimeIntegratorBase(cfd, config, domain, solution, residual_calculator) +end + +function compute_residual(integrator::TimeIntegratorBase) + compute!(integrator.residual_calculator) +end + +function apply_boundary(integrator::TimeIntegratorBase) + apply!(integrator.cfd.boundary_condition, integrator.solution.u) +end + +function map_idx(integrator::TimeIntegratorBase, i::Int) + return i - integrator.domain.ist + 1 # ← +1 转为 1-based +end + +# ---------------------- RK1Integrator ---------------------- +mutable struct RK1Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK1Integrator(cfd::Any) + RK1Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK1Integrator, dt::Float64) + compute_residual(integrator.base) + for i in integrator.base.domain.ist:(integrator.base.domain.ied - 1) + j = map_idx(integrator.base, i) + integrator.base.solution.u[i] += dt * integrator.base.solution.res[j] + end + apply_boundary(integrator.base) + update_old_field(integrator.base.solution) +end + +# ---------------------- RK2Integrator ---------------------- +mutable struct RK2Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK2Integrator(cfd::Any) + RK2Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK2Integrator, dt::Float64) + base = integrator.base + # 阶段1:预测步 + compute_residual(base) + u_pred = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u_pred[i] += dt * base.solution.res[j] + end + base.solution.u .= u_pred + apply_boundary(base) + # 阶段2:校正步 + compute_residual(base) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = 0.5 * base.solution.un[i] + 0.5 * base.solution.u[i] + 0.5 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end + +# ---------------------- RK3Integrator ---------------------- +mutable struct RK3Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK3Integrator(cfd::Any) + RK3Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK3Integrator, dt::Float64) + base = integrator.base + # 阶段1 + compute_residual(base) + u1 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u1[i] += dt * base.solution.res[j] + end + base.solution.u .= u1 + apply_boundary(base) + # 阶段2 + compute_residual(base) + u2 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u2[i] = 0.75 * base.solution.un[i] + 0.25 * base.solution.u[i] + 0.25 * dt * base.solution.res[j] + end + base.solution.u .= u2 + apply_boundary(base) + # 阶段3 + compute_residual(base) + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = c1 * base.solution.un[i] + c2 * base.solution.u[i] + c3 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/python/boundary.py b/example/1d-linear-convection/weno3/julia/01g/python/boundary.py new file mode 100644 index 000000000..6054f92de --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/python/boundary.py @@ -0,0 +1,103 @@ +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from factories.base_factory import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/python/cfd_registry.py b/example/1d-linear-convection/weno3/julia/01g/python/cfd_registry.py new file mode 100644 index 000000000..c12eb106c --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/python/cfd_registry.py @@ -0,0 +1,62 @@ +# cfd_registry.py +""" +CFD注册系统统一导入模块 +所有其他模块从这里导入注册系统 +""" + +import sys +import os + +# 添加当前目录到路径,确保能找到registry.py +current_dir = os.path.dirname(os.path.abspath(__file__)) +if current_dir not in sys.path: + sys.path.insert(0, current_dir) + +try: + # 尝试导入注册系统 + from registry import ComponentRegistry, register_component + REGISTRY_AVAILABLE = True +except ImportError: + # 如果找不到,提供简单的空实现 + print("⚠️ 警告: 未找到 registry.py,使用最小化回退实现") + + class ComponentRegistry: + """最小化的注册系统回退实现""" + _registries = {} + + @classmethod + def register(cls, category, name, component_class): + if category not in cls._registries: + cls._registries[category] = {} + cls._registries[category][name] = component_class + + @classmethod + def get(cls, category, name): + if category not in cls._registries: + raise ValueError(f"未知类别: {category}") + if name not in cls._registries[category]: + raise ValueError(f"未找到: {category}.{name}") + return cls._registries[category][name] + + @classmethod + def create(cls, category, name, *args, **kwargs): + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) + for cat, comps in cls._registries.items()} + + def register_component(category, name=None): + """简化的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + REGISTRY_AVAILABLE = False + +# 导出统一的接口 +__all__ = ['ComponentRegistry', 'register_component', 'REGISTRY_AVAILABLE'] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/python/config.py b/example/1d-linear-convection/weno3/julia/01g/python/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/python/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/python/domain.py b/example/1d-linear-convection/weno3/julia/01g/python/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/python/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/python/factories/base_factory.py b/example/1d-linear-convection/weno3/julia/01g/python/factories/base_factory.py new file mode 100644 index 000000000..7b8b6105e --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/python/factories/base_factory.py @@ -0,0 +1,49 @@ +# factories/base_factory.py +""" +工厂基类 - 提供统一的组件创建接口 +""" + +from cfd_registry import ComponentRegistry +from typing import Any, Optional + +class BaseFactory: + """工厂基类,提供统一的组件创建方法""" + + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + """ + 统一创建组件的方法 + + Args: + category: 组件类别(如'boundary', 'flux'等) + name: 组件名称(如'periodic', 'rusanov'等) + *args, **kwargs: 传递给构造函数的参数 + + Returns: + 创建的组件实例 + + Raises: + ValueError: 如果组件不存在 + """ + name_lower = name.lower() + + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + # 获取可用组件列表 + available = ComponentRegistry.list_all().get(category, []) + + # 构建友好的错误信息 + if available: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"可用类型:{available}") + else: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"({category}类别下没有注册任何组件)") + + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + """获取指定类别的可用组件列表""" + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/python/flux.py b/example/1d-linear-convection/weno3/julia/01g/python/flux.py new file mode 100644 index 000000000..5ac73aa8d --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/python/flux.py @@ -0,0 +1,74 @@ +# flux.py +""" +通量计算器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册,替代硬编码工厂 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象通量计算基类(统一接口) ---------------------- +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass + +# ---------------------- 2. 具体通量计算子类(使用装饰器注册) ---------------------- + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + +class FluxCalculatorFactory: + """通量计算器工厂""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD对象 + + Returns: + 通量计算器实例 + """ + from factories.base_factory import BaseFactory + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/python/gen_boundary_test_data.py b/example/1d-linear-convection/weno3/julia/01g/python/gen_boundary_test_data.py new file mode 100644 index 000000000..c7fc2a9c5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/python/gen_boundary_test_data.py @@ -0,0 +1,35 @@ +# python/gen_boundary_test_data.py +import numpy as np +from config import CfdConfig +from mesh import Mesh +from domain import Domain + +# 固定测试配置 +config = CfdConfig() +config.with_boundary("dirichlet", left_value=0.5, right_value=1.5) +config.debug = True + +mesh = Mesh() +domain = Domain(config, mesh) + +# 构造 mock CFD 对象(仅含 config + domain) +class MockCfd: + def __init__(self, config, domain): + self.config = config + self.domain = domain + +# 测试用 u:0,1,2,...,N-1 +u_input = np.arange(domain.ntcells, dtype=np.float64) +np.save("u_input.npy", u_input) + +# 测试每种边界 +from boundary import PeriodicBoundary, DirichletBoundary, NeumannBoundary + +for bc_name, bc_class in [("periodic", PeriodicBoundary), ("dirichlet", DirichletBoundary), ("neumann", NeumannBoundary)]: + u = u_input.copy() + cfd_mock = MockCfd(config, domain) + bc = bc_class(cfd_mock) + bc.apply(u) + np.save(f"u_{bc_name}_py.npy", u) + +print("✅ 测试数据已生成:u_*.npy") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/python/gen_ic_test_data.py b/example/1d-linear-convection/weno3/julia/01g/python/gen_ic_test_data.py new file mode 100644 index 000000000..69c2573b8 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/python/gen_ic_test_data.py @@ -0,0 +1,27 @@ +# python/gen_ic_test_data.py +import sys, os +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +import numpy as np +from config import CfdConfig +from mesh import Mesh +from domain import Domain +from solution import Solution + +# 固定 mesh +mesh = Mesh() +config = CfdConfig() + +# 测试三种 IC +for ic_type in ["step", "sin", "gaussian"]: + config.ic_type = ic_type + domain = Domain(config, mesh) + sol = Solution(config, domain) + + u_full = sol.u.copy() # 包含 ghost + u_interior = sol.u[domain.ist:domain.ied].copy() + + np.save(f"u_{ic_type}_full_py.npy", u_full) + np.save(f"u_{ic_type}_interior_py.npy", u_interior) + +print("✅ 初始条件测试数据已生成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/python/initial_condition.py b/example/1d-linear-convection/weno3/julia/01g/python/initial_condition.py new file mode 100644 index 000000000..047415b74 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/python/initial_condition.py @@ -0,0 +1,90 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, ic_type: str, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from factories.base_factory import BaseFactory + return BaseFactory.create_component('initial_condition', ic_type, config) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/python/mesh.py b/example/1d-linear-convection/weno3/julia/01g/python/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/python/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/python/plotter.py b/example/1d-linear-convection/weno3/julia/01g/python/plotter.py new file mode 100644 index 000000000..9f1a414fd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/python/plotter.py @@ -0,0 +1,107 @@ +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/python/reconstructor/__init__.py b/example/1d-linear-convection/weno3/julia/01g/python/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/python/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/python/reconstructor/base.py b/example/1d-linear-convection/weno3/julia/01g/python/reconstructor/base.py new file mode 100644 index 000000000..bbd638503 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/python/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def reconstruct(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/python/reconstructor/eno.py b/example/1d-linear-convection/weno3/julia/01g/python/reconstructor/eno.py new file mode 100644 index 000000000..c2fb385dd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/python/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def reconstruct(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/python/reconstructor/factory.py b/example/1d-linear-convection/weno3/julia/01g/python/reconstructor/factory.py new file mode 100644 index 000000000..efa4a1446 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/python/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from cfd_registry import ComponentRegistry + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from factories.base_factory import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/python/reconstructor/weno3.py b/example/1d-linear-convection/weno3/julia/01g/python/reconstructor/weno3.py new file mode 100644 index 000000000..6e8c3f230 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/python/reconstructor/weno3.py @@ -0,0 +1,88 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def reconstruct(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + """ + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 # smoothness indicator for stencil [v2, v3] + beta1 = (v2 - v1)**2 # smoothness indicator for stencil [v1, v2] + + d0, d1 = 2/3, 1/3 # optimal linear weights (for right value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 0.5 * v2 + 0.5 * v3 # reconstruction from [v2, v3] + q1 = -0.5 * v1 + 1.5 * v2 # reconstruction from [v1, v2] + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 + beta1 = (v2 - v1)**2 + + d0, d1 = 1/3, 2/3 # optimal linear weights (for left value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 1.5 * v2 - 0.5 * v3 # from [v2, v3] + q1 = 0.5 * v1 + 0.5 * v2 # from [v1, v2] + return w0 * q0 + w1 * q1 + """ diff --git a/example/1d-linear-convection/weno3/julia/01g/python/registry.py b/example/1d-linear-convection/weno3/julia/01g/python/registry.py new file mode 100644 index 000000000..61be90500 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/python/registry.py @@ -0,0 +1,75 @@ +# registry.py (改进版) +""" +CFD组件注册系统 - 简洁高效版 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表""" + + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True # 控制是否打印注册信息 + + @classmethod + def set_verbose(cls, verbose: bool): + """设置是否打印注册信息""" + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + # 检查是否已注册 + if name in cls._registries[category]: + if cls._registries[category][name] == component_class: + # 相同类重复注册,静默跳过 + return + elif cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取组件类""" + if category not in cls._registries: + available = list(cls._registries.keys()) + raise ValueError(f"❌ 未知类别: {category} (可用类别: {available})") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + @classmethod + def get_count(cls) -> int: + """获取注册组件总数""" + return sum(len(components) for components in cls._registries.values()) + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/python/residual.py b/example/1d-linear-convection/weno3/julia/01g/python/residual.py new file mode 100644 index 000000000..b4d4d7dcc --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/python/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux import FluxCalculatorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + self.reconstructor = self.cfd.reconstructor + + # 初始化通量计算器(工厂创建) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._reconstruct() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _reconstruct(self): + """私有方法:界面值重建""" + self.reconstructor.reconstruct(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/python/run_eno_weno.py b/example/1d-linear-convection/weno3/julia/01g/python/run_eno_weno.py new file mode 100644 index 000000000..fd7f38744 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/python/run_eno_weno.py @@ -0,0 +1,52 @@ +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + #mesh = Mesh(ncells=100, L=2.0) + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行ENO3求解(使用你的链式调用) + print("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + config_eno3.with_reconstruction("eno", 3) # 显式指定3阶(也可省略,ENO默认3阶) + # 可选:覆盖默认值(如dt) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + cfd_eno3.run() # 求解并生成result字典 + + # 可选:快速验证ENO3结果 + # plotter.plot_quick(cfd_eno3.result, title="ENO3 Quick Check") + + # 3. 配置并运行WENO3求解(注意:WENO默认5阶,这里显式指定3阶) + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) # 显式指定3阶(默认是5阶) + # 可选:覆盖默认值 + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + cfd_weno3.run() + + # 4. 可选:保存结果(供离线绘图) + # cfd_eno3.save_result("eno3_result.npz") + # cfd_weno3.save_result("weno3_result.npz") + + # 5. 绘制ENO/WENO对比图 + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" # 可选:保存图片 + ) + +if __name__ == "__main__": + # 主程序入口 + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/python/solution.py b/example/1d-linear-convection/weno3/julia/01g/python/solution.py new file mode 100644 index 000000000..92a46d3f4 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/python/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config.ic_type, config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/python/solver.py b/example/1d-linear-convection/weno3/julia/01g/python/solver.py new file mode 100644 index 000000000..0d0b442da --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/python/solver.py @@ -0,0 +1,86 @@ +import numpy as np +import matplotlib.pyplot as plt +import inflect +from abc import ABC, abstractmethod + + +from flux import InviscidFluxCalculator, RusanovFluxCalculator, EngquistOsherFluxCalculator, FluxCalculatorFactory + +from boundary import BoundaryCondition, PeriodicBoundary, DirichletBoundary, NeumannBoundary, BoundaryConditionFactory + +from time_integration import TimeIntegrator, RK1Integrator, RK2Integrator, RK3Integrator, TimeIntegratorFactory + +from mesh import Mesh + +from reconstructor import ReconstructorFactory + +from initial_condition import InitialCondition, StepFunctionIC, SineWaveIC, GaussianPulseIC, InitialConditionFactory + +from domain import Domain +from solution import Solution + +from config import CfdConfig +from residual import ResidualCalculator + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, config, mesh): + self.config = config + self.domain = Domain(config, mesh) + self.solution = Solution(config, self.domain) + self.reconstructor = ReconstructorFactory.create(config, self.domain) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + def exact_solution(self): + """通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界""" + x = self.domain.mesh.xcc + T = self.config.final_time + c = self.config.wave_speed + L = self.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = InitialConditionFactory.create(self.config.ic_type, self.config) + return ic.evaluate_at(x_shifted) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + #runge_kutta(self) + self.integrator.step(dt) + t += dt + config.dt = dt_old + + # 整理标准化结果 + u_numerical = self.solution.u[self.domain.ist:self.domain.ied].copy() + self.result = { + "x": domain.mesh.xcc, + "numerical": u_numerical, + "analytical": self.exact_solution(), + "config": { + "scheme": self.config.recon_scheme, + "order": self.config.spatial_order, + "rk_order": self.config.rk_order, + "final_time": self.config.final_time + } + } + + return u_numerical diff --git a/example/1d-linear-convection/weno3/julia/01g/python/time_integration.py b/example/1d-linear-convection/weno3/julia/01g/python/time_integration.py new file mode 100644 index 000000000..25ac0b4ca --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01g/python/time_integration.py @@ -0,0 +1,125 @@ +# time_integration.py + +""" +时间推进器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = cfd.residual_calculator + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(使用装饰器注册) ---------------------- + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class TimeIntegratorFactory: + """时间推进器工厂""" + + @staticmethod + def create(cfd) -> 'TimeIntegrator': + """ + 创建时间推进器实例 + + Args: + cfd: CFD对象 + + Returns: + 时间推进器实例 + """ + from factories.base_factory import BaseFactory + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01g/python/u_dirichlet_py.npy b/example/1d-linear-convection/weno3/julia/01g/python/u_dirichlet_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..3aa20bd2e395654a6e8c59bbbc9f5eaa955b345e GIT binary patch literal 480 zcmbWzxeftg6o%oW6O}@tQ2Y_`BNMTVeHmMHM59p2#LPq?$c$)2;vU?f%TQY8ZM07F zov8Zw0H1NzP(=`e8>IlQ+pS*SsNB7-1(=_si zx~l&#Cf|Elzsex&D8?~?Nlc-_G-fc1IV_-oMJ!<%D_BJnYgoqyHnD|m>|hsr*hdQo Wp*NHvjWVK9#x%-=MtSnjr}YA`88kxx literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01g/python/u_gaussian_full_py.npy b/example/1d-linear-convection/weno3/julia/01g/python/u_gaussian_full_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..b762f0fe295b52a9a176817021995ce5a561d778 GIT binary patch literal 480 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yCOVor3bhL411<(MU?Ou)P3Oi_+soE}ylQv)*=cg#Ue98-%FeWXxBn~o z-*&0B&Pq*ej`k{D0z3w9EA6|!ORWz4wA}u9Vfdl$E2r%H!VVg&w0>m2nCbZM)%@?^ zbQ@G0rY;|Sof!j>qBKc_8=pP9zb3SzgEt8=WEn__NZ z$*8-a{Z)*4!nvvF3 wqbFAl+ziF%o9|Zhkuvbx>B0B<*(1H@$~rUDs~6~AeE<2_q`W{KwBTd_0INu`7ytkO literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01g/python/u_gaussian_interior_py.npy b/example/1d-linear-convection/weno3/julia/01g/python/u_gaussian_interior_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..68af8954e7a17e69b6b2e6954b78ee542d4e96a0 GIT binary patch literal 448 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#y20EHL3bhL411=Mpb80#_p4wiv{^M1<)6Y(m^Y(fcvsHGc?YsS7$^W)X zt#wvvVso@t=@Q^Ecw1@T^<8Rp;HTyG#|y&`bzeDU-xqe!V5RjV`^8Mhf3N0$2dCSh z;xKjjP<`JzA?Co${oe+$2WBtK9e1T6?wYnbA&=>Lk?qOJ*(aWE{cN*=zyHlG=1iNl zN(soJS7TeOgcr6%N&Go&S^Ug2epV2>rCgn3z1$RY3rj}b z1?{h5%p2Bhd1c#FYWC{z!qS+GX48p_*$(R2wVEWXJEgqJq{jHxHq-S&9M{! literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01g/python/u_input.npy b/example/1d-linear-convection/weno3/julia/01g/python/u_input.npy new file mode 100644 index 0000000000000000000000000000000000000000..ef506fa75cacbbe63dfcc17332ddd6c33de5b888 GIT binary patch literal 480 zcmbWrI}gE70EXdX6O+MUFr0`uNFy$FzqBs35sSejjUF+GN-GwT_z(W5pP?kM2cBT{2ChY{-ZF ze<|^^w?*2qNlaq~vzSAPc`RTNOISezt60N2Hn52%wy=#I>|zi5IKUx}aEun(u}>5e R8pV`GF{4q;X%r6a*dIEnGEe{j literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01g/python/u_neumann_py.npy b/example/1d-linear-convection/weno3/julia/01g/python/u_neumann_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..fa723d1efce906983b6e6710379207d15afbc271 GIT binary patch literal 480 zcmbWrI}gE70EXdX6O+MUFr0`uC5^ZgRkymtMl1%CG}^`>Dy>*V;y?JqN?(i7w>|hs7?4gBy9N-W~I7U11fnvf( PG3BF}@lnkAKgRt9WDhZ? literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01g/python/u_periodic_py.npy b/example/1d-linear-convection/weno3/julia/01g/python/u_periodic_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..156aff85dfa4c84deaaed404e0dccc37937f8836 GIT binary patch literal 480 zcmbWzyAA+Jw5Jw7YF-Tj}jFJcs^aeGCKh-lzUPGOs<_`6u&g%W>?2t#0c7 zOUUOY-iJGid|Tp}#1y76g9@{l!#rA8L>o(3#tK%kh7Q)TflX{-8#~xV7kk*p0eWBW QC_@@$M5Bypl;8h*0k_X5-%Mo@PtK#WJA?o%og6P}-0bsJ$1U?l6M7O94XfzYY=Kp9oR6e-T9A{tpmy_6tDF-R}f( z-+mW}`}RZKw;$@h{Tm?e+z)l%epjeEd#FAks5zgY=FW!NlLxi;2-F?cP8 literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01h/julia/boundary.jl b/example/1d-linear-convection/weno3/julia/01h/julia/boundary.jl new file mode 100644 index 000000000..5b0baaf43 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/julia/boundary.jl @@ -0,0 +1,90 @@ +# julia/boundary.jl +# 目标:与 Python boundary.py 行为完全一致(1:1 同构) +# 前提:ist/ied 在 Julia 中按 1-based 设置(ist = nghosts + 1) + +using NPZ # 仅用于测试,实际模块可不依赖 + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象基类(Julia 风格:用函数接口) ---------------------- +# Julia 无 abstract class,用文档+约定 +# 所有子类型必须实现 apply!(bc, u) + +# ---------------------- PeriodicBoundary ---------------------- +mutable struct PeriodicBoundary + cfd::Any +end + +function apply!(self::PeriodicBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist # 1-based, e.g., 3 + ied = self.cfd.domain.ied # 1-based, e.g., 43 + + # 左 ghost: u[ist - 1 - ig] = u[ied - 1 - ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ied - 1 - ig] + end + # 右 ghost: u[ied + ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ied + ig] = u[ist + ig] + end +end + +# ---------------------- DirichletBoundary ---------------------- +mutable struct DirichletBoundary + cfd::Any +end + +function apply!(self::DirichletBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + left_value = getfield_safe(self.cfd.config, :left_boundary_value, 1.0) + right_value = getfield_safe(self.cfd.config, :right_boundary_value, 2.0) + + # 左边界 + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = left_value + end + # 右边界 + for ig in 0:(nghosts-1) + u[ied + ig] = right_value + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Dirichlet边界: 左值=$left_value, 右值=$right_value") + end +end + +# ---------------------- NeumannBoundary ---------------------- +mutable struct NeumannBoundary + cfd::Any +end + +function apply!(self::NeumannBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + # 左边界零梯度:u[ist - 1 - ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ist + ig] + end + # 右边界零梯度:u[ied + ig] = u[ied - 1 - ig] ← 注意是 ied - 1 - ig + for ig in 0:(nghosts-1) + u[ied + ig] = u[ied - 1 - ig] + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Neumann边界: 零梯度") + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/julia/domain.jl b/example/1d-linear-convection/weno3/julia/01h/julia/domain.jl new file mode 100644 index 000000000..a7edc2260 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/julia/domain.jl @@ -0,0 +1,61 @@ +# julia/domain.jl +""" +Domain 结构体(与 domain.py 完全同构) +- 保存 config +- nghosts 计算逻辑 1:1 +- ist/ied 为 0-based(与 Python 一致) +""" + +include("mesh.jl") + +mutable struct Domain + config::Any # 保存 config(与 Python 一致) + mesh::Mesh + nghosts::Int + ist::Int # 0-based + ied::Int # 0-based + ntcells::Int +end + +function Domain(config::Any, mesh::Mesh) + nghosts = _calc_nghosts(config) + ist = nghosts + 1 # ← 1-based 起始索引 + ied = ist + mesh.ncells # ← 1-based 结束索引(不包含) + ntcells = mesh.ncells + 2 * nghosts + Domain(config, mesh, nghosts, ist, ied, ntcells) +end + +function _calc_nghosts(config::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + order = getfield_safe(config, :spatial_order, 2) + + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + if scheme == "eno" + nghosts = order + elseif startswith(scheme, "weno") + nghosts = order ÷ 2 + 1 + else + error("未知重建格式 $(scheme),无法计算ghost层!") + end + + if nghosts <= 0 + error("计算得到的ghost层数量无效:$(nghosts)(阶数$(order),格式$(scheme))") + end + + return nghosts +end + +function is_physical_cell(domain::Domain, idx::Int) + return domain.ist <= idx < domain.ied +end + +function get_physical_indices(domain::Domain) + return domain.ist:(domain.ied - 1) +end + +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/julia/flux.jl b/example/1d-linear-convection/weno3/julia/01h/julia/flux.jl new file mode 100644 index 000000000..047598f7a --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/julia/flux.jl @@ -0,0 +1,70 @@ +# julia/flux.jl +""" +通量计算器模块(与 flux.py 完全同构) +- 抽象基类 + 具体实现 +- 字段:cfd, config, mesh, wave_speed +""" + +include("mesh.jl") + +# ---------------------- 抽象基类 ---------------------- +""" +InviscidFluxCalculator 抽象类型 +Julia 无 ABC,用文档约定 +所有子类型必须实现 compute! +""" +abstract type InviscidFluxCalculator end + +# ---------------------- RusanovFluxCalculator ---------------------- +mutable struct RusanovFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function RusanovFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::RusanovFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = calc.wave_speed + c_R = calc.wave_speed + F_L = c_L * u_L + F_R = c_R * u_R + Smax = max(abs(c_L), abs(c_R)) + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + end +end + +# ---------------------- EngquistOsherFluxCalculator ---------------------- +mutable struct EngquistOsherFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function EngquistOsherFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::EngquistOsherFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + c = calc.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/julia/initial_condition.jl b/example/1d-linear-convection/weno3/julia/01h/julia/initial_condition.jl new file mode 100644 index 000000000..5e029e8d2 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/julia/initial_condition.jl @@ -0,0 +1,86 @@ +# julia/initial_condition.jl +""" +初始条件模块(Julia 版) +目标:与 Python initial_condition.py 行为完全一致 +""" + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象接口(Julia 无继承,用函数约定) ---------------------- +# 所有初始条件必须实现: +# evaluate_at(ic, x::AbstractVector{Float64}) -> Vector{Float64} +# apply(ic, solution) + +# ---------------------- StepFunctionIC ---------------------- +struct StepFunctionIC + config::Any +end + +function evaluate_at(ic::StepFunctionIC, x::AbstractVector{Float64}) + u0 = ones(Float64, length(x)) + for i in eachindex(x) + if 0.5 <= x[i] <= 1.0 + u0[i] = 2.0 + end + end + return u0 +end + +function apply(ic::StepFunctionIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- SineWaveIC ---------------------- +struct SineWaveIC + config::Any +end + +function evaluate_at(ic::SineWaveIC, x::AbstractVector{Float64}) + L = getfield_safe(ic.config, :domain_length, 2.0) + return sin.(2π * x / L) +end + +function apply(ic::SineWaveIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- GaussianPulseIC ---------------------- +struct GaussianPulseIC + config::Any +end + +function evaluate_at(ic::GaussianPulseIC, x::AbstractVector{Float64}) + center = getfield_safe(ic.config, :pulse_center, 0.5) + width = getfield_safe(ic.config, :pulse_width, 0.1) + return exp.(-((x .- center) ./ width).^2) +end + +function apply(ic::GaussianPulseIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- 公共辅助函数 ---------------------- +""" +将初始场 values 应用到 solution.u 的物理区域(含 ghost 的数组) +""" +function _apply_to_interior(solution, values) + domain = solution.domain + # Python: for i in range(domain.ist, domain.ied) + # Julia: for i in domain.ist:domain.ied-1 (因为 ied 是结束索引,不包含) + for i in domain.ist:(domain.ied - 1) + j = i - domain.ist + 1 # values 是 1-based,i - ist 是 0-based → +1 + solution.u[i] = values[j] + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/julia/mesh.jl b/example/1d-linear-convection/weno3/julia/01h/julia/mesh.jl new file mode 100644 index 000000000..1b0dd4bf9 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/julia/mesh.jl @@ -0,0 +1,45 @@ +# julia/mesh.jl +""" +Mesh 结构体(与提供的 mesh.py 完全同构) +- 字段名、初始化顺序、循环逻辑完全一致 +- 不做任何泛化或优化 +""" + +mutable struct Mesh + xmin::Float64 + xmax::Float64 + ncells::Int + nnodes::Int + nx::Int + x::Vector{Float64} + xcc::Vector{Float64} + L::Float64 # 在 init_mesh 中赋值 + dx::Float64 # 在 init_mesh 中赋值 + + function Mesh() + # 与 Python __init__ 完全一致 + xmin = 0.0 + xmax = 2.0 + ncells = 40 + nnodes = ncells + 1 + nx = ncells + x = zeros(Float64, nnodes) + xcc = zeros(Float64, ncells) + + # 调用 init_mesh(模拟 Python) + L = xmax - xmin + dx = L / ncells + + # Generate node coordinates: for i in range(self.nnodes) + for i in 1:nnodes + x[i] = xmin + (i - 1) * dx # Python i → Julia i-1 + end + + # Generate cell center coordinates + for i in 1:ncells + xcc[i] = 0.5 * (x[i] + x[i+1]) + end + + new(xmin, xmax, ncells, nnodes, nx, x, xcc, L, dx) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/julia/reconstructor/weno3.jl b/example/1d-linear-convection/weno3/julia/01h/julia/reconstructor/weno3.jl new file mode 100644 index 000000000..2b6fe1abb --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/julia/reconstructor/weno3.jl @@ -0,0 +1,65 @@ +# julia/reconstructor/weno3.jl +""" +WENO3 重构器(与 reconstructor/weno3.py 完全同构) +""" + +mutable struct Weno3Reconstructor + # 无字段,与 Python 一致 +end + +function reconstruct(rec::Weno3Reconstructor, q::Vector{Float64}, cfd::Any) + domain = cfd.domain + solution = cfd.solution + _reconstruct_left_interfaces(domain, q, solution.q_face_left) + _reconstruct_right_interfaces(domain, q, solution.q_face_right) +end + +function _reconstruct_left_interfaces(domain, u, qL) + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in (domain.ist - 1):(domain.ied - 1) + j = i - (domain.ist - 1) + 1 # ← Julia 1-based: j = i - (ist-1) 对应 Python j = i - (ist-1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = _reconstruct_from_left_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_right_interfaces(domain, u, qR) + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in domain.ist:domain.ied + j = i - domain.ist + 1 # ← Julia 1-based: j = i - ist 对应 Python j = i - ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = _reconstruct_from_right_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_from_left_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -0.5*v1 + 1.5*v2 # r=1 stencil + q1 = 0.5*v2 + 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end + +function _reconstruct_from_right_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 0.5*v1 + 0.5*v2 # r=1 stencil + q1 = 1.5*v2 - 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/julia/residual.jl b/example/1d-linear-convection/weno3/julia/01h/julia/residual.jl new file mode 100644 index 000000000..15cc93878 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/julia/residual.jl @@ -0,0 +1,65 @@ +# julia/residual.jl +""" +残差计算器(与 residual.py 完全同构) +- 封装重建→通量→散度完整流程 +- 依赖 cfd 的多个字段 +""" + +include("mesh.jl") + +mutable struct ResidualCalculator + cfd::Any + config::Any + domain::Any + solution::Any + mesh::Mesh + reconstructor::Any + flux_calculator::Any # 通量计算器(外部传入,替代工厂) + + function ResidualCalculator(cfd::Any, flux_calculator::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + mesh = domain.mesh + reconstructor = cfd.reconstructor + + new(cfd, config, domain, solution, mesh, reconstructor, flux_calculator) + end +end + +""" +计算完整残差(对外唯一接口) +""" +function compute!(calc::ResidualCalculator) + #_reconstruct(calc) + _compute_inviscid_flux(calc) + _compute_flux_divergence(calc) +end + +""" +私有方法:界面值重建 +""" +function _reconstruct(calc::ResidualCalculator) + reconstruct(calc.reconstructor, calc.solution.u, calc.cfd) +end + +""" +私有方法:计算无粘通量 +""" +function _compute_inviscid_flux(calc::ResidualCalculator) + compute!(calc.flux_calculator, + calc.solution.q_face_left, + calc.solution.q_face_right, + calc.solution.flux) +end + +""" +私有方法:计算通量散度(残差 = -dF/dx) +""" +function _compute_flux_divergence(calc::ResidualCalculator) + solution = calc.solution + mesh = calc.mesh + for i in 1:mesh.ncells + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / mesh.dx + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/julia/solution.jl b/example/1d-linear-convection/weno3/julia/01h/julia/solution.jl new file mode 100644 index 000000000..90ca03931 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/julia/solution.jl @@ -0,0 +1,75 @@ +# julia/solution.jl +""" +Solution 结构体(与真实 solution.py 完全同构) +字段顺序、方法、逻辑 1:1 对齐 +""" + +include("mesh.jl") +include("domain.jl") +include("initial_condition.jl") + +mutable struct Solution + domain::Domain + q_face_left::Vector{Float64} + q_face_right::Vector{Float64} + flux::Vector{Float64} + res::Vector{Float64} + u::Vector{Float64} + un::Vector{Float64} + + function Solution(config::Any, domain::Domain) + mesh = domain.mesh + + q_face_left = zeros(Float64, mesh.nnodes) + q_face_right = zeros(Float64, mesh.nnodes) + flux = zeros(Float64, mesh.nnodes) + res = zeros(Float64, mesh.ncells) + u = zeros(Float64, domain.ntcells) + un = zeros(Float64, domain.ntcells) + + sol = new(domain, q_face_left, q_face_right, flux, res, u, un) + initialize_from_config(sol, config) + return sol + end +end + +""" +重置解数组为初始状态 +""" +function reset_solution(sol::Solution) + fill!(sol.u, 0.0) + fill!(sol.un, 0.0) +end + +""" +根据配置初始化场 +注:暂用硬编码替代 InitialConditionFactory +""" +function initialize_from_config(sol::Solution, config::Any) + ic_type = getfield_safe(config, :ic_type, "step") + + # 硬编码创建 IC(替代 InitialConditionFactory) + ic = if ic_type == "step" + StepFunctionIC(config) + elseif ic_type == "sin" + SineWaveIC(config) + elseif ic_type == "gaussian" + GaussianPulseIC(config) + else + error("未知初始条件类型: $ic_type") + end + + apply(ic, sol) # 调用 IC.apply +end + +""" +更新旧场:un = u +""" +function update_old_field(sol::Solution) + sol.un .= sol.u # 等价于 Python 的 un[:] = u[:] +end + +# ---------------------- 辅助函数 ---------------------- +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/julia/test/test_boundary.jl b/example/1d-linear-convection/weno3/julia/01h/julia/test/test_boundary.jl new file mode 100644 index 000000000..ef27d6820 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/julia/test/test_boundary.jl @@ -0,0 +1,57 @@ +# julia/test/test_boundary.jl +using NPZ +include("../boundary.jl") + +struct MockConfig + boundary_type::String + left_boundary_value::Float64 + right_boundary_value::Float64 + debug::Bool +end + +struct MockDomain + nghosts::Int + ist::Int # Julia 本地索引(1-based) + ied::Int + ntcells::Int +end + +struct MockCfd + config::MockConfig + domain::MockDomain +end + +# ===== 关键:使用 Julia 本地索引规则 ===== +nghosts = 2 +ncells = 40 +ist = nghosts + 1 # = 3 +ied = ist + ncells # = 43 +ntcells = ncells + 2 * nghosts # = 44 + +config = MockConfig("dirichlet", 0.5, 1.5, true) +domain = MockDomain(nghosts, ist, ied, ntcells) +cfd_mock = MockCfd(config, domain) + +# 加载 Python 生成的 u_input.npy +# 注意:Python u[0] → Julia u[1],所以内容完全对应 +u_input = npzread("../../python/u_input.npy") +@assert length(u_input) == ntcells "数组长度不匹配!" + +# 测试三种边界 +test_cases = [ + ("periodic", PeriodicBoundary(cfd_mock)), + ("dirichlet", DirichletBoundary(cfd_mock)), + ("neumann", NeumannBoundary(cfd_mock)) +] + +for (name, bc) in test_cases + u = copy(u_input) + apply!(bc, u) + + u_py = npzread("../../python/u_$(name)_py.npy") + err = maximum(abs.(u .- u_py)) + println("边界: $(name) | 最大误差: $(err)") + @assert err < 1e-12 "❌ $(name) 不一致!" +end + +println("✅ 所有边界条件测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/julia/test/test_domain.jl b/example/1d-linear-convection/weno3/julia/01h/julia/test/test_domain.jl new file mode 100644 index 000000000..7b423f774 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/julia/test/test_domain.jl @@ -0,0 +1,44 @@ +# julia/test/test_domain.jl +include("../mesh.jl") +include("../domain.jl") + +# MockConfig:模拟 Python CfdConfig +struct MockConfig + recon_scheme::String + spatial_order::Int +end + +# 测试 ENO +config_eno = MockConfig("eno", 2) +mesh = Mesh() +domain_eno = Domain(config_eno, mesh) + +println("ENO: nghosts = ", domain_eno.nghosts) # 2 +println("ENO: ist = ", domain_eno.ist) # 2 +println("ENO: ied = ", domain_eno.ied) # 42 +println("物理索引范围: ", collect(get_physical_indices(domain_eno))[1:3], " ... ", collect(get_physical_indices(domain_eno))[end-2:end]) + +# 测试 WENO(字符串 "weno") +config_weno = MockConfig("weno", 2) +domain_weno = Domain(config_weno, mesh) +println("WENO: nghosts = ", domain_weno.nghosts) # 2 + +# 测试 WENO3(字符串 "weno3") +config_weno3 = MockConfig("weno3", 2) +domain_weno3 = Domain(config_weno3, mesh) +println("WENO3: nghosts = ", domain_weno3.nghosts) # 2 + +# 测试 is_physical_cell +@assert is_physical_cell(domain_eno, 2) == true # ist=2 +@assert is_physical_cell(domain_eno, 41) == true # ied-1=41 +@assert is_physical_cell(domain_eno, 42) == false # ied=42 + +# ✅ 断言 +@assert domain_eno.nghosts == 2 +@assert domain_eno.ist == 2 +@assert domain_eno.ied == 42 +@assert domain_eno.ntcells == 44 +@assert domain_weno.nghosts == 2 +@assert domain_weno3.nghosts == 2 + +println("✅ Domain 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/julia/test/test_flux.jl b/example/1d-linear-convection/weno3/julia/01h/julia/test/test_flux.jl new file mode 100644 index 000000000..0ebc1dc05 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/julia/test/test_flux.jl @@ -0,0 +1,53 @@ +# julia/test/test_flux.jl +include("../mesh.jl") +include("../flux.jl") + +# Mock 对象 +struct MockConfig + flux_type::String + wave_speed::Float64 +end + +struct MockDomain + mesh::Mesh +end + +struct MockCfd + config::MockConfig + domain::MockDomain +end + +# 创建 mock +mesh = Mesh() +config = MockConfig("rusanov", 1.0) +domain = MockDomain(mesh) +cfd = MockCfd(config, domain) + +# 测试 Rusanov +rusanov = RusanovFluxCalculator(cfd) +N = mesh.nnodes +qL = [1.0, 2.0, 3.0, 4.0, 5.0, zeros(Float64, N-5)...] +qR = [2.0, 3.0, 4.0, 5.0, 6.0, zeros(Float64, N-5)...] +flux_rus = zeros(Float64, N) +compute!(rusanov, qL, qR, flux_rus) + +println("Rusanov flux[1] = ", flux_rus[1]) # 应为 1.0 +@assert abs(flux_rus[1] - 1.0) < 1e-12 + +# 测试 Engquist-Osher +eo = EngquistOsherFluxCalculator(cfd) +flux_eo = zeros(Float64, N) +compute!(eo, qL, qR, flux_eo) + +println("EO flux[1] = ", flux_eo[1]) # c=1 → cp=1, cm=0 → flux=1*1 + 0*2 = 1.0 +@assert abs(flux_eo[1] - 1.0) < 1e-12 + +# 测试 c = -1.0 +config_neg = MockConfig("rusanov", -1.0) +cfd_neg = MockCfd(config_neg, domain) +rusanov_neg = RusanovFluxCalculator(cfd_neg) +compute!(rusanov_neg, qL, qR, flux_rus) +println("Rusanov (c=-1) flux[1] = ", flux_rus[1]) # F_L=-1, F_R=-2, Smax=1 → flux = -1.5 -0.5*(-1) = -1.0 +@assert abs(flux_rus[1] - (-2.0)) < 1e-12 # ← 修正:-2.0 而非 -1.0 + +println("✅ Flux 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/julia/test/test_initial_condition.jl b/example/1d-linear-convection/weno3/julia/01h/julia/test/test_initial_condition.jl new file mode 100644 index 000000000..dc58691a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/julia/test/test_initial_condition.jl @@ -0,0 +1,45 @@ +# julia/test/test_initial_condition.jl +using NPZ + +# 包含初始条件模块 +include("../initial_condition.jl") + +# 构造 mock config(与 Python 完全一致) +struct MockConfig + ic_type::String + domain_length::Float64 + pulse_center::Float64 + pulse_width::Float64 +end + +# 生成与 Python Mesh.xcc 完全相同的 x 坐标 +function generate_xcc() + xmin, xmax = 0.0, 2.0 + ncells = 40 + dx = (xmax - xmin) / ncells + xcc = Vector{Float64}(undef, ncells) + for i in 1:ncells + xcc[i] = xmin + (i - 0.5) * dx # i-1 + 0.5 → i-0.5 + end + return xcc +end + +# 主测试 +xcc = generate_xcc() + +test_cases = [ + ("step", StepFunctionIC(MockConfig("step", 2.0, 0.5, 0.1))), + ("sin", SineWaveIC(MockConfig("sin", 2.0, 0.5, 0.1))), + ("gaussian", GaussianPulseIC(MockConfig("gaussian", 2.0, 0.5, 0.1))) +] + +for (name, ic) in test_cases + u_jl = evaluate_at(ic, xcc) + u_py = npzread("../../python/u_$(name)_interior_py.npy") + + err = maximum(abs.(u_jl .- u_py)) + println("IC: $(name) | 最大误差: $(err)") + @assert err < 1e-12 "❌ $(name) 不一致!" +end + +println("✅ 所有初始条件测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/julia/test/test_mesh.jl b/example/1d-linear-convection/weno3/julia/01h/julia/test/test_mesh.jl new file mode 100644 index 000000000..315d2aac2 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/julia/test/test_mesh.jl @@ -0,0 +1,37 @@ +# julia/test/test_mesh.jl +include("../mesh.jl") + +# 创建 mesh(与 Python 完全相同) +mesh = Mesh() + +# 打印关键值(与 Python 对比) +println("xmin = ", mesh.xmin) # 0.0 +println("xmax = ", mesh.xmax) # 2.0 +println("ncells = ", mesh.ncells) # 40 +println("nnodes = ", mesh.nnodes) # 41 +println("nx = ", mesh.nx) # 40 +println("L = ", mesh.L) # 2.0 +println("dx = ", mesh.dx) # 0.05 + +# 检查 x[1] (Python x[0]) 和 x[41] (Python x[40]) +println("x[1] = ", mesh.x[1]) # 0.0 +println("x[41] = ", mesh.x[41]) # 2.0 + +# 检查 xcc[1] (Python xcc[0]) 和 xcc[40] (Python xcc[39]) +println("xcc[1] = ", mesh.xcc[1]) # 0.025 +println("xcc[40] = ", mesh.xcc[40]) # 1.975 + +# ✅ 严格断言 +@assert mesh.xmin == 0.0 +@assert mesh.xmax == 2.0 +@assert mesh.ncells == 40 +@assert mesh.nnodes == 41 +@assert mesh.nx == 40 +@assert mesh.L == 2.0 +@assert mesh.dx == 0.05 +@assert mesh.x[1] == 0.0 +@assert mesh.x[41] == 2.0 +@assert abs(mesh.xcc[1] - 0.025) < 1e-12 +@assert abs(mesh.xcc[40] - 1.975) < 1e-12 + +println("✅ Mesh 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/julia/test/test_residual.jl b/example/1d-linear-convection/weno3/julia/01h/julia/test/test_residual.jl new file mode 100644 index 000000000..e37e8d2bd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/julia/test/test_residual.jl @@ -0,0 +1,53 @@ +# julia/test/test_residual.jl +include("../mesh.jl") +include("../domain.jl") +include("../solution.jl") +include("../flux.jl") +include("../residual.jl") + +# ===== Dummy Reconstructor ===== +struct DummyReconstructor end + +# ===== Mock Config ===== +struct MockConfig + flux_type::String + wave_speed::Float64 +end + +# ===== Mock CFD (必须包含 reconstructor 字段) ===== +struct MockCfd + config::MockConfig + domain::Domain + solution::Solution + reconstructor::DummyReconstructor # ← 关键:添加此字段 +end + +# ===== 主测试 ===== +config = MockConfig("rusanov", 1.0) +mesh = Mesh() +domain = Domain((recon_scheme="eno", spatial_order=2), mesh) +solution = Solution((ic_type="step",), domain) + +# 手动设置界面值(跳过重建) +for i in 1:mesh.nnodes + solution.q_face_left[i] = Float64(i) * 0.1 + solution.q_face_right[i] = Float64(i) * 0.1 +end + +flux_calc = RusanovFluxCalculator((config=config, domain=domain)) +dummy_recon = DummyReconstructor() +cfd = MockCfd(config, domain, solution, dummy_recon) + +# 创建残差计算器 +res_calc = ResidualCalculator(cfd, flux_calc) + +# 计算残差(注意:_reconstruct 仍被注释) +compute!(res_calc) + +println("flux[1] = ", solution.flux[1]) +println("res[1] = ", solution.res[1]) + +@assert abs(solution.flux[1] - 0.1) < 1e-12 +@assert abs(solution.res[1] - (-2.0)) < 1e-12 + +println("✅ Residual 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/julia/test/test_solution.jl b/example/1d-linear-convection/weno3/julia/01h/julia/test/test_solution.jl new file mode 100644 index 000000000..f44ff0f0e --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/julia/test/test_solution.jl @@ -0,0 +1,42 @@ +# julia/test/test_solution.jl +include("../mesh.jl") +include("../domain.jl") +include("../solution.jl") + +# MockConfig +struct MockConfig + recon_scheme::String + spatial_order::Int + ic_type::String + domain_length::Float64 + pulse_center::Float64 + pulse_width::Float64 +end + +# 创建 solution +config = MockConfig("eno", 2, "step", 2.0, 0.5, 0.1) +mesh = Mesh() +domain = Domain(config, mesh) +sol = Solution(config, domain) + +# 检查字段尺寸 +@assert length(sol.q_face_left) == mesh.nnodes # 41 +@assert length(sol.flux) == mesh.nnodes # 41 +@assert length(sol.res) == mesh.ncells # 40 +@assert length(sol.u) == domain.ntcells # 44 + +# 检查初始场 +println("u[3] (物理起始): ", sol.u[3]) # 应为 1.0 +println("u[23] (x=1.0): ", sol.u[23]) # 应为 2.0 + +# 测试 update_old_field +sol.u[3] = 999.0 +update_old_field(sol) +@assert sol.un[3] == 999.0 + +# 测试 reset_solution +reset_solution(sol) +@assert sol.u[3] == 0.0 +@assert sol.un[3] == 0.0 + +println("✅ Solution 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/julia/test/test_time_integration.jl b/example/1d-linear-convection/weno3/julia/01h/julia/test/test_time_integration.jl new file mode 100644 index 000000000..091a8637c --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/julia/test/test_time_integration.jl @@ -0,0 +1,53 @@ +# julia/test/test_time_integration.jl +include("../time_integration.jl") +include("../flux.jl") +include("../boundary.jl") + +# Mock CFD 对象 +struct MockCfd + config::Any + domain::Domain + solution::Solution + residual_calculator::Any + boundary_condition::Any +end + +# 创建完整链路 +mesh = Mesh() +domain = Domain((recon_scheme="eno", spatial_order=2), mesh) +solution = Solution((ic_type="step",), domain) + +# 手动设置界面值(跳过 reconstructor) +for i in 1:mesh.nnodes + solution.q_face_left[i] = Float64(i) * 0.1 + solution.q_face_right[i] = Float64(i) * 0.1 +end + +# Mock components +flux_calc = RusanovFluxCalculator((config=(wave_speed=1.0,), domain=domain)) +res_calc = ResidualCalculator((config=nothing, domain=domain, solution=solution, reconstructor=nothing), flux_calc) +bc = PeriodicBoundary((domain=domain, config=(debug=false,))) +cfd = MockCfd((rk_order=1,), domain, solution, res_calc, bc) + +# 测试 RK1 +rk1 = RK1Integrator(cfd) +step(rk1, 0.025) + +println("RK1 step completed") +@assert solution.u[3] != 0.0 # 应已更新 + +# 测试 RK2 +cfd2 = MockCfd((rk_order=2,), domain, solution, res_calc, bc) +rk2 = RK2Integrator(cfd2) +step(rk2, 0.025) + +println("RK2 step completed") + +# 测试 RK3 +cfd3 = MockCfd((rk_order=3,), domain, solution, res_calc, bc) +rk3 = RK3Integrator(cfd3) +step(rk3, 0.025) + +println("RK3 step completed") + +println("✅ Time Integration 逻辑测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/julia/test/test_weno3.jl b/example/1d-linear-convection/weno3/julia/01h/julia/test/test_weno3.jl new file mode 100644 index 000000000..9a91761e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/julia/test/test_weno3.jl @@ -0,0 +1,47 @@ +# julia/test/test_weno3.jl +include("../mesh.jl") +include("../domain.jl") +include("../solution.jl") +include("../reconstructor/weno3.jl") + +# Mock CFD +struct MockCfd + domain::Domain + solution::Solution +end + +# 创建数据 +mesh = Mesh() +# 注意:Domain 现在使用 1-based 索引(ist = nghosts + 1) +domain = Domain((recon_scheme="weno3", spatial_order=2), mesh) +solution = Solution((ic_type="step",), domain) + +# 设置 u(物理区域 + ghost) +u = solution.u +for i in 1:domain.ntcells + u[i] = Float64(i-1) * 0.1 # u = [0.0, 0.1, 0.2, ..., 4.3] +end + +# 创建 reconstructor +weno3 = Weno3Reconstructor() +cfd = MockCfd(domain, solution) + +# 重建 +reconstruct(weno3, u, cfd) + +println("q_face_left length = ", length(solution.q_face_left)) # 应为 41 +println("q_face_right length = ", length(solution.q_face_right)) # 应为 41 +println("q_face_left[1] = ", solution.q_face_left[1]) +println("q_face_right[1] = ", solution.q_face_right[1]) + +# 验证左界面值(i = ist-1 = 2, j = 1) +# v1 = u[1] = 0.0, v2 = u[2] = 0.1, v3 = u[3] = 0.2 +# qL = w0*(-0.5*0.0 + 1.5*0.1) + w1*(0.5*0.1 + 0.5*0.2) = w0*0.15 + w1*0.15 = 0.15 +@assert abs(solution.q_face_left[1] - 0.15) < 1e-12 + +# 验证右界面值(i = ist = 3, j = 1) +# v1 = u[2] = 0.1, v2 = u[3] = 0.2, v3 = u[4] = 0.3 +# qR = w0*(0.5*0.1 + 0.5*0.2) + w1*(1.5*0.2 - 0.5*0.3) = w0*0.15 + w1*0.15 = 0.15 +@assert abs(solution.q_face_right[1] - 0.15) < 1e-12 + +println("✅ WENO3 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/julia/test_residual.jl b/example/1d-linear-convection/weno3/julia/01h/julia/test_residual.jl new file mode 100644 index 000000000..315d2aac2 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/julia/test_residual.jl @@ -0,0 +1,37 @@ +# julia/test/test_mesh.jl +include("../mesh.jl") + +# 创建 mesh(与 Python 完全相同) +mesh = Mesh() + +# 打印关键值(与 Python 对比) +println("xmin = ", mesh.xmin) # 0.0 +println("xmax = ", mesh.xmax) # 2.0 +println("ncells = ", mesh.ncells) # 40 +println("nnodes = ", mesh.nnodes) # 41 +println("nx = ", mesh.nx) # 40 +println("L = ", mesh.L) # 2.0 +println("dx = ", mesh.dx) # 0.05 + +# 检查 x[1] (Python x[0]) 和 x[41] (Python x[40]) +println("x[1] = ", mesh.x[1]) # 0.0 +println("x[41] = ", mesh.x[41]) # 2.0 + +# 检查 xcc[1] (Python xcc[0]) 和 xcc[40] (Python xcc[39]) +println("xcc[1] = ", mesh.xcc[1]) # 0.025 +println("xcc[40] = ", mesh.xcc[40]) # 1.975 + +# ✅ 严格断言 +@assert mesh.xmin == 0.0 +@assert mesh.xmax == 2.0 +@assert mesh.ncells == 40 +@assert mesh.nnodes == 41 +@assert mesh.nx == 40 +@assert mesh.L == 2.0 +@assert mesh.dx == 0.05 +@assert mesh.x[1] == 0.0 +@assert mesh.x[41] == 2.0 +@assert abs(mesh.xcc[1] - 0.025) < 1e-12 +@assert abs(mesh.xcc[40] - 1.975) < 1e-12 + +println("✅ Mesh 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/julia/time_integration.jl b/example/1d-linear-convection/weno3/julia/01h/julia/time_integration.jl new file mode 100644 index 000000000..95e5cd67f --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/julia/time_integration.jl @@ -0,0 +1,130 @@ +# julia/time_integration.jl +""" +时间推进器模块(与 time_integration.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("residual.jl") +include("boundary.jl") + +# ---------------------- 抽象时间推进器基类 ---------------------- +abstract type TimeIntegrator end + +mutable struct TimeIntegratorBase <: TimeIntegrator + cfd::Any + config::Any + domain::Domain + solution::Solution + residual_calculator::Any # ResidualCalculator +end + +function TimeIntegratorBase(cfd::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + residual_calculator = cfd.residual_calculator + TimeIntegratorBase(cfd, config, domain, solution, residual_calculator) +end + +function compute_residual(integrator::TimeIntegratorBase) + compute!(integrator.residual_calculator) +end + +function apply_boundary(integrator::TimeIntegratorBase) + apply!(integrator.cfd.boundary_condition, integrator.solution.u) +end + +function map_idx(integrator::TimeIntegratorBase, i::Int) + return i - integrator.domain.ist + 1 # ← +1 转为 1-based +end + +# ---------------------- RK1Integrator ---------------------- +mutable struct RK1Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK1Integrator(cfd::Any) + RK1Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK1Integrator, dt::Float64) + compute_residual(integrator.base) + for i in integrator.base.domain.ist:(integrator.base.domain.ied - 1) + j = map_idx(integrator.base, i) + integrator.base.solution.u[i] += dt * integrator.base.solution.res[j] + end + apply_boundary(integrator.base) + update_old_field(integrator.base.solution) +end + +# ---------------------- RK2Integrator ---------------------- +mutable struct RK2Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK2Integrator(cfd::Any) + RK2Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK2Integrator, dt::Float64) + base = integrator.base + # 阶段1:预测步 + compute_residual(base) + u_pred = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u_pred[i] += dt * base.solution.res[j] + end + base.solution.u .= u_pred + apply_boundary(base) + # 阶段2:校正步 + compute_residual(base) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = 0.5 * base.solution.un[i] + 0.5 * base.solution.u[i] + 0.5 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end + +# ---------------------- RK3Integrator ---------------------- +mutable struct RK3Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK3Integrator(cfd::Any) + RK3Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK3Integrator, dt::Float64) + base = integrator.base + # 阶段1 + compute_residual(base) + u1 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u1[i] += dt * base.solution.res[j] + end + base.solution.u .= u1 + apply_boundary(base) + # 阶段2 + compute_residual(base) + u2 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u2[i] = 0.75 * base.solution.un[i] + 0.25 * base.solution.u[i] + 0.25 * dt * base.solution.res[j] + end + base.solution.u .= u2 + apply_boundary(base) + # 阶段3 + compute_residual(base) + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = c1 * base.solution.un[i] + c2 * base.solution.u[i] + c3 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/python/boundary.py b/example/1d-linear-convection/weno3/julia/01h/python/boundary.py new file mode 100644 index 000000000..6054f92de --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/python/boundary.py @@ -0,0 +1,103 @@ +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from factories.base_factory import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/python/cfd_registry.py b/example/1d-linear-convection/weno3/julia/01h/python/cfd_registry.py new file mode 100644 index 000000000..c12eb106c --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/python/cfd_registry.py @@ -0,0 +1,62 @@ +# cfd_registry.py +""" +CFD注册系统统一导入模块 +所有其他模块从这里导入注册系统 +""" + +import sys +import os + +# 添加当前目录到路径,确保能找到registry.py +current_dir = os.path.dirname(os.path.abspath(__file__)) +if current_dir not in sys.path: + sys.path.insert(0, current_dir) + +try: + # 尝试导入注册系统 + from registry import ComponentRegistry, register_component + REGISTRY_AVAILABLE = True +except ImportError: + # 如果找不到,提供简单的空实现 + print("⚠️ 警告: 未找到 registry.py,使用最小化回退实现") + + class ComponentRegistry: + """最小化的注册系统回退实现""" + _registries = {} + + @classmethod + def register(cls, category, name, component_class): + if category not in cls._registries: + cls._registries[category] = {} + cls._registries[category][name] = component_class + + @classmethod + def get(cls, category, name): + if category not in cls._registries: + raise ValueError(f"未知类别: {category}") + if name not in cls._registries[category]: + raise ValueError(f"未找到: {category}.{name}") + return cls._registries[category][name] + + @classmethod + def create(cls, category, name, *args, **kwargs): + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) + for cat, comps in cls._registries.items()} + + def register_component(category, name=None): + """简化的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + REGISTRY_AVAILABLE = False + +# 导出统一的接口 +__all__ = ['ComponentRegistry', 'register_component', 'REGISTRY_AVAILABLE'] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/python/config.py b/example/1d-linear-convection/weno3/julia/01h/python/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/python/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/python/domain.py b/example/1d-linear-convection/weno3/julia/01h/python/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/python/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/python/factories/base_factory.py b/example/1d-linear-convection/weno3/julia/01h/python/factories/base_factory.py new file mode 100644 index 000000000..7b8b6105e --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/python/factories/base_factory.py @@ -0,0 +1,49 @@ +# factories/base_factory.py +""" +工厂基类 - 提供统一的组件创建接口 +""" + +from cfd_registry import ComponentRegistry +from typing import Any, Optional + +class BaseFactory: + """工厂基类,提供统一的组件创建方法""" + + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + """ + 统一创建组件的方法 + + Args: + category: 组件类别(如'boundary', 'flux'等) + name: 组件名称(如'periodic', 'rusanov'等) + *args, **kwargs: 传递给构造函数的参数 + + Returns: + 创建的组件实例 + + Raises: + ValueError: 如果组件不存在 + """ + name_lower = name.lower() + + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + # 获取可用组件列表 + available = ComponentRegistry.list_all().get(category, []) + + # 构建友好的错误信息 + if available: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"可用类型:{available}") + else: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"({category}类别下没有注册任何组件)") + + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + """获取指定类别的可用组件列表""" + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/python/flux.py b/example/1d-linear-convection/weno3/julia/01h/python/flux.py new file mode 100644 index 000000000..5ac73aa8d --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/python/flux.py @@ -0,0 +1,74 @@ +# flux.py +""" +通量计算器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册,替代硬编码工厂 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象通量计算基类(统一接口) ---------------------- +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass + +# ---------------------- 2. 具体通量计算子类(使用装饰器注册) ---------------------- + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + +class FluxCalculatorFactory: + """通量计算器工厂""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD对象 + + Returns: + 通量计算器实例 + """ + from factories.base_factory import BaseFactory + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/python/gen_boundary_test_data.py b/example/1d-linear-convection/weno3/julia/01h/python/gen_boundary_test_data.py new file mode 100644 index 000000000..c7fc2a9c5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/python/gen_boundary_test_data.py @@ -0,0 +1,35 @@ +# python/gen_boundary_test_data.py +import numpy as np +from config import CfdConfig +from mesh import Mesh +from domain import Domain + +# 固定测试配置 +config = CfdConfig() +config.with_boundary("dirichlet", left_value=0.5, right_value=1.5) +config.debug = True + +mesh = Mesh() +domain = Domain(config, mesh) + +# 构造 mock CFD 对象(仅含 config + domain) +class MockCfd: + def __init__(self, config, domain): + self.config = config + self.domain = domain + +# 测试用 u:0,1,2,...,N-1 +u_input = np.arange(domain.ntcells, dtype=np.float64) +np.save("u_input.npy", u_input) + +# 测试每种边界 +from boundary import PeriodicBoundary, DirichletBoundary, NeumannBoundary + +for bc_name, bc_class in [("periodic", PeriodicBoundary), ("dirichlet", DirichletBoundary), ("neumann", NeumannBoundary)]: + u = u_input.copy() + cfd_mock = MockCfd(config, domain) + bc = bc_class(cfd_mock) + bc.apply(u) + np.save(f"u_{bc_name}_py.npy", u) + +print("✅ 测试数据已生成:u_*.npy") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/python/gen_ic_test_data.py b/example/1d-linear-convection/weno3/julia/01h/python/gen_ic_test_data.py new file mode 100644 index 000000000..69c2573b8 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/python/gen_ic_test_data.py @@ -0,0 +1,27 @@ +# python/gen_ic_test_data.py +import sys, os +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +import numpy as np +from config import CfdConfig +from mesh import Mesh +from domain import Domain +from solution import Solution + +# 固定 mesh +mesh = Mesh() +config = CfdConfig() + +# 测试三种 IC +for ic_type in ["step", "sin", "gaussian"]: + config.ic_type = ic_type + domain = Domain(config, mesh) + sol = Solution(config, domain) + + u_full = sol.u.copy() # 包含 ghost + u_interior = sol.u[domain.ist:domain.ied].copy() + + np.save(f"u_{ic_type}_full_py.npy", u_full) + np.save(f"u_{ic_type}_interior_py.npy", u_interior) + +print("✅ 初始条件测试数据已生成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/python/initial_condition.py b/example/1d-linear-convection/weno3/julia/01h/python/initial_condition.py new file mode 100644 index 000000000..047415b74 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/python/initial_condition.py @@ -0,0 +1,90 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, ic_type: str, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from factories.base_factory import BaseFactory + return BaseFactory.create_component('initial_condition', ic_type, config) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/python/mesh.py b/example/1d-linear-convection/weno3/julia/01h/python/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/python/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/python/plotter.py b/example/1d-linear-convection/weno3/julia/01h/python/plotter.py new file mode 100644 index 000000000..9f1a414fd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/python/plotter.py @@ -0,0 +1,107 @@ +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/python/reconstructor/__init__.py b/example/1d-linear-convection/weno3/julia/01h/python/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/python/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/python/reconstructor/base.py b/example/1d-linear-convection/weno3/julia/01h/python/reconstructor/base.py new file mode 100644 index 000000000..bbd638503 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/python/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def reconstruct(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/python/reconstructor/eno.py b/example/1d-linear-convection/weno3/julia/01h/python/reconstructor/eno.py new file mode 100644 index 000000000..c2fb385dd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/python/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def reconstruct(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/python/reconstructor/factory.py b/example/1d-linear-convection/weno3/julia/01h/python/reconstructor/factory.py new file mode 100644 index 000000000..efa4a1446 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/python/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from cfd_registry import ComponentRegistry + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from factories.base_factory import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/python/reconstructor/weno3.py b/example/1d-linear-convection/weno3/julia/01h/python/reconstructor/weno3.py new file mode 100644 index 000000000..bf68be509 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/python/reconstructor/weno3.py @@ -0,0 +1,59 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def reconstruct(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/julia/01h/python/registry.py b/example/1d-linear-convection/weno3/julia/01h/python/registry.py new file mode 100644 index 000000000..61be90500 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/python/registry.py @@ -0,0 +1,75 @@ +# registry.py (改进版) +""" +CFD组件注册系统 - 简洁高效版 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表""" + + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True # 控制是否打印注册信息 + + @classmethod + def set_verbose(cls, verbose: bool): + """设置是否打印注册信息""" + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + # 检查是否已注册 + if name in cls._registries[category]: + if cls._registries[category][name] == component_class: + # 相同类重复注册,静默跳过 + return + elif cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取组件类""" + if category not in cls._registries: + available = list(cls._registries.keys()) + raise ValueError(f"❌ 未知类别: {category} (可用类别: {available})") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + @classmethod + def get_count(cls) -> int: + """获取注册组件总数""" + return sum(len(components) for components in cls._registries.values()) + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/python/residual.py b/example/1d-linear-convection/weno3/julia/01h/python/residual.py new file mode 100644 index 000000000..b4d4d7dcc --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/python/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux import FluxCalculatorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + self.reconstructor = self.cfd.reconstructor + + # 初始化通量计算器(工厂创建) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._reconstruct() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _reconstruct(self): + """私有方法:界面值重建""" + self.reconstructor.reconstruct(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/python/run_eno_weno.py b/example/1d-linear-convection/weno3/julia/01h/python/run_eno_weno.py new file mode 100644 index 000000000..fd7f38744 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/python/run_eno_weno.py @@ -0,0 +1,52 @@ +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + #mesh = Mesh(ncells=100, L=2.0) + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行ENO3求解(使用你的链式调用) + print("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + config_eno3.with_reconstruction("eno", 3) # 显式指定3阶(也可省略,ENO默认3阶) + # 可选:覆盖默认值(如dt) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + cfd_eno3.run() # 求解并生成result字典 + + # 可选:快速验证ENO3结果 + # plotter.plot_quick(cfd_eno3.result, title="ENO3 Quick Check") + + # 3. 配置并运行WENO3求解(注意:WENO默认5阶,这里显式指定3阶) + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) # 显式指定3阶(默认是5阶) + # 可选:覆盖默认值 + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + cfd_weno3.run() + + # 4. 可选:保存结果(供离线绘图) + # cfd_eno3.save_result("eno3_result.npz") + # cfd_weno3.save_result("weno3_result.npz") + + # 5. 绘制ENO/WENO对比图 + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" # 可选:保存图片 + ) + +if __name__ == "__main__": + # 主程序入口 + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/python/solution.py b/example/1d-linear-convection/weno3/julia/01h/python/solution.py new file mode 100644 index 000000000..92a46d3f4 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/python/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config.ic_type, config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/python/solver.py b/example/1d-linear-convection/weno3/julia/01h/python/solver.py new file mode 100644 index 000000000..0d0b442da --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/python/solver.py @@ -0,0 +1,86 @@ +import numpy as np +import matplotlib.pyplot as plt +import inflect +from abc import ABC, abstractmethod + + +from flux import InviscidFluxCalculator, RusanovFluxCalculator, EngquistOsherFluxCalculator, FluxCalculatorFactory + +from boundary import BoundaryCondition, PeriodicBoundary, DirichletBoundary, NeumannBoundary, BoundaryConditionFactory + +from time_integration import TimeIntegrator, RK1Integrator, RK2Integrator, RK3Integrator, TimeIntegratorFactory + +from mesh import Mesh + +from reconstructor import ReconstructorFactory + +from initial_condition import InitialCondition, StepFunctionIC, SineWaveIC, GaussianPulseIC, InitialConditionFactory + +from domain import Domain +from solution import Solution + +from config import CfdConfig +from residual import ResidualCalculator + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, config, mesh): + self.config = config + self.domain = Domain(config, mesh) + self.solution = Solution(config, self.domain) + self.reconstructor = ReconstructorFactory.create(config, self.domain) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + def exact_solution(self): + """通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界""" + x = self.domain.mesh.xcc + T = self.config.final_time + c = self.config.wave_speed + L = self.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = InitialConditionFactory.create(self.config.ic_type, self.config) + return ic.evaluate_at(x_shifted) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + #runge_kutta(self) + self.integrator.step(dt) + t += dt + config.dt = dt_old + + # 整理标准化结果 + u_numerical = self.solution.u[self.domain.ist:self.domain.ied].copy() + self.result = { + "x": domain.mesh.xcc, + "numerical": u_numerical, + "analytical": self.exact_solution(), + "config": { + "scheme": self.config.recon_scheme, + "order": self.config.spatial_order, + "rk_order": self.config.rk_order, + "final_time": self.config.final_time + } + } + + return u_numerical diff --git a/example/1d-linear-convection/weno3/julia/01h/python/time_integration.py b/example/1d-linear-convection/weno3/julia/01h/python/time_integration.py new file mode 100644 index 000000000..25ac0b4ca --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01h/python/time_integration.py @@ -0,0 +1,125 @@ +# time_integration.py + +""" +时间推进器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = cfd.residual_calculator + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(使用装饰器注册) ---------------------- + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class TimeIntegratorFactory: + """时间推进器工厂""" + + @staticmethod + def create(cfd) -> 'TimeIntegrator': + """ + 创建时间推进器实例 + + Args: + cfd: CFD对象 + + Returns: + 时间推进器实例 + """ + from factories.base_factory import BaseFactory + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01h/python/u_dirichlet_py.npy b/example/1d-linear-convection/weno3/julia/01h/python/u_dirichlet_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..3aa20bd2e395654a6e8c59bbbc9f5eaa955b345e GIT binary patch literal 480 zcmbWzxeftg6o%oW6O}@tQ2Y_`BNMTVeHmMHM59p2#LPq?$c$)2;vU?f%TQY8ZM07F zov8Zw0H1NzP(=`e8>IlQ+pS*SsNB7-1(=_si zx~l&#Cf|Elzsex&D8?~?Nlc-_G-fc1IV_-oMJ!<%D_BJnYgoqyHnD|m>|hsr*hdQo Wp*NHvjWVK9#x%-=MtSnjr}YA`88kxx literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01h/python/u_gaussian_full_py.npy b/example/1d-linear-convection/weno3/julia/01h/python/u_gaussian_full_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..b762f0fe295b52a9a176817021995ce5a561d778 GIT binary patch literal 480 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yCOVor3bhL411<(MU?Ou)P3Oi_+soE}ylQv)*=cg#Ue98-%FeWXxBn~o z-*&0B&Pq*ej`k{D0z3w9EA6|!ORWz4wA}u9Vfdl$E2r%H!VVg&w0>m2nCbZM)%@?^ zbQ@G0rY;|Sof!j>qBKc_8=pP9zb3SzgEt8=WEn__NZ z$*8-a{Z)*4!nvvF3 wqbFAl+ziF%o9|Zhkuvbx>B0B<*(1H@$~rUDs~6~AeE<2_q`W{KwBTd_0INu`7ytkO literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01h/python/u_gaussian_interior_py.npy b/example/1d-linear-convection/weno3/julia/01h/python/u_gaussian_interior_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..68af8954e7a17e69b6b2e6954b78ee542d4e96a0 GIT binary patch literal 448 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#y20EHL3bhL411=Mpb80#_p4wiv{^M1<)6Y(m^Y(fcvsHGc?YsS7$^W)X zt#wvvVso@t=@Q^Ecw1@T^<8Rp;HTyG#|y&`bzeDU-xqe!V5RjV`^8Mhf3N0$2dCSh z;xKjjP<`JzA?Co${oe+$2WBtK9e1T6?wYnbA&=>Lk?qOJ*(aWE{cN*=zyHlG=1iNl zN(soJS7TeOgcr6%N&Go&S^Ug2epV2>rCgn3z1$RY3rj}b z1?{h5%p2Bhd1c#FYWC{z!qS+GX48p_*$(R2wVEWXJEgqJq{jHxHq-S&9M{! literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01h/python/u_input.npy b/example/1d-linear-convection/weno3/julia/01h/python/u_input.npy new file mode 100644 index 0000000000000000000000000000000000000000..ef506fa75cacbbe63dfcc17332ddd6c33de5b888 GIT binary patch literal 480 zcmbWrI}gE70EXdX6O+MUFr0`uNFy$FzqBs35sSejjUF+GN-GwT_z(W5pP?kM2cBT{2ChY{-ZF ze<|^^w?*2qNlaq~vzSAPc`RTNOISezt60N2Hn52%wy=#I>|zi5IKUx}aEun(u}>5e R8pV`GF{4q;X%r6a*dIEnGEe{j literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01h/python/u_neumann_py.npy b/example/1d-linear-convection/weno3/julia/01h/python/u_neumann_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..fa723d1efce906983b6e6710379207d15afbc271 GIT binary patch literal 480 zcmbWrI}gE70EXdX6O+MUFr0`uC5^ZgRkymtMl1%CG}^`>Dy>*V;y?JqN?(i7w>|hs7?4gBy9N-W~I7U11fnvf( PG3BF}@lnkAKgRt9WDhZ? literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01h/python/u_periodic_py.npy b/example/1d-linear-convection/weno3/julia/01h/python/u_periodic_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..156aff85dfa4c84deaaed404e0dccc37937f8836 GIT binary patch literal 480 zcmbWzyAA+Jw5Jw7YF-Tj}jFJcs^aeGCKh-lzUPGOs<_`6u&g%W>?2t#0c7 zOUUOY-iJGid|Tp}#1y76g9@{l!#rA8L>o(3#tK%kh7Q)TflX{-8#~xV7kk*p0eWBW QC_@@$M5Bypl;8h*0k_X5-%Mo@PtK#WJA?o%og6P}-0bsJ$1U?l6M7O94XfzYY=Kp9oR6e-T9A{tpmy_6tDF-R}f( z-+mW}`}RZKw;$@h{Tm?e+z)l%epjeEd#FAks5zgY=FW!NlLxi;2-F?cP8 literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01i/julia/boundary.jl b/example/1d-linear-convection/weno3/julia/01i/julia/boundary.jl new file mode 100644 index 000000000..5b0baaf43 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/julia/boundary.jl @@ -0,0 +1,90 @@ +# julia/boundary.jl +# 目标:与 Python boundary.py 行为完全一致(1:1 同构) +# 前提:ist/ied 在 Julia 中按 1-based 设置(ist = nghosts + 1) + +using NPZ # 仅用于测试,实际模块可不依赖 + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象基类(Julia 风格:用函数接口) ---------------------- +# Julia 无 abstract class,用文档+约定 +# 所有子类型必须实现 apply!(bc, u) + +# ---------------------- PeriodicBoundary ---------------------- +mutable struct PeriodicBoundary + cfd::Any +end + +function apply!(self::PeriodicBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist # 1-based, e.g., 3 + ied = self.cfd.domain.ied # 1-based, e.g., 43 + + # 左 ghost: u[ist - 1 - ig] = u[ied - 1 - ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ied - 1 - ig] + end + # 右 ghost: u[ied + ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ied + ig] = u[ist + ig] + end +end + +# ---------------------- DirichletBoundary ---------------------- +mutable struct DirichletBoundary + cfd::Any +end + +function apply!(self::DirichletBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + left_value = getfield_safe(self.cfd.config, :left_boundary_value, 1.0) + right_value = getfield_safe(self.cfd.config, :right_boundary_value, 2.0) + + # 左边界 + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = left_value + end + # 右边界 + for ig in 0:(nghosts-1) + u[ied + ig] = right_value + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Dirichlet边界: 左值=$left_value, 右值=$right_value") + end +end + +# ---------------------- NeumannBoundary ---------------------- +mutable struct NeumannBoundary + cfd::Any +end + +function apply!(self::NeumannBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + # 左边界零梯度:u[ist - 1 - ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ist + ig] + end + # 右边界零梯度:u[ied + ig] = u[ied - 1 - ig] ← 注意是 ied - 1 - ig + for ig in 0:(nghosts-1) + u[ied + ig] = u[ied - 1 - ig] + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Neumann边界: 零梯度") + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/julia/domain.jl b/example/1d-linear-convection/weno3/julia/01i/julia/domain.jl new file mode 100644 index 000000000..a7edc2260 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/julia/domain.jl @@ -0,0 +1,61 @@ +# julia/domain.jl +""" +Domain 结构体(与 domain.py 完全同构) +- 保存 config +- nghosts 计算逻辑 1:1 +- ist/ied 为 0-based(与 Python 一致) +""" + +include("mesh.jl") + +mutable struct Domain + config::Any # 保存 config(与 Python 一致) + mesh::Mesh + nghosts::Int + ist::Int # 0-based + ied::Int # 0-based + ntcells::Int +end + +function Domain(config::Any, mesh::Mesh) + nghosts = _calc_nghosts(config) + ist = nghosts + 1 # ← 1-based 起始索引 + ied = ist + mesh.ncells # ← 1-based 结束索引(不包含) + ntcells = mesh.ncells + 2 * nghosts + Domain(config, mesh, nghosts, ist, ied, ntcells) +end + +function _calc_nghosts(config::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + order = getfield_safe(config, :spatial_order, 2) + + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + if scheme == "eno" + nghosts = order + elseif startswith(scheme, "weno") + nghosts = order ÷ 2 + 1 + else + error("未知重建格式 $(scheme),无法计算ghost层!") + end + + if nghosts <= 0 + error("计算得到的ghost层数量无效:$(nghosts)(阶数$(order),格式$(scheme))") + end + + return nghosts +end + +function is_physical_cell(domain::Domain, idx::Int) + return domain.ist <= idx < domain.ied +end + +function get_physical_indices(domain::Domain) + return domain.ist:(domain.ied - 1) +end + +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/julia/flux.jl b/example/1d-linear-convection/weno3/julia/01i/julia/flux.jl new file mode 100644 index 000000000..047598f7a --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/julia/flux.jl @@ -0,0 +1,70 @@ +# julia/flux.jl +""" +通量计算器模块(与 flux.py 完全同构) +- 抽象基类 + 具体实现 +- 字段:cfd, config, mesh, wave_speed +""" + +include("mesh.jl") + +# ---------------------- 抽象基类 ---------------------- +""" +InviscidFluxCalculator 抽象类型 +Julia 无 ABC,用文档约定 +所有子类型必须实现 compute! +""" +abstract type InviscidFluxCalculator end + +# ---------------------- RusanovFluxCalculator ---------------------- +mutable struct RusanovFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function RusanovFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::RusanovFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = calc.wave_speed + c_R = calc.wave_speed + F_L = c_L * u_L + F_R = c_R * u_R + Smax = max(abs(c_L), abs(c_R)) + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + end +end + +# ---------------------- EngquistOsherFluxCalculator ---------------------- +mutable struct EngquistOsherFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function EngquistOsherFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::EngquistOsherFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + c = calc.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/julia/initial_condition.jl b/example/1d-linear-convection/weno3/julia/01i/julia/initial_condition.jl new file mode 100644 index 000000000..5e029e8d2 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/julia/initial_condition.jl @@ -0,0 +1,86 @@ +# julia/initial_condition.jl +""" +初始条件模块(Julia 版) +目标:与 Python initial_condition.py 行为完全一致 +""" + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象接口(Julia 无继承,用函数约定) ---------------------- +# 所有初始条件必须实现: +# evaluate_at(ic, x::AbstractVector{Float64}) -> Vector{Float64} +# apply(ic, solution) + +# ---------------------- StepFunctionIC ---------------------- +struct StepFunctionIC + config::Any +end + +function evaluate_at(ic::StepFunctionIC, x::AbstractVector{Float64}) + u0 = ones(Float64, length(x)) + for i in eachindex(x) + if 0.5 <= x[i] <= 1.0 + u0[i] = 2.0 + end + end + return u0 +end + +function apply(ic::StepFunctionIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- SineWaveIC ---------------------- +struct SineWaveIC + config::Any +end + +function evaluate_at(ic::SineWaveIC, x::AbstractVector{Float64}) + L = getfield_safe(ic.config, :domain_length, 2.0) + return sin.(2π * x / L) +end + +function apply(ic::SineWaveIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- GaussianPulseIC ---------------------- +struct GaussianPulseIC + config::Any +end + +function evaluate_at(ic::GaussianPulseIC, x::AbstractVector{Float64}) + center = getfield_safe(ic.config, :pulse_center, 0.5) + width = getfield_safe(ic.config, :pulse_width, 0.1) + return exp.(-((x .- center) ./ width).^2) +end + +function apply(ic::GaussianPulseIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- 公共辅助函数 ---------------------- +""" +将初始场 values 应用到 solution.u 的物理区域(含 ghost 的数组) +""" +function _apply_to_interior(solution, values) + domain = solution.domain + # Python: for i in range(domain.ist, domain.ied) + # Julia: for i in domain.ist:domain.ied-1 (因为 ied 是结束索引,不包含) + for i in domain.ist:(domain.ied - 1) + j = i - domain.ist + 1 # values 是 1-based,i - ist 是 0-based → +1 + solution.u[i] = values[j] + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/julia/mesh.jl b/example/1d-linear-convection/weno3/julia/01i/julia/mesh.jl new file mode 100644 index 000000000..1b0dd4bf9 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/julia/mesh.jl @@ -0,0 +1,45 @@ +# julia/mesh.jl +""" +Mesh 结构体(与提供的 mesh.py 完全同构) +- 字段名、初始化顺序、循环逻辑完全一致 +- 不做任何泛化或优化 +""" + +mutable struct Mesh + xmin::Float64 + xmax::Float64 + ncells::Int + nnodes::Int + nx::Int + x::Vector{Float64} + xcc::Vector{Float64} + L::Float64 # 在 init_mesh 中赋值 + dx::Float64 # 在 init_mesh 中赋值 + + function Mesh() + # 与 Python __init__ 完全一致 + xmin = 0.0 + xmax = 2.0 + ncells = 40 + nnodes = ncells + 1 + nx = ncells + x = zeros(Float64, nnodes) + xcc = zeros(Float64, ncells) + + # 调用 init_mesh(模拟 Python) + L = xmax - xmin + dx = L / ncells + + # Generate node coordinates: for i in range(self.nnodes) + for i in 1:nnodes + x[i] = xmin + (i - 1) * dx # Python i → Julia i-1 + end + + # Generate cell center coordinates + for i in 1:ncells + xcc[i] = 0.5 * (x[i] + x[i+1]) + end + + new(xmin, xmax, ncells, nnodes, nx, x, xcc, L, dx) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/julia/reconstructor/eno.jl b/example/1d-linear-convection/weno3/julia/01i/julia/reconstructor/eno.jl new file mode 100644 index 000000000..e78a636f3 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/julia/reconstructor/eno.jl @@ -0,0 +1,107 @@ +# julia/reconstructor/eno.jl +""" +ENO 重构器(与 reconstructor/eno.py 完全同构) +""" + +# ---------------------- ENO 系数初始化 ---------------------- +function _init_eno_coef!(spatial_order::Int, coef::Matrix{Float64}) + if spatial_order == 1 + coef[1, 1] = 1.0 + coef[2, 1] = 1.0 + elseif spatial_order == 2 + coef[1, 1:2] = [3.0/2.0, -1.0/2.0] + coef[2, 1:2] = [1.0/2.0, 1.0/2.0] + coef[3, 1:2] = [-1.0/2.0, 3.0/2.0] + elseif spatial_order == 3 + coef[1, 1:3] = [11.0/6.0, -7.0/6.0, 1.0/3.0] + coef[2, 1:3] = [1.0/3.0, 5.0/6.0, -1.0/6.0] + coef[3, 1:3] = [-1.0/6.0, 5.0/6.0, 1.0/3.0] + coef[4, 1:3] = [1.0/3.0, -7.0/6.0, 11.0/6.0] + elseif spatial_order == 4 + coef[1, 1:4] = [25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0] + coef[2, 1:4] = [1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0] + coef[3, 1:4] = [-1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0] + coef[4, 1:4] = [1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0] + coef[5, 1:4] = [-1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0] + elseif spatial_order == 5 + coef[1, 1:5] = [137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0] + coef[2, 1:5] = [1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0] + coef[3, 1:5] = [-1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0] + coef[4, 1:5] = [1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0] + coef[5, 1:5] = [-1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0] + coef[6, 1:5] = [1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0] + elseif spatial_order == 6 + coef[1, 1:6] = [49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0] + coef[2, 1:6] = [1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0] + coef[3, 1:6] = [-1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0] + coef[4, 1:6] = [1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0] + coef[5, 1:6] = [-1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0] + coef[6, 1:6] = [1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0] + coef[7, 1:6] = [-1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0] + elseif spatial_order == 7 + coef[1, 1:7] = [363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0] + coef[2, 1:7] = [1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0] + coef[3, 1:7] = [-1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0] + coef[4, 1:7] = [1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0] + coef[5, 1:7] = [-1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0] + coef[6, 1:7] = [1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0] + coef[7, 1:7] = [-1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0] + coef[8, 1:7] = [1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0] + else + error("ENO 系数未实现 order=$spatial_order") + end +end + +# ---------------------- ENO 重构器 ---------------------- +mutable struct EnoReconstructor + spatial_order::Int + ntcells::Int + lmc::Vector{Int} + coef::Matrix{Float64} + dd::Matrix{Float64} + + function EnoReconstructor(spatial_order::Int, ntcells::Int) + lmc = zeros(Int, ntcells) + coef = zeros(Float64, spatial_order + 1, spatial_order) + dd = zeros(Float64, spatial_order, ntcells) + _init_eno_coef!(spatial_order, coef) + new(spatial_order, ntcells, lmc, coef, dd) + end +end + +function reconstruct(rec::EnoReconstructor, q::Vector{Float64}, cfd::Any) + # 1. 差商计算 (dd[1,:] = q) + @views rec.dd[1, :] .= q + for m in 2:rec.spatial_order + for j in 1:(rec.ntcells - m + 1) + rec.dd[m, j] = rec.dd[m-1, j+1] - rec.dd[m-1, j] + end + end + + # 2. 选择 smoothest stencil + domain = cfd.domain + for i in (domain.ist - 1):(domain.ied) # Python: range(ist-1, ied+1) → ied+1-1 = ied + rec.lmc[i] = i + for m in 2:rec.spatial_order + if abs(rec.dd[m, rec.lmc[i] - 1]) < abs(rec.dd[m, rec.lmc[i]]) + rec.lmc[i] -= 1 + end + end + end + + # 3. 重构界面值 + solution = cfd.solution + for i in domain.ist:(domain.ied) # Python: range(ist, ied+1) → ied+1-1 = ied + j = i - domain.ist + 1 # Julia 1-based + k1 = rec.lmc[i - 1] + k2 = rec.lmc[i] + r1 = (i - 1) - k1 + 1 + r2 = i - k2 + 1 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in 1:rec.spatial_order + solution.q_face_left[j] += q[k1 + m - 1] * rec.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m - 1] * rec.coef[r2, m] + end + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/julia/reconstructor/weno3.jl b/example/1d-linear-convection/weno3/julia/01i/julia/reconstructor/weno3.jl new file mode 100644 index 000000000..2b6fe1abb --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/julia/reconstructor/weno3.jl @@ -0,0 +1,65 @@ +# julia/reconstructor/weno3.jl +""" +WENO3 重构器(与 reconstructor/weno3.py 完全同构) +""" + +mutable struct Weno3Reconstructor + # 无字段,与 Python 一致 +end + +function reconstruct(rec::Weno3Reconstructor, q::Vector{Float64}, cfd::Any) + domain = cfd.domain + solution = cfd.solution + _reconstruct_left_interfaces(domain, q, solution.q_face_left) + _reconstruct_right_interfaces(domain, q, solution.q_face_right) +end + +function _reconstruct_left_interfaces(domain, u, qL) + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in (domain.ist - 1):(domain.ied - 1) + j = i - (domain.ist - 1) + 1 # ← Julia 1-based: j = i - (ist-1) 对应 Python j = i - (ist-1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = _reconstruct_from_left_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_right_interfaces(domain, u, qR) + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in domain.ist:domain.ied + j = i - domain.ist + 1 # ← Julia 1-based: j = i - ist 对应 Python j = i - ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = _reconstruct_from_right_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_from_left_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -0.5*v1 + 1.5*v2 # r=1 stencil + q1 = 0.5*v2 + 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end + +function _reconstruct_from_right_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 0.5*v1 + 0.5*v2 # r=1 stencil + q1 = 1.5*v2 - 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/julia/residual.jl b/example/1d-linear-convection/weno3/julia/01i/julia/residual.jl new file mode 100644 index 000000000..15cc93878 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/julia/residual.jl @@ -0,0 +1,65 @@ +# julia/residual.jl +""" +残差计算器(与 residual.py 完全同构) +- 封装重建→通量→散度完整流程 +- 依赖 cfd 的多个字段 +""" + +include("mesh.jl") + +mutable struct ResidualCalculator + cfd::Any + config::Any + domain::Any + solution::Any + mesh::Mesh + reconstructor::Any + flux_calculator::Any # 通量计算器(外部传入,替代工厂) + + function ResidualCalculator(cfd::Any, flux_calculator::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + mesh = domain.mesh + reconstructor = cfd.reconstructor + + new(cfd, config, domain, solution, mesh, reconstructor, flux_calculator) + end +end + +""" +计算完整残差(对外唯一接口) +""" +function compute!(calc::ResidualCalculator) + #_reconstruct(calc) + _compute_inviscid_flux(calc) + _compute_flux_divergence(calc) +end + +""" +私有方法:界面值重建 +""" +function _reconstruct(calc::ResidualCalculator) + reconstruct(calc.reconstructor, calc.solution.u, calc.cfd) +end + +""" +私有方法:计算无粘通量 +""" +function _compute_inviscid_flux(calc::ResidualCalculator) + compute!(calc.flux_calculator, + calc.solution.q_face_left, + calc.solution.q_face_right, + calc.solution.flux) +end + +""" +私有方法:计算通量散度(残差 = -dF/dx) +""" +function _compute_flux_divergence(calc::ResidualCalculator) + solution = calc.solution + mesh = calc.mesh + for i in 1:mesh.ncells + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / mesh.dx + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/julia/solution.jl b/example/1d-linear-convection/weno3/julia/01i/julia/solution.jl new file mode 100644 index 000000000..90ca03931 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/julia/solution.jl @@ -0,0 +1,75 @@ +# julia/solution.jl +""" +Solution 结构体(与真实 solution.py 完全同构) +字段顺序、方法、逻辑 1:1 对齐 +""" + +include("mesh.jl") +include("domain.jl") +include("initial_condition.jl") + +mutable struct Solution + domain::Domain + q_face_left::Vector{Float64} + q_face_right::Vector{Float64} + flux::Vector{Float64} + res::Vector{Float64} + u::Vector{Float64} + un::Vector{Float64} + + function Solution(config::Any, domain::Domain) + mesh = domain.mesh + + q_face_left = zeros(Float64, mesh.nnodes) + q_face_right = zeros(Float64, mesh.nnodes) + flux = zeros(Float64, mesh.nnodes) + res = zeros(Float64, mesh.ncells) + u = zeros(Float64, domain.ntcells) + un = zeros(Float64, domain.ntcells) + + sol = new(domain, q_face_left, q_face_right, flux, res, u, un) + initialize_from_config(sol, config) + return sol + end +end + +""" +重置解数组为初始状态 +""" +function reset_solution(sol::Solution) + fill!(sol.u, 0.0) + fill!(sol.un, 0.0) +end + +""" +根据配置初始化场 +注:暂用硬编码替代 InitialConditionFactory +""" +function initialize_from_config(sol::Solution, config::Any) + ic_type = getfield_safe(config, :ic_type, "step") + + # 硬编码创建 IC(替代 InitialConditionFactory) + ic = if ic_type == "step" + StepFunctionIC(config) + elseif ic_type == "sin" + SineWaveIC(config) + elseif ic_type == "gaussian" + GaussianPulseIC(config) + else + error("未知初始条件类型: $ic_type") + end + + apply(ic, sol) # 调用 IC.apply +end + +""" +更新旧场:un = u +""" +function update_old_field(sol::Solution) + sol.un .= sol.u # 等价于 Python 的 un[:] = u[:] +end + +# ---------------------- 辅助函数 ---------------------- +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/julia/test/test_boundary.jl b/example/1d-linear-convection/weno3/julia/01i/julia/test/test_boundary.jl new file mode 100644 index 000000000..ef27d6820 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/julia/test/test_boundary.jl @@ -0,0 +1,57 @@ +# julia/test/test_boundary.jl +using NPZ +include("../boundary.jl") + +struct MockConfig + boundary_type::String + left_boundary_value::Float64 + right_boundary_value::Float64 + debug::Bool +end + +struct MockDomain + nghosts::Int + ist::Int # Julia 本地索引(1-based) + ied::Int + ntcells::Int +end + +struct MockCfd + config::MockConfig + domain::MockDomain +end + +# ===== 关键:使用 Julia 本地索引规则 ===== +nghosts = 2 +ncells = 40 +ist = nghosts + 1 # = 3 +ied = ist + ncells # = 43 +ntcells = ncells + 2 * nghosts # = 44 + +config = MockConfig("dirichlet", 0.5, 1.5, true) +domain = MockDomain(nghosts, ist, ied, ntcells) +cfd_mock = MockCfd(config, domain) + +# 加载 Python 生成的 u_input.npy +# 注意:Python u[0] → Julia u[1],所以内容完全对应 +u_input = npzread("../../python/u_input.npy") +@assert length(u_input) == ntcells "数组长度不匹配!" + +# 测试三种边界 +test_cases = [ + ("periodic", PeriodicBoundary(cfd_mock)), + ("dirichlet", DirichletBoundary(cfd_mock)), + ("neumann", NeumannBoundary(cfd_mock)) +] + +for (name, bc) in test_cases + u = copy(u_input) + apply!(bc, u) + + u_py = npzread("../../python/u_$(name)_py.npy") + err = maximum(abs.(u .- u_py)) + println("边界: $(name) | 最大误差: $(err)") + @assert err < 1e-12 "❌ $(name) 不一致!" +end + +println("✅ 所有边界条件测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/julia/test/test_domain.jl b/example/1d-linear-convection/weno3/julia/01i/julia/test/test_domain.jl new file mode 100644 index 000000000..7b423f774 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/julia/test/test_domain.jl @@ -0,0 +1,44 @@ +# julia/test/test_domain.jl +include("../mesh.jl") +include("../domain.jl") + +# MockConfig:模拟 Python CfdConfig +struct MockConfig + recon_scheme::String + spatial_order::Int +end + +# 测试 ENO +config_eno = MockConfig("eno", 2) +mesh = Mesh() +domain_eno = Domain(config_eno, mesh) + +println("ENO: nghosts = ", domain_eno.nghosts) # 2 +println("ENO: ist = ", domain_eno.ist) # 2 +println("ENO: ied = ", domain_eno.ied) # 42 +println("物理索引范围: ", collect(get_physical_indices(domain_eno))[1:3], " ... ", collect(get_physical_indices(domain_eno))[end-2:end]) + +# 测试 WENO(字符串 "weno") +config_weno = MockConfig("weno", 2) +domain_weno = Domain(config_weno, mesh) +println("WENO: nghosts = ", domain_weno.nghosts) # 2 + +# 测试 WENO3(字符串 "weno3") +config_weno3 = MockConfig("weno3", 2) +domain_weno3 = Domain(config_weno3, mesh) +println("WENO3: nghosts = ", domain_weno3.nghosts) # 2 + +# 测试 is_physical_cell +@assert is_physical_cell(domain_eno, 2) == true # ist=2 +@assert is_physical_cell(domain_eno, 41) == true # ied-1=41 +@assert is_physical_cell(domain_eno, 42) == false # ied=42 + +# ✅ 断言 +@assert domain_eno.nghosts == 2 +@assert domain_eno.ist == 2 +@assert domain_eno.ied == 42 +@assert domain_eno.ntcells == 44 +@assert domain_weno.nghosts == 2 +@assert domain_weno3.nghosts == 2 + +println("✅ Domain 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/julia/test/test_eno.jl b/example/1d-linear-convection/weno3/julia/01i/julia/test/test_eno.jl new file mode 100644 index 000000000..17b9a7a48 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/julia/test/test_eno.jl @@ -0,0 +1,34 @@ +# julia/test/test_eno.jl +include("../mesh.jl") +include("../domain.jl") +include("../solution.jl") +include("../reconstructor/eno.jl") + +struct MockCfd + domain::Domain + solution::Solution +end + +mesh = Mesh() +# 注意:Domain 使用 1-based 索引(ist = nghosts + 1) +domain = Domain((recon_scheme="eno", spatial_order=2), mesh) +solution = Solution((ic_type="step",), domain) + +# 线性初始场: u[i] = (i-1) * 0.1 +u = solution.u +for i in 1:domain.ntcells + u[i] = Float64(i-1) * 0.1 +end + +eno = EnoReconstructor(2, domain.ntcells) +cfd = MockCfd(domain, solution) +reconstruct(eno, u, cfd) + +println("q_face_left[1] = ", solution.q_face_left[1]) +println("q_face_right[1] = ", solution.q_face_right[1]) + +# ENO2 对线性函数应精确重构 0.15 +@assert abs(solution.q_face_left[1] - 0.15) < 1e-12 +@assert abs(solution.q_face_right[1] - 0.15) < 1e-12 + +println("✅ ENO 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/julia/test/test_flux.jl b/example/1d-linear-convection/weno3/julia/01i/julia/test/test_flux.jl new file mode 100644 index 000000000..0ebc1dc05 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/julia/test/test_flux.jl @@ -0,0 +1,53 @@ +# julia/test/test_flux.jl +include("../mesh.jl") +include("../flux.jl") + +# Mock 对象 +struct MockConfig + flux_type::String + wave_speed::Float64 +end + +struct MockDomain + mesh::Mesh +end + +struct MockCfd + config::MockConfig + domain::MockDomain +end + +# 创建 mock +mesh = Mesh() +config = MockConfig("rusanov", 1.0) +domain = MockDomain(mesh) +cfd = MockCfd(config, domain) + +# 测试 Rusanov +rusanov = RusanovFluxCalculator(cfd) +N = mesh.nnodes +qL = [1.0, 2.0, 3.0, 4.0, 5.0, zeros(Float64, N-5)...] +qR = [2.0, 3.0, 4.0, 5.0, 6.0, zeros(Float64, N-5)...] +flux_rus = zeros(Float64, N) +compute!(rusanov, qL, qR, flux_rus) + +println("Rusanov flux[1] = ", flux_rus[1]) # 应为 1.0 +@assert abs(flux_rus[1] - 1.0) < 1e-12 + +# 测试 Engquist-Osher +eo = EngquistOsherFluxCalculator(cfd) +flux_eo = zeros(Float64, N) +compute!(eo, qL, qR, flux_eo) + +println("EO flux[1] = ", flux_eo[1]) # c=1 → cp=1, cm=0 → flux=1*1 + 0*2 = 1.0 +@assert abs(flux_eo[1] - 1.0) < 1e-12 + +# 测试 c = -1.0 +config_neg = MockConfig("rusanov", -1.0) +cfd_neg = MockCfd(config_neg, domain) +rusanov_neg = RusanovFluxCalculator(cfd_neg) +compute!(rusanov_neg, qL, qR, flux_rus) +println("Rusanov (c=-1) flux[1] = ", flux_rus[1]) # F_L=-1, F_R=-2, Smax=1 → flux = -1.5 -0.5*(-1) = -1.0 +@assert abs(flux_rus[1] - (-2.0)) < 1e-12 # ← 修正:-2.0 而非 -1.0 + +println("✅ Flux 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/julia/test/test_initial_condition.jl b/example/1d-linear-convection/weno3/julia/01i/julia/test/test_initial_condition.jl new file mode 100644 index 000000000..dc58691a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/julia/test/test_initial_condition.jl @@ -0,0 +1,45 @@ +# julia/test/test_initial_condition.jl +using NPZ + +# 包含初始条件模块 +include("../initial_condition.jl") + +# 构造 mock config(与 Python 完全一致) +struct MockConfig + ic_type::String + domain_length::Float64 + pulse_center::Float64 + pulse_width::Float64 +end + +# 生成与 Python Mesh.xcc 完全相同的 x 坐标 +function generate_xcc() + xmin, xmax = 0.0, 2.0 + ncells = 40 + dx = (xmax - xmin) / ncells + xcc = Vector{Float64}(undef, ncells) + for i in 1:ncells + xcc[i] = xmin + (i - 0.5) * dx # i-1 + 0.5 → i-0.5 + end + return xcc +end + +# 主测试 +xcc = generate_xcc() + +test_cases = [ + ("step", StepFunctionIC(MockConfig("step", 2.0, 0.5, 0.1))), + ("sin", SineWaveIC(MockConfig("sin", 2.0, 0.5, 0.1))), + ("gaussian", GaussianPulseIC(MockConfig("gaussian", 2.0, 0.5, 0.1))) +] + +for (name, ic) in test_cases + u_jl = evaluate_at(ic, xcc) + u_py = npzread("../../python/u_$(name)_interior_py.npy") + + err = maximum(abs.(u_jl .- u_py)) + println("IC: $(name) | 最大误差: $(err)") + @assert err < 1e-12 "❌ $(name) 不一致!" +end + +println("✅ 所有初始条件测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/julia/test/test_mesh.jl b/example/1d-linear-convection/weno3/julia/01i/julia/test/test_mesh.jl new file mode 100644 index 000000000..315d2aac2 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/julia/test/test_mesh.jl @@ -0,0 +1,37 @@ +# julia/test/test_mesh.jl +include("../mesh.jl") + +# 创建 mesh(与 Python 完全相同) +mesh = Mesh() + +# 打印关键值(与 Python 对比) +println("xmin = ", mesh.xmin) # 0.0 +println("xmax = ", mesh.xmax) # 2.0 +println("ncells = ", mesh.ncells) # 40 +println("nnodes = ", mesh.nnodes) # 41 +println("nx = ", mesh.nx) # 40 +println("L = ", mesh.L) # 2.0 +println("dx = ", mesh.dx) # 0.05 + +# 检查 x[1] (Python x[0]) 和 x[41] (Python x[40]) +println("x[1] = ", mesh.x[1]) # 0.0 +println("x[41] = ", mesh.x[41]) # 2.0 + +# 检查 xcc[1] (Python xcc[0]) 和 xcc[40] (Python xcc[39]) +println("xcc[1] = ", mesh.xcc[1]) # 0.025 +println("xcc[40] = ", mesh.xcc[40]) # 1.975 + +# ✅ 严格断言 +@assert mesh.xmin == 0.0 +@assert mesh.xmax == 2.0 +@assert mesh.ncells == 40 +@assert mesh.nnodes == 41 +@assert mesh.nx == 40 +@assert mesh.L == 2.0 +@assert mesh.dx == 0.05 +@assert mesh.x[1] == 0.0 +@assert mesh.x[41] == 2.0 +@assert abs(mesh.xcc[1] - 0.025) < 1e-12 +@assert abs(mesh.xcc[40] - 1.975) < 1e-12 + +println("✅ Mesh 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/julia/test/test_residual.jl b/example/1d-linear-convection/weno3/julia/01i/julia/test/test_residual.jl new file mode 100644 index 000000000..e37e8d2bd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/julia/test/test_residual.jl @@ -0,0 +1,53 @@ +# julia/test/test_residual.jl +include("../mesh.jl") +include("../domain.jl") +include("../solution.jl") +include("../flux.jl") +include("../residual.jl") + +# ===== Dummy Reconstructor ===== +struct DummyReconstructor end + +# ===== Mock Config ===== +struct MockConfig + flux_type::String + wave_speed::Float64 +end + +# ===== Mock CFD (必须包含 reconstructor 字段) ===== +struct MockCfd + config::MockConfig + domain::Domain + solution::Solution + reconstructor::DummyReconstructor # ← 关键:添加此字段 +end + +# ===== 主测试 ===== +config = MockConfig("rusanov", 1.0) +mesh = Mesh() +domain = Domain((recon_scheme="eno", spatial_order=2), mesh) +solution = Solution((ic_type="step",), domain) + +# 手动设置界面值(跳过重建) +for i in 1:mesh.nnodes + solution.q_face_left[i] = Float64(i) * 0.1 + solution.q_face_right[i] = Float64(i) * 0.1 +end + +flux_calc = RusanovFluxCalculator((config=config, domain=domain)) +dummy_recon = DummyReconstructor() +cfd = MockCfd(config, domain, solution, dummy_recon) + +# 创建残差计算器 +res_calc = ResidualCalculator(cfd, flux_calc) + +# 计算残差(注意:_reconstruct 仍被注释) +compute!(res_calc) + +println("flux[1] = ", solution.flux[1]) +println("res[1] = ", solution.res[1]) + +@assert abs(solution.flux[1] - 0.1) < 1e-12 +@assert abs(solution.res[1] - (-2.0)) < 1e-12 + +println("✅ Residual 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/julia/test/test_solution.jl b/example/1d-linear-convection/weno3/julia/01i/julia/test/test_solution.jl new file mode 100644 index 000000000..f44ff0f0e --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/julia/test/test_solution.jl @@ -0,0 +1,42 @@ +# julia/test/test_solution.jl +include("../mesh.jl") +include("../domain.jl") +include("../solution.jl") + +# MockConfig +struct MockConfig + recon_scheme::String + spatial_order::Int + ic_type::String + domain_length::Float64 + pulse_center::Float64 + pulse_width::Float64 +end + +# 创建 solution +config = MockConfig("eno", 2, "step", 2.0, 0.5, 0.1) +mesh = Mesh() +domain = Domain(config, mesh) +sol = Solution(config, domain) + +# 检查字段尺寸 +@assert length(sol.q_face_left) == mesh.nnodes # 41 +@assert length(sol.flux) == mesh.nnodes # 41 +@assert length(sol.res) == mesh.ncells # 40 +@assert length(sol.u) == domain.ntcells # 44 + +# 检查初始场 +println("u[3] (物理起始): ", sol.u[3]) # 应为 1.0 +println("u[23] (x=1.0): ", sol.u[23]) # 应为 2.0 + +# 测试 update_old_field +sol.u[3] = 999.0 +update_old_field(sol) +@assert sol.un[3] == 999.0 + +# 测试 reset_solution +reset_solution(sol) +@assert sol.u[3] == 0.0 +@assert sol.un[3] == 0.0 + +println("✅ Solution 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/julia/test/test_time_integration.jl b/example/1d-linear-convection/weno3/julia/01i/julia/test/test_time_integration.jl new file mode 100644 index 000000000..091a8637c --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/julia/test/test_time_integration.jl @@ -0,0 +1,53 @@ +# julia/test/test_time_integration.jl +include("../time_integration.jl") +include("../flux.jl") +include("../boundary.jl") + +# Mock CFD 对象 +struct MockCfd + config::Any + domain::Domain + solution::Solution + residual_calculator::Any + boundary_condition::Any +end + +# 创建完整链路 +mesh = Mesh() +domain = Domain((recon_scheme="eno", spatial_order=2), mesh) +solution = Solution((ic_type="step",), domain) + +# 手动设置界面值(跳过 reconstructor) +for i in 1:mesh.nnodes + solution.q_face_left[i] = Float64(i) * 0.1 + solution.q_face_right[i] = Float64(i) * 0.1 +end + +# Mock components +flux_calc = RusanovFluxCalculator((config=(wave_speed=1.0,), domain=domain)) +res_calc = ResidualCalculator((config=nothing, domain=domain, solution=solution, reconstructor=nothing), flux_calc) +bc = PeriodicBoundary((domain=domain, config=(debug=false,))) +cfd = MockCfd((rk_order=1,), domain, solution, res_calc, bc) + +# 测试 RK1 +rk1 = RK1Integrator(cfd) +step(rk1, 0.025) + +println("RK1 step completed") +@assert solution.u[3] != 0.0 # 应已更新 + +# 测试 RK2 +cfd2 = MockCfd((rk_order=2,), domain, solution, res_calc, bc) +rk2 = RK2Integrator(cfd2) +step(rk2, 0.025) + +println("RK2 step completed") + +# 测试 RK3 +cfd3 = MockCfd((rk_order=3,), domain, solution, res_calc, bc) +rk3 = RK3Integrator(cfd3) +step(rk3, 0.025) + +println("RK3 step completed") + +println("✅ Time Integration 逻辑测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/julia/test/test_weno3.jl b/example/1d-linear-convection/weno3/julia/01i/julia/test/test_weno3.jl new file mode 100644 index 000000000..9a91761e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/julia/test/test_weno3.jl @@ -0,0 +1,47 @@ +# julia/test/test_weno3.jl +include("../mesh.jl") +include("../domain.jl") +include("../solution.jl") +include("../reconstructor/weno3.jl") + +# Mock CFD +struct MockCfd + domain::Domain + solution::Solution +end + +# 创建数据 +mesh = Mesh() +# 注意:Domain 现在使用 1-based 索引(ist = nghosts + 1) +domain = Domain((recon_scheme="weno3", spatial_order=2), mesh) +solution = Solution((ic_type="step",), domain) + +# 设置 u(物理区域 + ghost) +u = solution.u +for i in 1:domain.ntcells + u[i] = Float64(i-1) * 0.1 # u = [0.0, 0.1, 0.2, ..., 4.3] +end + +# 创建 reconstructor +weno3 = Weno3Reconstructor() +cfd = MockCfd(domain, solution) + +# 重建 +reconstruct(weno3, u, cfd) + +println("q_face_left length = ", length(solution.q_face_left)) # 应为 41 +println("q_face_right length = ", length(solution.q_face_right)) # 应为 41 +println("q_face_left[1] = ", solution.q_face_left[1]) +println("q_face_right[1] = ", solution.q_face_right[1]) + +# 验证左界面值(i = ist-1 = 2, j = 1) +# v1 = u[1] = 0.0, v2 = u[2] = 0.1, v3 = u[3] = 0.2 +# qL = w0*(-0.5*0.0 + 1.5*0.1) + w1*(0.5*0.1 + 0.5*0.2) = w0*0.15 + w1*0.15 = 0.15 +@assert abs(solution.q_face_left[1] - 0.15) < 1e-12 + +# 验证右界面值(i = ist = 3, j = 1) +# v1 = u[2] = 0.1, v2 = u[3] = 0.2, v3 = u[4] = 0.3 +# qR = w0*(0.5*0.1 + 0.5*0.2) + w1*(1.5*0.2 - 0.5*0.3) = w0*0.15 + w1*0.15 = 0.15 +@assert abs(solution.q_face_right[1] - 0.15) < 1e-12 + +println("✅ WENO3 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/julia/test_residual.jl b/example/1d-linear-convection/weno3/julia/01i/julia/test_residual.jl new file mode 100644 index 000000000..315d2aac2 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/julia/test_residual.jl @@ -0,0 +1,37 @@ +# julia/test/test_mesh.jl +include("../mesh.jl") + +# 创建 mesh(与 Python 完全相同) +mesh = Mesh() + +# 打印关键值(与 Python 对比) +println("xmin = ", mesh.xmin) # 0.0 +println("xmax = ", mesh.xmax) # 2.0 +println("ncells = ", mesh.ncells) # 40 +println("nnodes = ", mesh.nnodes) # 41 +println("nx = ", mesh.nx) # 40 +println("L = ", mesh.L) # 2.0 +println("dx = ", mesh.dx) # 0.05 + +# 检查 x[1] (Python x[0]) 和 x[41] (Python x[40]) +println("x[1] = ", mesh.x[1]) # 0.0 +println("x[41] = ", mesh.x[41]) # 2.0 + +# 检查 xcc[1] (Python xcc[0]) 和 xcc[40] (Python xcc[39]) +println("xcc[1] = ", mesh.xcc[1]) # 0.025 +println("xcc[40] = ", mesh.xcc[40]) # 1.975 + +# ✅ 严格断言 +@assert mesh.xmin == 0.0 +@assert mesh.xmax == 2.0 +@assert mesh.ncells == 40 +@assert mesh.nnodes == 41 +@assert mesh.nx == 40 +@assert mesh.L == 2.0 +@assert mesh.dx == 0.05 +@assert mesh.x[1] == 0.0 +@assert mesh.x[41] == 2.0 +@assert abs(mesh.xcc[1] - 0.025) < 1e-12 +@assert abs(mesh.xcc[40] - 1.975) < 1e-12 + +println("✅ Mesh 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/julia/time_integration.jl b/example/1d-linear-convection/weno3/julia/01i/julia/time_integration.jl new file mode 100644 index 000000000..95e5cd67f --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/julia/time_integration.jl @@ -0,0 +1,130 @@ +# julia/time_integration.jl +""" +时间推进器模块(与 time_integration.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("residual.jl") +include("boundary.jl") + +# ---------------------- 抽象时间推进器基类 ---------------------- +abstract type TimeIntegrator end + +mutable struct TimeIntegratorBase <: TimeIntegrator + cfd::Any + config::Any + domain::Domain + solution::Solution + residual_calculator::Any # ResidualCalculator +end + +function TimeIntegratorBase(cfd::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + residual_calculator = cfd.residual_calculator + TimeIntegratorBase(cfd, config, domain, solution, residual_calculator) +end + +function compute_residual(integrator::TimeIntegratorBase) + compute!(integrator.residual_calculator) +end + +function apply_boundary(integrator::TimeIntegratorBase) + apply!(integrator.cfd.boundary_condition, integrator.solution.u) +end + +function map_idx(integrator::TimeIntegratorBase, i::Int) + return i - integrator.domain.ist + 1 # ← +1 转为 1-based +end + +# ---------------------- RK1Integrator ---------------------- +mutable struct RK1Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK1Integrator(cfd::Any) + RK1Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK1Integrator, dt::Float64) + compute_residual(integrator.base) + for i in integrator.base.domain.ist:(integrator.base.domain.ied - 1) + j = map_idx(integrator.base, i) + integrator.base.solution.u[i] += dt * integrator.base.solution.res[j] + end + apply_boundary(integrator.base) + update_old_field(integrator.base.solution) +end + +# ---------------------- RK2Integrator ---------------------- +mutable struct RK2Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK2Integrator(cfd::Any) + RK2Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK2Integrator, dt::Float64) + base = integrator.base + # 阶段1:预测步 + compute_residual(base) + u_pred = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u_pred[i] += dt * base.solution.res[j] + end + base.solution.u .= u_pred + apply_boundary(base) + # 阶段2:校正步 + compute_residual(base) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = 0.5 * base.solution.un[i] + 0.5 * base.solution.u[i] + 0.5 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end + +# ---------------------- RK3Integrator ---------------------- +mutable struct RK3Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK3Integrator(cfd::Any) + RK3Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK3Integrator, dt::Float64) + base = integrator.base + # 阶段1 + compute_residual(base) + u1 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u1[i] += dt * base.solution.res[j] + end + base.solution.u .= u1 + apply_boundary(base) + # 阶段2 + compute_residual(base) + u2 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u2[i] = 0.75 * base.solution.un[i] + 0.25 * base.solution.u[i] + 0.25 * dt * base.solution.res[j] + end + base.solution.u .= u2 + apply_boundary(base) + # 阶段3 + compute_residual(base) + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = c1 * base.solution.un[i] + c2 * base.solution.u[i] + c3 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/python/boundary.py b/example/1d-linear-convection/weno3/julia/01i/python/boundary.py new file mode 100644 index 000000000..6054f92de --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/python/boundary.py @@ -0,0 +1,103 @@ +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from factories.base_factory import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/python/cfd_registry.py b/example/1d-linear-convection/weno3/julia/01i/python/cfd_registry.py new file mode 100644 index 000000000..c12eb106c --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/python/cfd_registry.py @@ -0,0 +1,62 @@ +# cfd_registry.py +""" +CFD注册系统统一导入模块 +所有其他模块从这里导入注册系统 +""" + +import sys +import os + +# 添加当前目录到路径,确保能找到registry.py +current_dir = os.path.dirname(os.path.abspath(__file__)) +if current_dir not in sys.path: + sys.path.insert(0, current_dir) + +try: + # 尝试导入注册系统 + from registry import ComponentRegistry, register_component + REGISTRY_AVAILABLE = True +except ImportError: + # 如果找不到,提供简单的空实现 + print("⚠️ 警告: 未找到 registry.py,使用最小化回退实现") + + class ComponentRegistry: + """最小化的注册系统回退实现""" + _registries = {} + + @classmethod + def register(cls, category, name, component_class): + if category not in cls._registries: + cls._registries[category] = {} + cls._registries[category][name] = component_class + + @classmethod + def get(cls, category, name): + if category not in cls._registries: + raise ValueError(f"未知类别: {category}") + if name not in cls._registries[category]: + raise ValueError(f"未找到: {category}.{name}") + return cls._registries[category][name] + + @classmethod + def create(cls, category, name, *args, **kwargs): + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) + for cat, comps in cls._registries.items()} + + def register_component(category, name=None): + """简化的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + REGISTRY_AVAILABLE = False + +# 导出统一的接口 +__all__ = ['ComponentRegistry', 'register_component', 'REGISTRY_AVAILABLE'] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/python/config.py b/example/1d-linear-convection/weno3/julia/01i/python/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/python/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/python/domain.py b/example/1d-linear-convection/weno3/julia/01i/python/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/python/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/python/eno_weno_comparison.png b/example/1d-linear-convection/weno3/julia/01i/python/eno_weno_comparison.png new file mode 100644 index 0000000000000000000000000000000000000000..8d7135ec5ad2cfcf1cd9fffa499596b9bbd8bb33 GIT binary patch literal 224091 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

QZ3;A{z^y1I%tmNc(c)bqip=UU8@!F-F(kyVSX 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 literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01i/python/factories/base_factory.py b/example/1d-linear-convection/weno3/julia/01i/python/factories/base_factory.py new file mode 100644 index 000000000..7b8b6105e --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/python/factories/base_factory.py @@ -0,0 +1,49 @@ +# factories/base_factory.py +""" +工厂基类 - 提供统一的组件创建接口 +""" + +from cfd_registry import ComponentRegistry +from typing import Any, Optional + +class BaseFactory: + """工厂基类,提供统一的组件创建方法""" + + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + """ + 统一创建组件的方法 + + Args: + category: 组件类别(如'boundary', 'flux'等) + name: 组件名称(如'periodic', 'rusanov'等) + *args, **kwargs: 传递给构造函数的参数 + + Returns: + 创建的组件实例 + + Raises: + ValueError: 如果组件不存在 + """ + name_lower = name.lower() + + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + # 获取可用组件列表 + available = ComponentRegistry.list_all().get(category, []) + + # 构建友好的错误信息 + if available: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"可用类型:{available}") + else: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"({category}类别下没有注册任何组件)") + + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + """获取指定类别的可用组件列表""" + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/python/flux.py b/example/1d-linear-convection/weno3/julia/01i/python/flux.py new file mode 100644 index 000000000..5ac73aa8d --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/python/flux.py @@ -0,0 +1,74 @@ +# flux.py +""" +通量计算器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册,替代硬编码工厂 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象通量计算基类(统一接口) ---------------------- +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass + +# ---------------------- 2. 具体通量计算子类(使用装饰器注册) ---------------------- + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + +class FluxCalculatorFactory: + """通量计算器工厂""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD对象 + + Returns: + 通量计算器实例 + """ + from factories.base_factory import BaseFactory + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/python/gen_boundary_test_data.py b/example/1d-linear-convection/weno3/julia/01i/python/gen_boundary_test_data.py new file mode 100644 index 000000000..c7fc2a9c5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/python/gen_boundary_test_data.py @@ -0,0 +1,35 @@ +# python/gen_boundary_test_data.py +import numpy as np +from config import CfdConfig +from mesh import Mesh +from domain import Domain + +# 固定测试配置 +config = CfdConfig() +config.with_boundary("dirichlet", left_value=0.5, right_value=1.5) +config.debug = True + +mesh = Mesh() +domain = Domain(config, mesh) + +# 构造 mock CFD 对象(仅含 config + domain) +class MockCfd: + def __init__(self, config, domain): + self.config = config + self.domain = domain + +# 测试用 u:0,1,2,...,N-1 +u_input = np.arange(domain.ntcells, dtype=np.float64) +np.save("u_input.npy", u_input) + +# 测试每种边界 +from boundary import PeriodicBoundary, DirichletBoundary, NeumannBoundary + +for bc_name, bc_class in [("periodic", PeriodicBoundary), ("dirichlet", DirichletBoundary), ("neumann", NeumannBoundary)]: + u = u_input.copy() + cfd_mock = MockCfd(config, domain) + bc = bc_class(cfd_mock) + bc.apply(u) + np.save(f"u_{bc_name}_py.npy", u) + +print("✅ 测试数据已生成:u_*.npy") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/python/gen_ic_test_data.py b/example/1d-linear-convection/weno3/julia/01i/python/gen_ic_test_data.py new file mode 100644 index 000000000..69c2573b8 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/python/gen_ic_test_data.py @@ -0,0 +1,27 @@ +# python/gen_ic_test_data.py +import sys, os +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +import numpy as np +from config import CfdConfig +from mesh import Mesh +from domain import Domain +from solution import Solution + +# 固定 mesh +mesh = Mesh() +config = CfdConfig() + +# 测试三种 IC +for ic_type in ["step", "sin", "gaussian"]: + config.ic_type = ic_type + domain = Domain(config, mesh) + sol = Solution(config, domain) + + u_full = sol.u.copy() # 包含 ghost + u_interior = sol.u[domain.ist:domain.ied].copy() + + np.save(f"u_{ic_type}_full_py.npy", u_full) + np.save(f"u_{ic_type}_interior_py.npy", u_interior) + +print("✅ 初始条件测试数据已生成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/python/initial_condition.py b/example/1d-linear-convection/weno3/julia/01i/python/initial_condition.py new file mode 100644 index 000000000..047415b74 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/python/initial_condition.py @@ -0,0 +1,90 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, ic_type: str, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from factories.base_factory import BaseFactory + return BaseFactory.create_component('initial_condition', ic_type, config) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/python/mesh.py b/example/1d-linear-convection/weno3/julia/01i/python/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/python/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/python/plotter.py b/example/1d-linear-convection/weno3/julia/01i/python/plotter.py new file mode 100644 index 000000000..9f1a414fd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/python/plotter.py @@ -0,0 +1,107 @@ +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/python/reconstructor/__init__.py b/example/1d-linear-convection/weno3/julia/01i/python/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/python/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/python/reconstructor/base.py b/example/1d-linear-convection/weno3/julia/01i/python/reconstructor/base.py new file mode 100644 index 000000000..bbd638503 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/python/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def reconstruct(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/python/reconstructor/eno.py b/example/1d-linear-convection/weno3/julia/01i/python/reconstructor/eno.py new file mode 100644 index 000000000..c2fb385dd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/python/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def reconstruct(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/python/reconstructor/factory.py b/example/1d-linear-convection/weno3/julia/01i/python/reconstructor/factory.py new file mode 100644 index 000000000..efa4a1446 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/python/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from cfd_registry import ComponentRegistry + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from factories.base_factory import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/python/reconstructor/weno3.py b/example/1d-linear-convection/weno3/julia/01i/python/reconstructor/weno3.py new file mode 100644 index 000000000..bf68be509 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/python/reconstructor/weno3.py @@ -0,0 +1,59 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def reconstruct(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/julia/01i/python/registry.py b/example/1d-linear-convection/weno3/julia/01i/python/registry.py new file mode 100644 index 000000000..61be90500 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/python/registry.py @@ -0,0 +1,75 @@ +# registry.py (改进版) +""" +CFD组件注册系统 - 简洁高效版 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表""" + + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True # 控制是否打印注册信息 + + @classmethod + def set_verbose(cls, verbose: bool): + """设置是否打印注册信息""" + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + # 检查是否已注册 + if name in cls._registries[category]: + if cls._registries[category][name] == component_class: + # 相同类重复注册,静默跳过 + return + elif cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取组件类""" + if category not in cls._registries: + available = list(cls._registries.keys()) + raise ValueError(f"❌ 未知类别: {category} (可用类别: {available})") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + @classmethod + def get_count(cls) -> int: + """获取注册组件总数""" + return sum(len(components) for components in cls._registries.values()) + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/python/residual.py b/example/1d-linear-convection/weno3/julia/01i/python/residual.py new file mode 100644 index 000000000..b4d4d7dcc --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/python/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux import FluxCalculatorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + self.reconstructor = self.cfd.reconstructor + + # 初始化通量计算器(工厂创建) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._reconstruct() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _reconstruct(self): + """私有方法:界面值重建""" + self.reconstructor.reconstruct(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/python/run_eno_weno.py b/example/1d-linear-convection/weno3/julia/01i/python/run_eno_weno.py new file mode 100644 index 000000000..fd7f38744 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/python/run_eno_weno.py @@ -0,0 +1,52 @@ +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + #mesh = Mesh(ncells=100, L=2.0) + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行ENO3求解(使用你的链式调用) + print("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + config_eno3.with_reconstruction("eno", 3) # 显式指定3阶(也可省略,ENO默认3阶) + # 可选:覆盖默认值(如dt) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + cfd_eno3.run() # 求解并生成result字典 + + # 可选:快速验证ENO3结果 + # plotter.plot_quick(cfd_eno3.result, title="ENO3 Quick Check") + + # 3. 配置并运行WENO3求解(注意:WENO默认5阶,这里显式指定3阶) + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) # 显式指定3阶(默认是5阶) + # 可选:覆盖默认值 + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + cfd_weno3.run() + + # 4. 可选:保存结果(供离线绘图) + # cfd_eno3.save_result("eno3_result.npz") + # cfd_weno3.save_result("weno3_result.npz") + + # 5. 绘制ENO/WENO对比图 + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" # 可选:保存图片 + ) + +if __name__ == "__main__": + # 主程序入口 + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/python/solution.py b/example/1d-linear-convection/weno3/julia/01i/python/solution.py new file mode 100644 index 000000000..92a46d3f4 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/python/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config.ic_type, config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/python/solver.py b/example/1d-linear-convection/weno3/julia/01i/python/solver.py new file mode 100644 index 000000000..0d0b442da --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/python/solver.py @@ -0,0 +1,86 @@ +import numpy as np +import matplotlib.pyplot as plt +import inflect +from abc import ABC, abstractmethod + + +from flux import InviscidFluxCalculator, RusanovFluxCalculator, EngquistOsherFluxCalculator, FluxCalculatorFactory + +from boundary import BoundaryCondition, PeriodicBoundary, DirichletBoundary, NeumannBoundary, BoundaryConditionFactory + +from time_integration import TimeIntegrator, RK1Integrator, RK2Integrator, RK3Integrator, TimeIntegratorFactory + +from mesh import Mesh + +from reconstructor import ReconstructorFactory + +from initial_condition import InitialCondition, StepFunctionIC, SineWaveIC, GaussianPulseIC, InitialConditionFactory + +from domain import Domain +from solution import Solution + +from config import CfdConfig +from residual import ResidualCalculator + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, config, mesh): + self.config = config + self.domain = Domain(config, mesh) + self.solution = Solution(config, self.domain) + self.reconstructor = ReconstructorFactory.create(config, self.domain) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + def exact_solution(self): + """通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界""" + x = self.domain.mesh.xcc + T = self.config.final_time + c = self.config.wave_speed + L = self.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = InitialConditionFactory.create(self.config.ic_type, self.config) + return ic.evaluate_at(x_shifted) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + #runge_kutta(self) + self.integrator.step(dt) + t += dt + config.dt = dt_old + + # 整理标准化结果 + u_numerical = self.solution.u[self.domain.ist:self.domain.ied].copy() + self.result = { + "x": domain.mesh.xcc, + "numerical": u_numerical, + "analytical": self.exact_solution(), + "config": { + "scheme": self.config.recon_scheme, + "order": self.config.spatial_order, + "rk_order": self.config.rk_order, + "final_time": self.config.final_time + } + } + + return u_numerical diff --git a/example/1d-linear-convection/weno3/julia/01i/python/time_integration.py b/example/1d-linear-convection/weno3/julia/01i/python/time_integration.py new file mode 100644 index 000000000..25ac0b4ca --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01i/python/time_integration.py @@ -0,0 +1,125 @@ +# time_integration.py + +""" +时间推进器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = cfd.residual_calculator + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(使用装饰器注册) ---------------------- + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class TimeIntegratorFactory: + """时间推进器工厂""" + + @staticmethod + def create(cfd) -> 'TimeIntegrator': + """ + 创建时间推进器实例 + + Args: + cfd: CFD对象 + + Returns: + 时间推进器实例 + """ + from factories.base_factory import BaseFactory + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01i/python/u_dirichlet_py.npy b/example/1d-linear-convection/weno3/julia/01i/python/u_dirichlet_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..3aa20bd2e395654a6e8c59bbbc9f5eaa955b345e GIT binary patch literal 480 zcmbWzxeftg6o%oW6O}@tQ2Y_`BNMTVeHmMHM59p2#LPq?$c$)2;vU?f%TQY8ZM07F zov8Zw0H1NzP(=`e8>IlQ+pS*SsNB7-1(=_si zx~l&#Cf|Elzsex&D8?~?Nlc-_G-fc1IV_-oMJ!<%D_BJnYgoqyHnD|m>|hsr*hdQo Wp*NHvjWVK9#x%-=MtSnjr}YA`88kxx literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01i/python/u_gaussian_full_py.npy b/example/1d-linear-convection/weno3/julia/01i/python/u_gaussian_full_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..b762f0fe295b52a9a176817021995ce5a561d778 GIT binary patch literal 480 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yCOVor3bhL411<(MU?Ou)P3Oi_+soE}ylQv)*=cg#Ue98-%FeWXxBn~o z-*&0B&Pq*ej`k{D0z3w9EA6|!ORWz4wA}u9Vfdl$E2r%H!VVg&w0>m2nCbZM)%@?^ zbQ@G0rY;|Sof!j>qBKc_8=pP9zb3SzgEt8=WEn__NZ z$*8-a{Z)*4!nvvF3 wqbFAl+ziF%o9|Zhkuvbx>B0B<*(1H@$~rUDs~6~AeE<2_q`W{KwBTd_0INu`7ytkO literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01i/python/u_gaussian_interior_py.npy b/example/1d-linear-convection/weno3/julia/01i/python/u_gaussian_interior_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..68af8954e7a17e69b6b2e6954b78ee542d4e96a0 GIT binary patch literal 448 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#y20EHL3bhL411=Mpb80#_p4wiv{^M1<)6Y(m^Y(fcvsHGc?YsS7$^W)X zt#wvvVso@t=@Q^Ecw1@T^<8Rp;HTyG#|y&`bzeDU-xqe!V5RjV`^8Mhf3N0$2dCSh z;xKjjP<`JzA?Co${oe+$2WBtK9e1T6?wYnbA&=>Lk?qOJ*(aWE{cN*=zyHlG=1iNl zN(soJS7TeOgcr6%N&Go&S^Ug2epV2>rCgn3z1$RY3rj}b z1?{h5%p2Bhd1c#FYWC{z!qS+GX48p_*$(R2wVEWXJEgqJq{jHxHq-S&9M{! literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01i/python/u_input.npy b/example/1d-linear-convection/weno3/julia/01i/python/u_input.npy new file mode 100644 index 0000000000000000000000000000000000000000..ef506fa75cacbbe63dfcc17332ddd6c33de5b888 GIT binary patch literal 480 zcmbWrI}gE70EXdX6O+MUFr0`uNFy$FzqBs35sSejjUF+GN-GwT_z(W5pP?kM2cBT{2ChY{-ZF ze<|^^w?*2qNlaq~vzSAPc`RTNOISezt60N2Hn52%wy=#I>|zi5IKUx}aEun(u}>5e R8pV`GF{4q;X%r6a*dIEnGEe{j literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01i/python/u_neumann_py.npy b/example/1d-linear-convection/weno3/julia/01i/python/u_neumann_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..fa723d1efce906983b6e6710379207d15afbc271 GIT binary patch literal 480 zcmbWrI}gE70EXdX6O+MUFr0`uC5^ZgRkymtMl1%CG}^`>Dy>*V;y?JqN?(i7w>|hs7?4gBy9N-W~I7U11fnvf( PG3BF}@lnkAKgRt9WDhZ? literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01i/python/u_periodic_py.npy b/example/1d-linear-convection/weno3/julia/01i/python/u_periodic_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..156aff85dfa4c84deaaed404e0dccc37937f8836 GIT binary patch literal 480 zcmbWzyAA+Jw5Jw7YF-Tj}jFJcs^aeGCKh-lzUPGOs<_`6u&g%W>?2t#0c7 zOUUOY-iJGid|Tp}#1y76g9@{l!#rA8L>o(3#tK%kh7Q)TflX{-8#~xV7kk*p0eWBW QC_@@$M5Bypl;8h*0k_X5-%Mo@PtK#WJA?o%og6P}-0bsJ$1U?l6M7O94XfzYY=Kp9oR6e-T9A{tpmy_6tDF-R}f( z-+mW}`}RZKw;$@h{Tm?e+z)l%epjeEd#FAks5zgY=FW!NlLxi;2-F?cP8 literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01j/julia/boundary.jl b/example/1d-linear-convection/weno3/julia/01j/julia/boundary.jl new file mode 100644 index 000000000..5b0baaf43 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/julia/boundary.jl @@ -0,0 +1,90 @@ +# julia/boundary.jl +# 目标:与 Python boundary.py 行为完全一致(1:1 同构) +# 前提:ist/ied 在 Julia 中按 1-based 设置(ist = nghosts + 1) + +using NPZ # 仅用于测试,实际模块可不依赖 + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象基类(Julia 风格:用函数接口) ---------------------- +# Julia 无 abstract class,用文档+约定 +# 所有子类型必须实现 apply!(bc, u) + +# ---------------------- PeriodicBoundary ---------------------- +mutable struct PeriodicBoundary + cfd::Any +end + +function apply!(self::PeriodicBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist # 1-based, e.g., 3 + ied = self.cfd.domain.ied # 1-based, e.g., 43 + + # 左 ghost: u[ist - 1 - ig] = u[ied - 1 - ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ied - 1 - ig] + end + # 右 ghost: u[ied + ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ied + ig] = u[ist + ig] + end +end + +# ---------------------- DirichletBoundary ---------------------- +mutable struct DirichletBoundary + cfd::Any +end + +function apply!(self::DirichletBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + left_value = getfield_safe(self.cfd.config, :left_boundary_value, 1.0) + right_value = getfield_safe(self.cfd.config, :right_boundary_value, 2.0) + + # 左边界 + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = left_value + end + # 右边界 + for ig in 0:(nghosts-1) + u[ied + ig] = right_value + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Dirichlet边界: 左值=$left_value, 右值=$right_value") + end +end + +# ---------------------- NeumannBoundary ---------------------- +mutable struct NeumannBoundary + cfd::Any +end + +function apply!(self::NeumannBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + # 左边界零梯度:u[ist - 1 - ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ist + ig] + end + # 右边界零梯度:u[ied + ig] = u[ied - 1 - ig] ← 注意是 ied - 1 - ig + for ig in 0:(nghosts-1) + u[ied + ig] = u[ied - 1 - ig] + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Neumann边界: 零梯度") + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/julia/config.jl b/example/1d-linear-convection/weno3/julia/01j/julia/config.jl new file mode 100644 index 000000000..bf48de3a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/julia/config.jl @@ -0,0 +1,68 @@ +# julia/config.jl +""" +CfdConfig:与 Python config.py 完全同构 +""" +mutable struct CfdConfig + ic_type::String + recon_scheme::String + flux_type::String + rk_order::Int + wave_speed::Float64 + final_time::Float64 + dt::Float64 + boundary_type::String + left_boundary_value::Float64 + right_boundary_value::Float64 + spatial_order::Int + + function CfdConfig() + new( + "step", + "eno", + "rusanov", + 1, + 1.0, + 0.625, + 0.025, + "periodic", + 1.0, + 2.0, + 2 + ) + end +end + +""" +专用配置:重建方案(链式调用) +""" +function with_reconstruction(cfg::CfdConfig, scheme::String, order::Union{Int, Nothing}=nothing) + cfg.recon_scheme = lowercase(scheme) + + if order !== nothing + cfg.spatial_order = order + else + if startswith(cfg.recon_scheme, "weno") + cfg.spatial_order = 5 + elseif cfg.recon_scheme == "eno" + cfg.spatial_order = 3 + else + error("不支持的重建格式:$scheme(仅支持 eno/weno)") + end + end + + return cfg # 支持链式调用 +end + +""" +专用配置:边界条件(链式调用) +""" +function with_boundary(cfg::CfdConfig, bc_type::String; left_value=nothing, right_value=nothing) + cfg.boundary_type = bc_type + if left_value !== nothing + cfg.left_boundary_value = left_value + end + if right_value !== nothing + cfg.right_boundary_value = right_value + end + return cfg +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/julia/domain.jl b/example/1d-linear-convection/weno3/julia/01j/julia/domain.jl new file mode 100644 index 000000000..a7edc2260 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/julia/domain.jl @@ -0,0 +1,61 @@ +# julia/domain.jl +""" +Domain 结构体(与 domain.py 完全同构) +- 保存 config +- nghosts 计算逻辑 1:1 +- ist/ied 为 0-based(与 Python 一致) +""" + +include("mesh.jl") + +mutable struct Domain + config::Any # 保存 config(与 Python 一致) + mesh::Mesh + nghosts::Int + ist::Int # 0-based + ied::Int # 0-based + ntcells::Int +end + +function Domain(config::Any, mesh::Mesh) + nghosts = _calc_nghosts(config) + ist = nghosts + 1 # ← 1-based 起始索引 + ied = ist + mesh.ncells # ← 1-based 结束索引(不包含) + ntcells = mesh.ncells + 2 * nghosts + Domain(config, mesh, nghosts, ist, ied, ntcells) +end + +function _calc_nghosts(config::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + order = getfield_safe(config, :spatial_order, 2) + + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + if scheme == "eno" + nghosts = order + elseif startswith(scheme, "weno") + nghosts = order ÷ 2 + 1 + else + error("未知重建格式 $(scheme),无法计算ghost层!") + end + + if nghosts <= 0 + error("计算得到的ghost层数量无效:$(nghosts)(阶数$(order),格式$(scheme))") + end + + return nghosts +end + +function is_physical_cell(domain::Domain, idx::Int) + return domain.ist <= idx < domain.ied +end + +function get_physical_indices(domain::Domain) + return domain.ist:(domain.ied - 1) +end + +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/julia/flux.jl b/example/1d-linear-convection/weno3/julia/01j/julia/flux.jl new file mode 100644 index 000000000..047598f7a --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/julia/flux.jl @@ -0,0 +1,70 @@ +# julia/flux.jl +""" +通量计算器模块(与 flux.py 完全同构) +- 抽象基类 + 具体实现 +- 字段:cfd, config, mesh, wave_speed +""" + +include("mesh.jl") + +# ---------------------- 抽象基类 ---------------------- +""" +InviscidFluxCalculator 抽象类型 +Julia 无 ABC,用文档约定 +所有子类型必须实现 compute! +""" +abstract type InviscidFluxCalculator end + +# ---------------------- RusanovFluxCalculator ---------------------- +mutable struct RusanovFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function RusanovFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::RusanovFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = calc.wave_speed + c_R = calc.wave_speed + F_L = c_L * u_L + F_R = c_R * u_R + Smax = max(abs(c_L), abs(c_R)) + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + end +end + +# ---------------------- EngquistOsherFluxCalculator ---------------------- +mutable struct EngquistOsherFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function EngquistOsherFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::EngquistOsherFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + c = calc.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/julia/initial_condition.jl b/example/1d-linear-convection/weno3/julia/01j/julia/initial_condition.jl new file mode 100644 index 000000000..5e029e8d2 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/julia/initial_condition.jl @@ -0,0 +1,86 @@ +# julia/initial_condition.jl +""" +初始条件模块(Julia 版) +目标:与 Python initial_condition.py 行为完全一致 +""" + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象接口(Julia 无继承,用函数约定) ---------------------- +# 所有初始条件必须实现: +# evaluate_at(ic, x::AbstractVector{Float64}) -> Vector{Float64} +# apply(ic, solution) + +# ---------------------- StepFunctionIC ---------------------- +struct StepFunctionIC + config::Any +end + +function evaluate_at(ic::StepFunctionIC, x::AbstractVector{Float64}) + u0 = ones(Float64, length(x)) + for i in eachindex(x) + if 0.5 <= x[i] <= 1.0 + u0[i] = 2.0 + end + end + return u0 +end + +function apply(ic::StepFunctionIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- SineWaveIC ---------------------- +struct SineWaveIC + config::Any +end + +function evaluate_at(ic::SineWaveIC, x::AbstractVector{Float64}) + L = getfield_safe(ic.config, :domain_length, 2.0) + return sin.(2π * x / L) +end + +function apply(ic::SineWaveIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- GaussianPulseIC ---------------------- +struct GaussianPulseIC + config::Any +end + +function evaluate_at(ic::GaussianPulseIC, x::AbstractVector{Float64}) + center = getfield_safe(ic.config, :pulse_center, 0.5) + width = getfield_safe(ic.config, :pulse_width, 0.1) + return exp.(-((x .- center) ./ width).^2) +end + +function apply(ic::GaussianPulseIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- 公共辅助函数 ---------------------- +""" +将初始场 values 应用到 solution.u 的物理区域(含 ghost 的数组) +""" +function _apply_to_interior(solution, values) + domain = solution.domain + # Python: for i in range(domain.ist, domain.ied) + # Julia: for i in domain.ist:domain.ied-1 (因为 ied 是结束索引,不包含) + for i in domain.ist:(domain.ied - 1) + j = i - domain.ist + 1 # values 是 1-based,i - ist 是 0-based → +1 + solution.u[i] = values[j] + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/julia/mesh.jl b/example/1d-linear-convection/weno3/julia/01j/julia/mesh.jl new file mode 100644 index 000000000..1b0dd4bf9 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/julia/mesh.jl @@ -0,0 +1,45 @@ +# julia/mesh.jl +""" +Mesh 结构体(与提供的 mesh.py 完全同构) +- 字段名、初始化顺序、循环逻辑完全一致 +- 不做任何泛化或优化 +""" + +mutable struct Mesh + xmin::Float64 + xmax::Float64 + ncells::Int + nnodes::Int + nx::Int + x::Vector{Float64} + xcc::Vector{Float64} + L::Float64 # 在 init_mesh 中赋值 + dx::Float64 # 在 init_mesh 中赋值 + + function Mesh() + # 与 Python __init__ 完全一致 + xmin = 0.0 + xmax = 2.0 + ncells = 40 + nnodes = ncells + 1 + nx = ncells + x = zeros(Float64, nnodes) + xcc = zeros(Float64, ncells) + + # 调用 init_mesh(模拟 Python) + L = xmax - xmin + dx = L / ncells + + # Generate node coordinates: for i in range(self.nnodes) + for i in 1:nnodes + x[i] = xmin + (i - 1) * dx # Python i → Julia i-1 + end + + # Generate cell center coordinates + for i in 1:ncells + xcc[i] = 0.5 * (x[i] + x[i+1]) + end + + new(xmin, xmax, ncells, nnodes, nx, x, xcc, L, dx) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/julia/plotter.jl b/example/1d-linear-convection/weno3/julia/01j/julia/plotter.jl new file mode 100644 index 000000000..a77d8d688 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/julia/plotter.jl @@ -0,0 +1,147 @@ +# julia/plotter.jl +""" +CFDPlotter 的 Julia 实现(通过 PythonCall.jl 调用 Matplotlib) +确保与 Python plotter.py 行为完全一致 +""" + +using PythonCall + +# 初始化 Python 环境(加载 matplotlib, inflect) +const plt = pyimport("matplotlib.pyplot") +const inflect = pyimport("inflect") + +mutable struct CFDPlotter + default_styles::Dict{String, Any} + p::Py +end + +function CFDPlotter() + default_styles = Dict{String, Any}( + "numerical" => Dict( + :color => "blue", + :linestyle => "-", + :marker => "o", + :markerfacecolor => "none" + ), + "analytical" => Dict( + :color => "red", + :linestyle => "--", + :marker => "", + :linewidth => 1.5 + ), + "comparison" => [ + Dict(:color => "black", :linestyle => "-", :marker => "o", :markerfacecolor => "none"), + Dict(:color => "blue", :linestyle => "--", :marker => "s", :markerfacecolor => "none"), + Dict(:color => "green", :linestyle => ":", :marker => "^", :markerfacecolor => "none") + ] + ) + p = inflect.engine() + CFDPlotter(default_styles, p) +end + +""" +轻量即时绘图(快速验证结果) +""" +function plot_quick(plotter::CFDPlotter, cfd_result::Dict; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + rk_order = cfd_result["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = cfd_result["config"]["final_time"] + order = cfd_result["config"]["order"] + scheme = uppercase(cfd_result["config"]["scheme"]) + actual_title = "1D Convection (t=$(final_time))\n$(order)th-order $(scheme) + $(rk_str)-order RK" + end + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"]; + label="Numerical ($(uppercase(cfd_result["config"]["scheme"])))", + plotter.default_styles["numerical"]..., + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + # 通用样式 + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +""" +多格式/多精度对比绘图 +""" +function plot_comparison(plotter::CFDPlotter, result_list::Vector{Dict{String, Any}}; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + schemes = [uppercase(r["config"]["scheme"]) * string(r["config"]["order"]) for r in result_list] + rk_order = result_list[1]["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = result_list[1]["config"]["final_time"] + actual_title = "1D Convection Comparison (t=$(final_time))\n$(join(schemes, ", ")) + $(rk_str)-order RK" + end + + # 绘制多个数值解 + for (i, res) in enumerate(result_list) + style = plotter.default_styles["comparison"][mod1(i, length(plotter.default_styles["comparison"]))] + label = "Numerical ($(uppercase(res["config"]["scheme"]))$(res["config"]["order"]))" + plt.plot( + res["x"], res["numerical"]; + label=label, + style..., + markersize=5, linewidth=0.5 + ) + end + + # 绘制解析解 + plt.plot( + result_list[1]["x"], result_list[1]["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +function _set_common_style(plotter::CFDPlotter, title::String) + plt.title(title, fontsize=12) + plt.xlabel("x", fontsize=10) + plt.ylabel("u", fontsize=10) + plt.legend(fontsize=9) + plt.grid(true, color="gray", linestyle="--", linewidth=0.5, alpha=0.7) + plt.tight_layout() +end + +""" +快捷函数:ENO/WENO对比绘图 +""" +function plot_eno_weno_comparison(eno_result::Dict, weno_result::Dict; save_path=nothing) + plotter = CFDPlotter() + plot_comparison(plotter, [eno_result, weno_result]; save_path=save_path) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/julia/reconstructor/eno.jl b/example/1d-linear-convection/weno3/julia/01j/julia/reconstructor/eno.jl new file mode 100644 index 000000000..e78a636f3 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/julia/reconstructor/eno.jl @@ -0,0 +1,107 @@ +# julia/reconstructor/eno.jl +""" +ENO 重构器(与 reconstructor/eno.py 完全同构) +""" + +# ---------------------- ENO 系数初始化 ---------------------- +function _init_eno_coef!(spatial_order::Int, coef::Matrix{Float64}) + if spatial_order == 1 + coef[1, 1] = 1.0 + coef[2, 1] = 1.0 + elseif spatial_order == 2 + coef[1, 1:2] = [3.0/2.0, -1.0/2.0] + coef[2, 1:2] = [1.0/2.0, 1.0/2.0] + coef[3, 1:2] = [-1.0/2.0, 3.0/2.0] + elseif spatial_order == 3 + coef[1, 1:3] = [11.0/6.0, -7.0/6.0, 1.0/3.0] + coef[2, 1:3] = [1.0/3.0, 5.0/6.0, -1.0/6.0] + coef[3, 1:3] = [-1.0/6.0, 5.0/6.0, 1.0/3.0] + coef[4, 1:3] = [1.0/3.0, -7.0/6.0, 11.0/6.0] + elseif spatial_order == 4 + coef[1, 1:4] = [25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0] + coef[2, 1:4] = [1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0] + coef[3, 1:4] = [-1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0] + coef[4, 1:4] = [1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0] + coef[5, 1:4] = [-1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0] + elseif spatial_order == 5 + coef[1, 1:5] = [137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0] + coef[2, 1:5] = [1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0] + coef[3, 1:5] = [-1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0] + coef[4, 1:5] = [1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0] + coef[5, 1:5] = [-1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0] + coef[6, 1:5] = [1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0] + elseif spatial_order == 6 + coef[1, 1:6] = [49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0] + coef[2, 1:6] = [1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0] + coef[3, 1:6] = [-1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0] + coef[4, 1:6] = [1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0] + coef[5, 1:6] = [-1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0] + coef[6, 1:6] = [1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0] + coef[7, 1:6] = [-1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0] + elseif spatial_order == 7 + coef[1, 1:7] = [363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0] + coef[2, 1:7] = [1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0] + coef[3, 1:7] = [-1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0] + coef[4, 1:7] = [1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0] + coef[5, 1:7] = [-1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0] + coef[6, 1:7] = [1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0] + coef[7, 1:7] = [-1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0] + coef[8, 1:7] = [1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0] + else + error("ENO 系数未实现 order=$spatial_order") + end +end + +# ---------------------- ENO 重构器 ---------------------- +mutable struct EnoReconstructor + spatial_order::Int + ntcells::Int + lmc::Vector{Int} + coef::Matrix{Float64} + dd::Matrix{Float64} + + function EnoReconstructor(spatial_order::Int, ntcells::Int) + lmc = zeros(Int, ntcells) + coef = zeros(Float64, spatial_order + 1, spatial_order) + dd = zeros(Float64, spatial_order, ntcells) + _init_eno_coef!(spatial_order, coef) + new(spatial_order, ntcells, lmc, coef, dd) + end +end + +function reconstruct(rec::EnoReconstructor, q::Vector{Float64}, cfd::Any) + # 1. 差商计算 (dd[1,:] = q) + @views rec.dd[1, :] .= q + for m in 2:rec.spatial_order + for j in 1:(rec.ntcells - m + 1) + rec.dd[m, j] = rec.dd[m-1, j+1] - rec.dd[m-1, j] + end + end + + # 2. 选择 smoothest stencil + domain = cfd.domain + for i in (domain.ist - 1):(domain.ied) # Python: range(ist-1, ied+1) → ied+1-1 = ied + rec.lmc[i] = i + for m in 2:rec.spatial_order + if abs(rec.dd[m, rec.lmc[i] - 1]) < abs(rec.dd[m, rec.lmc[i]]) + rec.lmc[i] -= 1 + end + end + end + + # 3. 重构界面值 + solution = cfd.solution + for i in domain.ist:(domain.ied) # Python: range(ist, ied+1) → ied+1-1 = ied + j = i - domain.ist + 1 # Julia 1-based + k1 = rec.lmc[i - 1] + k2 = rec.lmc[i] + r1 = (i - 1) - k1 + 1 + r2 = i - k2 + 1 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in 1:rec.spatial_order + solution.q_face_left[j] += q[k1 + m - 1] * rec.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m - 1] * rec.coef[r2, m] + end + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/julia/reconstructor/weno3.jl b/example/1d-linear-convection/weno3/julia/01j/julia/reconstructor/weno3.jl new file mode 100644 index 000000000..2b6fe1abb --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/julia/reconstructor/weno3.jl @@ -0,0 +1,65 @@ +# julia/reconstructor/weno3.jl +""" +WENO3 重构器(与 reconstructor/weno3.py 完全同构) +""" + +mutable struct Weno3Reconstructor + # 无字段,与 Python 一致 +end + +function reconstruct(rec::Weno3Reconstructor, q::Vector{Float64}, cfd::Any) + domain = cfd.domain + solution = cfd.solution + _reconstruct_left_interfaces(domain, q, solution.q_face_left) + _reconstruct_right_interfaces(domain, q, solution.q_face_right) +end + +function _reconstruct_left_interfaces(domain, u, qL) + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in (domain.ist - 1):(domain.ied - 1) + j = i - (domain.ist - 1) + 1 # ← Julia 1-based: j = i - (ist-1) 对应 Python j = i - (ist-1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = _reconstruct_from_left_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_right_interfaces(domain, u, qR) + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in domain.ist:domain.ied + j = i - domain.ist + 1 # ← Julia 1-based: j = i - ist 对应 Python j = i - ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = _reconstruct_from_right_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_from_left_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -0.5*v1 + 1.5*v2 # r=1 stencil + q1 = 0.5*v2 + 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end + +function _reconstruct_from_right_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 0.5*v1 + 0.5*v2 # r=1 stencil + q1 = 1.5*v2 - 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/julia/residual.jl b/example/1d-linear-convection/weno3/julia/01j/julia/residual.jl new file mode 100644 index 000000000..e8fd65843 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/julia/residual.jl @@ -0,0 +1,65 @@ +# julia/residual.jl +""" +残差计算器(与 residual.py 完全同构) +- 封装重建→通量→散度完整流程 +- 依赖 cfd 的多个字段 +""" + +include("mesh.jl") + +mutable struct ResidualCalculator + cfd::Any + config::Any + domain::Any + solution::Any + mesh::Mesh + reconstructor::Any + flux_calculator::Any # 通量计算器(外部传入,替代工厂) + + function ResidualCalculator(cfd::Any, flux_calculator::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + mesh = domain.mesh + reconstructor = cfd.reconstructor + + new(cfd, config, domain, solution, mesh, reconstructor, flux_calculator) + end +end + +""" +计算完整残差(对外唯一接口) +""" +function compute!(calc::ResidualCalculator) + _reconstruct(calc) + _compute_inviscid_flux(calc) + _compute_flux_divergence(calc) +end + +""" +私有方法:界面值重建 +""" +function _reconstruct(calc::ResidualCalculator) + reconstruct(calc.reconstructor, calc.solution.u, calc.cfd) +end + +""" +私有方法:计算无粘通量 +""" +function _compute_inviscid_flux(calc::ResidualCalculator) + compute!(calc.flux_calculator, + calc.solution.q_face_left, + calc.solution.q_face_right, + calc.solution.flux) +end + +""" +私有方法:计算通量散度(残差 = -dF/dx) +""" +function _compute_flux_divergence(calc::ResidualCalculator) + solution = calc.solution + mesh = calc.mesh + for i in 1:mesh.ncells + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / mesh.dx + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/julia/run_eno_weno.jl b/example/1d-linear-convection/weno3/julia/01j/julia/run_eno_weno.jl new file mode 100644 index 000000000..58b6cfa5f --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/julia/run_eno_weno.jl @@ -0,0 +1,50 @@ +# julia/run_eno_weno.jl +""" +1:1 复刻 run_eno_weno.py 的 Julia 版本 +""" + +include("config.jl") +include("mesh.jl") +include("solver.jl") +include("plotter.jl") + +function performEnoWenoAnalysis() + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO3 求解 + println("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + with_reconstruction(config_eno3, "eno", 3) # 显式指定 3 阶 + config_eno3.dt = 0.0025 # 覆盖默认值 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + run!(cfd_eno3) # 求解并生成 result 字典 + + # 3. 配置并运行 WENO3 求解 + println("Running WENO3 solver...") + config_weno3 = CfdConfig() + with_reconstruction(config_weno3, "weno", 3) # 显式指定 3 阶(WENO 默认 5 阶) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + run!(cfd_weno3) + + # 5. 绘制 ENO/WENO 对比图 + println("Plotting comparison results...") + plot_eno_weno_comparison( + cfd_eno3.result, + cfd_weno3.result; + save_path="eno_weno_comparison.png" + ) + + return cfd_eno3, cfd_weno3 +end + +# 主程序入口 +if abspath(PROGRAM_FILE) == @__FILE__ + performEnoWenoAnalysis() + println("Analysis completed!") +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/julia/solution.jl b/example/1d-linear-convection/weno3/julia/01j/julia/solution.jl new file mode 100644 index 000000000..90ca03931 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/julia/solution.jl @@ -0,0 +1,75 @@ +# julia/solution.jl +""" +Solution 结构体(与真实 solution.py 完全同构) +字段顺序、方法、逻辑 1:1 对齐 +""" + +include("mesh.jl") +include("domain.jl") +include("initial_condition.jl") + +mutable struct Solution + domain::Domain + q_face_left::Vector{Float64} + q_face_right::Vector{Float64} + flux::Vector{Float64} + res::Vector{Float64} + u::Vector{Float64} + un::Vector{Float64} + + function Solution(config::Any, domain::Domain) + mesh = domain.mesh + + q_face_left = zeros(Float64, mesh.nnodes) + q_face_right = zeros(Float64, mesh.nnodes) + flux = zeros(Float64, mesh.nnodes) + res = zeros(Float64, mesh.ncells) + u = zeros(Float64, domain.ntcells) + un = zeros(Float64, domain.ntcells) + + sol = new(domain, q_face_left, q_face_right, flux, res, u, un) + initialize_from_config(sol, config) + return sol + end +end + +""" +重置解数组为初始状态 +""" +function reset_solution(sol::Solution) + fill!(sol.u, 0.0) + fill!(sol.un, 0.0) +end + +""" +根据配置初始化场 +注:暂用硬编码替代 InitialConditionFactory +""" +function initialize_from_config(sol::Solution, config::Any) + ic_type = getfield_safe(config, :ic_type, "step") + + # 硬编码创建 IC(替代 InitialConditionFactory) + ic = if ic_type == "step" + StepFunctionIC(config) + elseif ic_type == "sin" + SineWaveIC(config) + elseif ic_type == "gaussian" + GaussianPulseIC(config) + else + error("未知初始条件类型: $ic_type") + end + + apply(ic, sol) # 调用 IC.apply +end + +""" +更新旧场:un = u +""" +function update_old_field(sol::Solution) + sol.un .= sol.u # 等价于 Python 的 un[:] = u[:] +end + +# ---------------------- 辅助函数 ---------------------- +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/julia/solver.jl b/example/1d-linear-convection/weno3/julia/01j/julia/solver.jl new file mode 100644 index 000000000..056628944 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/julia/solver.jl @@ -0,0 +1,167 @@ +# julia/solver.jl +""" +CFD 求解器主类(与 solver.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("initial_condition.jl") +include("boundary.jl") +include("flux.jl") +include("residual.jl") +include("time_integration.jl") +include("reconstructor/eno.jl") +include("reconstructor/weno3.jl") + +# ---------------------- Cfd 求解器 ---------------------- +mutable struct Cfd + config::Any + domain::Domain + solution::Solution + reconstructor::Any + residual_calculator::ResidualCalculator + integrator::Any + boundary_condition::Any + result::Dict{String, Any} + + function Cfd(config::Any, mesh::Mesh) + domain = Domain(config, mesh) + solution = Solution(config, domain) + + # 在 Cfd 构造函数中 + # =============== 重建器创建 =============== + recon_scheme = config.recon_scheme + spatial_order = config.spatial_order + + # ✅ 模拟 Python ReconstructorFactory 的逻辑 + if recon_scheme == "weno" + recon_scheme = "weno$(spatial_order)" + end + + # 现在根据 recon_scheme 创建 + reconstructor = if recon_scheme == "eno" + EnoReconstructor(spatial_order, domain.ntcells) + elseif recon_scheme == "weno3" + Weno3Reconstructor() + else + error("不支持的重建格式: $recon_scheme") + end + + # 通量计算器 + flux_calculator = if config.flux_type == "rusanov" + RusanovFluxCalculator((config=config, domain=domain)) + elseif config.flux_type == "engquist-osher" + EngquistOsherFluxCalculator((config=config, domain=domain)) + else + error("不支持的通量类型: $(config.flux_type)") + end + + # 残差计算器 + residual_calculator = ResidualCalculator( + (config=config, domain=domain, solution=solution, reconstructor=reconstructor), + flux_calculator + ) + + # 边界条件 + boundary_condition = if config.boundary_type == "periodic" + PeriodicBoundary((config=config, domain=domain)) + elseif config.boundary_type == "dirichlet" + DirichletBoundary((config=config, domain=domain)) + elseif config.boundary_type == "neumann" + NeumannBoundary((config=config, domain=domain)) + else + error("不支持的边界类型: $(config.boundary_type)") + end + + # 时间推进器 + integrator = if config.rk_order == 1 + RK1Integrator((config=config, domain=domain, solution=solution, residual_calculator=residual_calculator, boundary_condition=boundary_condition)) + elseif config.rk_order == 2 + RK2Integrator((config=config, domain=domain, solution=solution, residual_calculator=residual_calculator, boundary_condition=boundary_condition)) + elseif config.rk_order == 3 + RK3Integrator((config=config, domain=domain, solution=solution, residual_calculator=residual_calculator, boundary_condition=boundary_condition)) + else + error("不支持的 RK 阶数: $(config.rk_order)") + end + + #@show typeof(integrator) + + # 注入 cfd 到 residual_calculator 和 integrator + residual_calculator.cfd = (config=config, domain=domain, solution=solution, reconstructor=reconstructor, residual_calculator=residual_calculator, integrator=integrator, boundary_condition=boundary_condition) + integrator.base.cfd = residual_calculator.cfd + + result = Dict{String, Any}() + new(config, domain, solution, reconstructor, residual_calculator, integrator, boundary_condition, result) + end +end + +""" +通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界 +""" +function exact_solution(cfd::Cfd) + x = cfd.domain.mesh.xcc + T = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = @. (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = if cfd.config.ic_type == "step" + StepFunctionIC(cfd.config) + elseif cfd.config.ic_type == "sin" + SineWaveIC(cfd.config) + elseif cfd.config.ic_type == "gaussian" + GaussianPulseIC(cfd.config) + else + error("未知初始条件: $(cfd.config.ic_type)") + end + + return evaluate_at(ic, x_shifted) +end + +""" +主求解循环 +""" +function run!(cfd::Cfd) + # 应用初始边界条件并同步 old field + apply!(cfd.boundary_condition, cfd.solution.u) + update_old_field(cfd.solution) + + t = 0.0 + dt_old = cfd.config.dt + dt = dt_old + + while t < cfd.config.final_time + if t + dt > cfd.config.final_time + dt = cfd.config.final_time - t + end + #@show t, dt, maximum(cfd.solution.u), minimum(cfd.solution.u) + # 执行时间步 + step(cfd.integrator, dt) + t += dt + end + + # 恢复 dt + cfd.config.dt = dt_old + + # 整理结果 + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied-1] # Python: [ist:ied] + analytical = exact_solution(cfd) + + cfd.result = Dict( + "x" => cfd.domain.mesh.xcc, + "numerical" => u_numerical, + "analytical" => analytical, + "config" => Dict( + "scheme" => cfd.config.recon_scheme, + "order" => cfd.config.spatial_order, + "rk_order" => cfd.config.rk_order, + "final_time" => cfd.config.final_time + ) + ) + + return u_numerical +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/julia/test/test_boundary.jl b/example/1d-linear-convection/weno3/julia/01j/julia/test/test_boundary.jl new file mode 100644 index 000000000..ef27d6820 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/julia/test/test_boundary.jl @@ -0,0 +1,57 @@ +# julia/test/test_boundary.jl +using NPZ +include("../boundary.jl") + +struct MockConfig + boundary_type::String + left_boundary_value::Float64 + right_boundary_value::Float64 + debug::Bool +end + +struct MockDomain + nghosts::Int + ist::Int # Julia 本地索引(1-based) + ied::Int + ntcells::Int +end + +struct MockCfd + config::MockConfig + domain::MockDomain +end + +# ===== 关键:使用 Julia 本地索引规则 ===== +nghosts = 2 +ncells = 40 +ist = nghosts + 1 # = 3 +ied = ist + ncells # = 43 +ntcells = ncells + 2 * nghosts # = 44 + +config = MockConfig("dirichlet", 0.5, 1.5, true) +domain = MockDomain(nghosts, ist, ied, ntcells) +cfd_mock = MockCfd(config, domain) + +# 加载 Python 生成的 u_input.npy +# 注意:Python u[0] → Julia u[1],所以内容完全对应 +u_input = npzread("../../python/u_input.npy") +@assert length(u_input) == ntcells "数组长度不匹配!" + +# 测试三种边界 +test_cases = [ + ("periodic", PeriodicBoundary(cfd_mock)), + ("dirichlet", DirichletBoundary(cfd_mock)), + ("neumann", NeumannBoundary(cfd_mock)) +] + +for (name, bc) in test_cases + u = copy(u_input) + apply!(bc, u) + + u_py = npzread("../../python/u_$(name)_py.npy") + err = maximum(abs.(u .- u_py)) + println("边界: $(name) | 最大误差: $(err)") + @assert err < 1e-12 "❌ $(name) 不一致!" +end + +println("✅ 所有边界条件测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/julia/test/test_domain.jl b/example/1d-linear-convection/weno3/julia/01j/julia/test/test_domain.jl new file mode 100644 index 000000000..7b423f774 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/julia/test/test_domain.jl @@ -0,0 +1,44 @@ +# julia/test/test_domain.jl +include("../mesh.jl") +include("../domain.jl") + +# MockConfig:模拟 Python CfdConfig +struct MockConfig + recon_scheme::String + spatial_order::Int +end + +# 测试 ENO +config_eno = MockConfig("eno", 2) +mesh = Mesh() +domain_eno = Domain(config_eno, mesh) + +println("ENO: nghosts = ", domain_eno.nghosts) # 2 +println("ENO: ist = ", domain_eno.ist) # 2 +println("ENO: ied = ", domain_eno.ied) # 42 +println("物理索引范围: ", collect(get_physical_indices(domain_eno))[1:3], " ... ", collect(get_physical_indices(domain_eno))[end-2:end]) + +# 测试 WENO(字符串 "weno") +config_weno = MockConfig("weno", 2) +domain_weno = Domain(config_weno, mesh) +println("WENO: nghosts = ", domain_weno.nghosts) # 2 + +# 测试 WENO3(字符串 "weno3") +config_weno3 = MockConfig("weno3", 2) +domain_weno3 = Domain(config_weno3, mesh) +println("WENO3: nghosts = ", domain_weno3.nghosts) # 2 + +# 测试 is_physical_cell +@assert is_physical_cell(domain_eno, 2) == true # ist=2 +@assert is_physical_cell(domain_eno, 41) == true # ied-1=41 +@assert is_physical_cell(domain_eno, 42) == false # ied=42 + +# ✅ 断言 +@assert domain_eno.nghosts == 2 +@assert domain_eno.ist == 2 +@assert domain_eno.ied == 42 +@assert domain_eno.ntcells == 44 +@assert domain_weno.nghosts == 2 +@assert domain_weno3.nghosts == 2 + +println("✅ Domain 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/julia/test/test_eno.jl b/example/1d-linear-convection/weno3/julia/01j/julia/test/test_eno.jl new file mode 100644 index 000000000..17b9a7a48 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/julia/test/test_eno.jl @@ -0,0 +1,34 @@ +# julia/test/test_eno.jl +include("../mesh.jl") +include("../domain.jl") +include("../solution.jl") +include("../reconstructor/eno.jl") + +struct MockCfd + domain::Domain + solution::Solution +end + +mesh = Mesh() +# 注意:Domain 使用 1-based 索引(ist = nghosts + 1) +domain = Domain((recon_scheme="eno", spatial_order=2), mesh) +solution = Solution((ic_type="step",), domain) + +# 线性初始场: u[i] = (i-1) * 0.1 +u = solution.u +for i in 1:domain.ntcells + u[i] = Float64(i-1) * 0.1 +end + +eno = EnoReconstructor(2, domain.ntcells) +cfd = MockCfd(domain, solution) +reconstruct(eno, u, cfd) + +println("q_face_left[1] = ", solution.q_face_left[1]) +println("q_face_right[1] = ", solution.q_face_right[1]) + +# ENO2 对线性函数应精确重构 0.15 +@assert abs(solution.q_face_left[1] - 0.15) < 1e-12 +@assert abs(solution.q_face_right[1] - 0.15) < 1e-12 + +println("✅ ENO 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/julia/test/test_flux.jl b/example/1d-linear-convection/weno3/julia/01j/julia/test/test_flux.jl new file mode 100644 index 000000000..0ebc1dc05 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/julia/test/test_flux.jl @@ -0,0 +1,53 @@ +# julia/test/test_flux.jl +include("../mesh.jl") +include("../flux.jl") + +# Mock 对象 +struct MockConfig + flux_type::String + wave_speed::Float64 +end + +struct MockDomain + mesh::Mesh +end + +struct MockCfd + config::MockConfig + domain::MockDomain +end + +# 创建 mock +mesh = Mesh() +config = MockConfig("rusanov", 1.0) +domain = MockDomain(mesh) +cfd = MockCfd(config, domain) + +# 测试 Rusanov +rusanov = RusanovFluxCalculator(cfd) +N = mesh.nnodes +qL = [1.0, 2.0, 3.0, 4.0, 5.0, zeros(Float64, N-5)...] +qR = [2.0, 3.0, 4.0, 5.0, 6.0, zeros(Float64, N-5)...] +flux_rus = zeros(Float64, N) +compute!(rusanov, qL, qR, flux_rus) + +println("Rusanov flux[1] = ", flux_rus[1]) # 应为 1.0 +@assert abs(flux_rus[1] - 1.0) < 1e-12 + +# 测试 Engquist-Osher +eo = EngquistOsherFluxCalculator(cfd) +flux_eo = zeros(Float64, N) +compute!(eo, qL, qR, flux_eo) + +println("EO flux[1] = ", flux_eo[1]) # c=1 → cp=1, cm=0 → flux=1*1 + 0*2 = 1.0 +@assert abs(flux_eo[1] - 1.0) < 1e-12 + +# 测试 c = -1.0 +config_neg = MockConfig("rusanov", -1.0) +cfd_neg = MockCfd(config_neg, domain) +rusanov_neg = RusanovFluxCalculator(cfd_neg) +compute!(rusanov_neg, qL, qR, flux_rus) +println("Rusanov (c=-1) flux[1] = ", flux_rus[1]) # F_L=-1, F_R=-2, Smax=1 → flux = -1.5 -0.5*(-1) = -1.0 +@assert abs(flux_rus[1] - (-2.0)) < 1e-12 # ← 修正:-2.0 而非 -1.0 + +println("✅ Flux 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/julia/test/test_initial_condition.jl b/example/1d-linear-convection/weno3/julia/01j/julia/test/test_initial_condition.jl new file mode 100644 index 000000000..dc58691a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/julia/test/test_initial_condition.jl @@ -0,0 +1,45 @@ +# julia/test/test_initial_condition.jl +using NPZ + +# 包含初始条件模块 +include("../initial_condition.jl") + +# 构造 mock config(与 Python 完全一致) +struct MockConfig + ic_type::String + domain_length::Float64 + pulse_center::Float64 + pulse_width::Float64 +end + +# 生成与 Python Mesh.xcc 完全相同的 x 坐标 +function generate_xcc() + xmin, xmax = 0.0, 2.0 + ncells = 40 + dx = (xmax - xmin) / ncells + xcc = Vector{Float64}(undef, ncells) + for i in 1:ncells + xcc[i] = xmin + (i - 0.5) * dx # i-1 + 0.5 → i-0.5 + end + return xcc +end + +# 主测试 +xcc = generate_xcc() + +test_cases = [ + ("step", StepFunctionIC(MockConfig("step", 2.0, 0.5, 0.1))), + ("sin", SineWaveIC(MockConfig("sin", 2.0, 0.5, 0.1))), + ("gaussian", GaussianPulseIC(MockConfig("gaussian", 2.0, 0.5, 0.1))) +] + +for (name, ic) in test_cases + u_jl = evaluate_at(ic, xcc) + u_py = npzread("../../python/u_$(name)_interior_py.npy") + + err = maximum(abs.(u_jl .- u_py)) + println("IC: $(name) | 最大误差: $(err)") + @assert err < 1e-12 "❌ $(name) 不一致!" +end + +println("✅ 所有初始条件测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/julia/test/test_mesh.jl b/example/1d-linear-convection/weno3/julia/01j/julia/test/test_mesh.jl new file mode 100644 index 000000000..315d2aac2 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/julia/test/test_mesh.jl @@ -0,0 +1,37 @@ +# julia/test/test_mesh.jl +include("../mesh.jl") + +# 创建 mesh(与 Python 完全相同) +mesh = Mesh() + +# 打印关键值(与 Python 对比) +println("xmin = ", mesh.xmin) # 0.0 +println("xmax = ", mesh.xmax) # 2.0 +println("ncells = ", mesh.ncells) # 40 +println("nnodes = ", mesh.nnodes) # 41 +println("nx = ", mesh.nx) # 40 +println("L = ", mesh.L) # 2.0 +println("dx = ", mesh.dx) # 0.05 + +# 检查 x[1] (Python x[0]) 和 x[41] (Python x[40]) +println("x[1] = ", mesh.x[1]) # 0.0 +println("x[41] = ", mesh.x[41]) # 2.0 + +# 检查 xcc[1] (Python xcc[0]) 和 xcc[40] (Python xcc[39]) +println("xcc[1] = ", mesh.xcc[1]) # 0.025 +println("xcc[40] = ", mesh.xcc[40]) # 1.975 + +# ✅ 严格断言 +@assert mesh.xmin == 0.0 +@assert mesh.xmax == 2.0 +@assert mesh.ncells == 40 +@assert mesh.nnodes == 41 +@assert mesh.nx == 40 +@assert mesh.L == 2.0 +@assert mesh.dx == 0.05 +@assert mesh.x[1] == 0.0 +@assert mesh.x[41] == 2.0 +@assert abs(mesh.xcc[1] - 0.025) < 1e-12 +@assert abs(mesh.xcc[40] - 1.975) < 1e-12 + +println("✅ Mesh 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/julia/test/test_residual.jl b/example/1d-linear-convection/weno3/julia/01j/julia/test/test_residual.jl new file mode 100644 index 000000000..e37e8d2bd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/julia/test/test_residual.jl @@ -0,0 +1,53 @@ +# julia/test/test_residual.jl +include("../mesh.jl") +include("../domain.jl") +include("../solution.jl") +include("../flux.jl") +include("../residual.jl") + +# ===== Dummy Reconstructor ===== +struct DummyReconstructor end + +# ===== Mock Config ===== +struct MockConfig + flux_type::String + wave_speed::Float64 +end + +# ===== Mock CFD (必须包含 reconstructor 字段) ===== +struct MockCfd + config::MockConfig + domain::Domain + solution::Solution + reconstructor::DummyReconstructor # ← 关键:添加此字段 +end + +# ===== 主测试 ===== +config = MockConfig("rusanov", 1.0) +mesh = Mesh() +domain = Domain((recon_scheme="eno", spatial_order=2), mesh) +solution = Solution((ic_type="step",), domain) + +# 手动设置界面值(跳过重建) +for i in 1:mesh.nnodes + solution.q_face_left[i] = Float64(i) * 0.1 + solution.q_face_right[i] = Float64(i) * 0.1 +end + +flux_calc = RusanovFluxCalculator((config=config, domain=domain)) +dummy_recon = DummyReconstructor() +cfd = MockCfd(config, domain, solution, dummy_recon) + +# 创建残差计算器 +res_calc = ResidualCalculator(cfd, flux_calc) + +# 计算残差(注意:_reconstruct 仍被注释) +compute!(res_calc) + +println("flux[1] = ", solution.flux[1]) +println("res[1] = ", solution.res[1]) + +@assert abs(solution.flux[1] - 0.1) < 1e-12 +@assert abs(solution.res[1] - (-2.0)) < 1e-12 + +println("✅ Residual 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/julia/test/test_solution.jl b/example/1d-linear-convection/weno3/julia/01j/julia/test/test_solution.jl new file mode 100644 index 000000000..f44ff0f0e --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/julia/test/test_solution.jl @@ -0,0 +1,42 @@ +# julia/test/test_solution.jl +include("../mesh.jl") +include("../domain.jl") +include("../solution.jl") + +# MockConfig +struct MockConfig + recon_scheme::String + spatial_order::Int + ic_type::String + domain_length::Float64 + pulse_center::Float64 + pulse_width::Float64 +end + +# 创建 solution +config = MockConfig("eno", 2, "step", 2.0, 0.5, 0.1) +mesh = Mesh() +domain = Domain(config, mesh) +sol = Solution(config, domain) + +# 检查字段尺寸 +@assert length(sol.q_face_left) == mesh.nnodes # 41 +@assert length(sol.flux) == mesh.nnodes # 41 +@assert length(sol.res) == mesh.ncells # 40 +@assert length(sol.u) == domain.ntcells # 44 + +# 检查初始场 +println("u[3] (物理起始): ", sol.u[3]) # 应为 1.0 +println("u[23] (x=1.0): ", sol.u[23]) # 应为 2.0 + +# 测试 update_old_field +sol.u[3] = 999.0 +update_old_field(sol) +@assert sol.un[3] == 999.0 + +# 测试 reset_solution +reset_solution(sol) +@assert sol.u[3] == 0.0 +@assert sol.un[3] == 0.0 + +println("✅ Solution 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/julia/test/test_time_integration.jl b/example/1d-linear-convection/weno3/julia/01j/julia/test/test_time_integration.jl new file mode 100644 index 000000000..091a8637c --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/julia/test/test_time_integration.jl @@ -0,0 +1,53 @@ +# julia/test/test_time_integration.jl +include("../time_integration.jl") +include("../flux.jl") +include("../boundary.jl") + +# Mock CFD 对象 +struct MockCfd + config::Any + domain::Domain + solution::Solution + residual_calculator::Any + boundary_condition::Any +end + +# 创建完整链路 +mesh = Mesh() +domain = Domain((recon_scheme="eno", spatial_order=2), mesh) +solution = Solution((ic_type="step",), domain) + +# 手动设置界面值(跳过 reconstructor) +for i in 1:mesh.nnodes + solution.q_face_left[i] = Float64(i) * 0.1 + solution.q_face_right[i] = Float64(i) * 0.1 +end + +# Mock components +flux_calc = RusanovFluxCalculator((config=(wave_speed=1.0,), domain=domain)) +res_calc = ResidualCalculator((config=nothing, domain=domain, solution=solution, reconstructor=nothing), flux_calc) +bc = PeriodicBoundary((domain=domain, config=(debug=false,))) +cfd = MockCfd((rk_order=1,), domain, solution, res_calc, bc) + +# 测试 RK1 +rk1 = RK1Integrator(cfd) +step(rk1, 0.025) + +println("RK1 step completed") +@assert solution.u[3] != 0.0 # 应已更新 + +# 测试 RK2 +cfd2 = MockCfd((rk_order=2,), domain, solution, res_calc, bc) +rk2 = RK2Integrator(cfd2) +step(rk2, 0.025) + +println("RK2 step completed") + +# 测试 RK3 +cfd3 = MockCfd((rk_order=3,), domain, solution, res_calc, bc) +rk3 = RK3Integrator(cfd3) +step(rk3, 0.025) + +println("RK3 step completed") + +println("✅ Time Integration 逻辑测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/julia/test/test_weno3.jl b/example/1d-linear-convection/weno3/julia/01j/julia/test/test_weno3.jl new file mode 100644 index 000000000..9a91761e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/julia/test/test_weno3.jl @@ -0,0 +1,47 @@ +# julia/test/test_weno3.jl +include("../mesh.jl") +include("../domain.jl") +include("../solution.jl") +include("../reconstructor/weno3.jl") + +# Mock CFD +struct MockCfd + domain::Domain + solution::Solution +end + +# 创建数据 +mesh = Mesh() +# 注意:Domain 现在使用 1-based 索引(ist = nghosts + 1) +domain = Domain((recon_scheme="weno3", spatial_order=2), mesh) +solution = Solution((ic_type="step",), domain) + +# 设置 u(物理区域 + ghost) +u = solution.u +for i in 1:domain.ntcells + u[i] = Float64(i-1) * 0.1 # u = [0.0, 0.1, 0.2, ..., 4.3] +end + +# 创建 reconstructor +weno3 = Weno3Reconstructor() +cfd = MockCfd(domain, solution) + +# 重建 +reconstruct(weno3, u, cfd) + +println("q_face_left length = ", length(solution.q_face_left)) # 应为 41 +println("q_face_right length = ", length(solution.q_face_right)) # 应为 41 +println("q_face_left[1] = ", solution.q_face_left[1]) +println("q_face_right[1] = ", solution.q_face_right[1]) + +# 验证左界面值(i = ist-1 = 2, j = 1) +# v1 = u[1] = 0.0, v2 = u[2] = 0.1, v3 = u[3] = 0.2 +# qL = w0*(-0.5*0.0 + 1.5*0.1) + w1*(0.5*0.1 + 0.5*0.2) = w0*0.15 + w1*0.15 = 0.15 +@assert abs(solution.q_face_left[1] - 0.15) < 1e-12 + +# 验证右界面值(i = ist = 3, j = 1) +# v1 = u[2] = 0.1, v2 = u[3] = 0.2, v3 = u[4] = 0.3 +# qR = w0*(0.5*0.1 + 0.5*0.2) + w1*(1.5*0.2 - 0.5*0.3) = w0*0.15 + w1*0.15 = 0.15 +@assert abs(solution.q_face_right[1] - 0.15) < 1e-12 + +println("✅ WENO3 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/julia/test_residual.jl b/example/1d-linear-convection/weno3/julia/01j/julia/test_residual.jl new file mode 100644 index 000000000..315d2aac2 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/julia/test_residual.jl @@ -0,0 +1,37 @@ +# julia/test/test_mesh.jl +include("../mesh.jl") + +# 创建 mesh(与 Python 完全相同) +mesh = Mesh() + +# 打印关键值(与 Python 对比) +println("xmin = ", mesh.xmin) # 0.0 +println("xmax = ", mesh.xmax) # 2.0 +println("ncells = ", mesh.ncells) # 40 +println("nnodes = ", mesh.nnodes) # 41 +println("nx = ", mesh.nx) # 40 +println("L = ", mesh.L) # 2.0 +println("dx = ", mesh.dx) # 0.05 + +# 检查 x[1] (Python x[0]) 和 x[41] (Python x[40]) +println("x[1] = ", mesh.x[1]) # 0.0 +println("x[41] = ", mesh.x[41]) # 2.0 + +# 检查 xcc[1] (Python xcc[0]) 和 xcc[40] (Python xcc[39]) +println("xcc[1] = ", mesh.xcc[1]) # 0.025 +println("xcc[40] = ", mesh.xcc[40]) # 1.975 + +# ✅ 严格断言 +@assert mesh.xmin == 0.0 +@assert mesh.xmax == 2.0 +@assert mesh.ncells == 40 +@assert mesh.nnodes == 41 +@assert mesh.nx == 40 +@assert mesh.L == 2.0 +@assert mesh.dx == 0.05 +@assert mesh.x[1] == 0.0 +@assert mesh.x[41] == 2.0 +@assert abs(mesh.xcc[1] - 0.025) < 1e-12 +@assert abs(mesh.xcc[40] - 1.975) < 1e-12 + +println("✅ Mesh 测试通过!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/julia/time_integration.jl b/example/1d-linear-convection/weno3/julia/01j/julia/time_integration.jl new file mode 100644 index 000000000..95e5cd67f --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/julia/time_integration.jl @@ -0,0 +1,130 @@ +# julia/time_integration.jl +""" +时间推进器模块(与 time_integration.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("residual.jl") +include("boundary.jl") + +# ---------------------- 抽象时间推进器基类 ---------------------- +abstract type TimeIntegrator end + +mutable struct TimeIntegratorBase <: TimeIntegrator + cfd::Any + config::Any + domain::Domain + solution::Solution + residual_calculator::Any # ResidualCalculator +end + +function TimeIntegratorBase(cfd::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + residual_calculator = cfd.residual_calculator + TimeIntegratorBase(cfd, config, domain, solution, residual_calculator) +end + +function compute_residual(integrator::TimeIntegratorBase) + compute!(integrator.residual_calculator) +end + +function apply_boundary(integrator::TimeIntegratorBase) + apply!(integrator.cfd.boundary_condition, integrator.solution.u) +end + +function map_idx(integrator::TimeIntegratorBase, i::Int) + return i - integrator.domain.ist + 1 # ← +1 转为 1-based +end + +# ---------------------- RK1Integrator ---------------------- +mutable struct RK1Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK1Integrator(cfd::Any) + RK1Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK1Integrator, dt::Float64) + compute_residual(integrator.base) + for i in integrator.base.domain.ist:(integrator.base.domain.ied - 1) + j = map_idx(integrator.base, i) + integrator.base.solution.u[i] += dt * integrator.base.solution.res[j] + end + apply_boundary(integrator.base) + update_old_field(integrator.base.solution) +end + +# ---------------------- RK2Integrator ---------------------- +mutable struct RK2Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK2Integrator(cfd::Any) + RK2Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK2Integrator, dt::Float64) + base = integrator.base + # 阶段1:预测步 + compute_residual(base) + u_pred = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u_pred[i] += dt * base.solution.res[j] + end + base.solution.u .= u_pred + apply_boundary(base) + # 阶段2:校正步 + compute_residual(base) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = 0.5 * base.solution.un[i] + 0.5 * base.solution.u[i] + 0.5 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end + +# ---------------------- RK3Integrator ---------------------- +mutable struct RK3Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK3Integrator(cfd::Any) + RK3Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK3Integrator, dt::Float64) + base = integrator.base + # 阶段1 + compute_residual(base) + u1 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u1[i] += dt * base.solution.res[j] + end + base.solution.u .= u1 + apply_boundary(base) + # 阶段2 + compute_residual(base) + u2 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u2[i] = 0.75 * base.solution.un[i] + 0.25 * base.solution.u[i] + 0.25 * dt * base.solution.res[j] + end + base.solution.u .= u2 + apply_boundary(base) + # 阶段3 + compute_residual(base) + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = c1 * base.solution.un[i] + c2 * base.solution.u[i] + c3 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/python/boundary.py b/example/1d-linear-convection/weno3/julia/01j/python/boundary.py new file mode 100644 index 000000000..6054f92de --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/python/boundary.py @@ -0,0 +1,103 @@ +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from factories.base_factory import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/python/cfd_registry.py b/example/1d-linear-convection/weno3/julia/01j/python/cfd_registry.py new file mode 100644 index 000000000..c12eb106c --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/python/cfd_registry.py @@ -0,0 +1,62 @@ +# cfd_registry.py +""" +CFD注册系统统一导入模块 +所有其他模块从这里导入注册系统 +""" + +import sys +import os + +# 添加当前目录到路径,确保能找到registry.py +current_dir = os.path.dirname(os.path.abspath(__file__)) +if current_dir not in sys.path: + sys.path.insert(0, current_dir) + +try: + # 尝试导入注册系统 + from registry import ComponentRegistry, register_component + REGISTRY_AVAILABLE = True +except ImportError: + # 如果找不到,提供简单的空实现 + print("⚠️ 警告: 未找到 registry.py,使用最小化回退实现") + + class ComponentRegistry: + """最小化的注册系统回退实现""" + _registries = {} + + @classmethod + def register(cls, category, name, component_class): + if category not in cls._registries: + cls._registries[category] = {} + cls._registries[category][name] = component_class + + @classmethod + def get(cls, category, name): + if category not in cls._registries: + raise ValueError(f"未知类别: {category}") + if name not in cls._registries[category]: + raise ValueError(f"未找到: {category}.{name}") + return cls._registries[category][name] + + @classmethod + def create(cls, category, name, *args, **kwargs): + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) + for cat, comps in cls._registries.items()} + + def register_component(category, name=None): + """简化的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + REGISTRY_AVAILABLE = False + +# 导出统一的接口 +__all__ = ['ComponentRegistry', 'register_component', 'REGISTRY_AVAILABLE'] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/python/config.py b/example/1d-linear-convection/weno3/julia/01j/python/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/python/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/python/domain.py b/example/1d-linear-convection/weno3/julia/01j/python/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/python/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/python/eno_weno_comparison.png b/example/1d-linear-convection/weno3/julia/01j/python/eno_weno_comparison.png new file mode 100644 index 0000000000000000000000000000000000000000..8d7135ec5ad2cfcf1cd9fffa499596b9bbd8bb33 GIT binary patch literal 224091 zcmeFZ`9IX{`#-Eji$V*PMA|Khtl32&*}{zM$-WFR_O%G9h*b71`;2|xMT?SsXDo^A zW71$^(D!)hy59Ha{kiWy;QPbv@whI{L^E@~&ht2)+wls~P*bF*J4r`FLqmT{>4qi^ z&4E}NnmuFt_P{f0aqeaCP2BnBU1u!^3uiYICvzHA6XyqZ4$gMg_c>h6ov_vp_JUWg z@?R0X%wgs1`~WM?$A|gPPh4?uvgA`KJf{u+%KisRx>y>TE9a2^cDSd@xYF#Pp}BSA zy0&}#!pPoNM|=NlE#2np@`~W**y*o?I&?VV%H2cNJMWWfll?i)x@v1*epq|&I_+VN zh>QF7b$K~-dGS$Y+Bdg~>&*lPRg1n{9IP>(o~(#zeWg+<$kV<#Y{vy($mjU4uZMs5 zs;Bq<*Hg%YeX+a#_j4N>ou~fybKe4z#s2qm@Ah7Y5BA?*4;lTZ{`(ntbb5CyeB}TB zlK%gAVedmi|F1Xro5PCXzkid4=I8Z}|Njlr{9mwwhfL2YJm()QGADcF8di(+#nEzv zTq7jc`A|Q3MjpC(^rFg7u~8jkA7Lu%ljN(WpMD#diVi>6MLmvO(eQAmPoDp5zK6oL zebGO@M#Zhn_s7+GQDV<2hDCR)NwhNY>h;vw^yOIF^kkXHpQH(o(bUwmqn0@RP%buk zzo*#mk1uw4=8jguz3T*NW*&4)PqtQ*cAi1Zu3a2wPD!}7411Akvq{+LHq0J|QzyDH z%GymYPKy>>v<-C1|eX%(5>irlA`C}};_p5*Z6ilNOG zl_Z1x`R#h-C3SYHp|f8%*8Md%>a44AZ}FeEH=nh<;nigOqP9y@?WhY$ zGwqvf60SnLdL{hudu1eb`lw+J{v&ni-4@}|avd{O1yY}08! zp)nsy!rCTftTl{HqAMZu!Fc1Um+NfTM+0oD@cyI2JHp+W3aG0DhgQm%pTkAfSTl)t zX_+@dj%3`5yg1d&lV8FYNT%UnuU#FowSdc+O(NBh)wD`&dZX>N-ga(z&8A1Se2zGW z*?V4;PMJsB`_CqYK$Vk>z2^AV1OD8~SHE$8l}LzM^jX{>w~MlgJM+V568`w?IHRW& zcBCg13=E-cDzAF6$b|U(iP^o)uYC0-e!;tMPb+n2sA`Rk zVcVrv9lo9tbGl&{x4l|V%IvrzejmTH{ovv5vIpaG5+#*eoyHALsDT3t7JFN}Q*B-y zH*}7e?aPg;T%W^ZIHX3inkV!fYR!#2elY7d@EeF>r%F&esjEcWHnHjWC-&zKJ%=mP zZR9Y@lxPE=`@_)A(wo{}+6?wGoZ=7ILoXm7bnt4m8;QY7Z0*;N;xm$^cOHvIaK@Xo zNc*f0U{B~i!`b5nN-v$tReNsRpC@eI{OXpQQ37MT6eYgB#EfdGfEryKtFLF_zxU85 zkN!;}W8LrX2eiuUbdNG>KQd%H*b~EjD#>>}yK+2;wQJ+K_{+H%W0Q?uyiRas`lq0k zV0u;yoKAk?gX>u9{=5VhQG0_q>iGNMxW$ob#s4hK!X4wk$4EAw`tvrr({6Zu08 zK`j%d$`x`4%ZHtwWoxAPd#nyxcIO_8j$xXVp)M4+zGsw)PYJJ#zvkG8z9B;y#X1kuGgDjqbjmWx$Kw6UIQr$DhsTY*J0(|ow07{2k3o$ou^$jDA7Bcs(iU-Ykht-aT2{!?Yp@?_44+Wt5EVp z)0JNru}Q3me(XxW-CF-=%XVvv>L9_JT9P+ZY8wY{ZeVV`DidD0-)^jKWK`X6?ZS1(ravg$qfwOeo0 zeS#iKs-?ug+Y<^9*lLQSDqMZ3Eel_#7_T}npW#X|fr`Fcz z^96m%VPT?N&CX=nk5mmZ=*%V@nERQ1s6wu5F6|KRQz$FJk}Wsied+rSnM^hPb;3=* zbM87}{q1n^ign>c#*!7kJLD==!z*e9OufY_5DvHb?nYLRqj;&GXL}hRdQMEYSkLw4 z+Ip@oSj(TMdw{p8;AUW86YpWVVsI^QHIjP=8jo%@a2s(c6G)Mpri&A`?Q0+++ zm#2>j@adQ3tWCU-vCBMGDm)w5a`Vi4Sd5{tx}Q{1Jt0h)<`5slscZKn{&-Yxt`bbk zq(W}&n1;U^b65{xl(v$P6_=l4_q@_yoX4H$)h+ll$#2VAp!?U4udSH!ktwoDo~143 znH;IX`I<(lD~kCKv3#thwBLwzdF*nOv2SIJgj*iH9?ydWBPB`WI`643my$|ST&KtO zLGNr?4tGD#X+YYfOwRSvc|yhX5G$ZvBcs&eM-v&PZvlj+ylUbA~l9%eZBu;S-Z z(`oASw$$lDxJDnGtDOwU|K3X^jQkC-?c>@^#w!)SElL5((v06)ksTRNH)i ze$x04o^NkmtUQ*`j_k4eWRF=v=#!+B2Fh_2{qY*Gp}`wSbA6H zj83Y(8;(;jcS6gKmD)z-G9_8Xr)+U)WenWO-oA8C+{8?4|H0}{YHst@ZlChH8eW5n z4s^)^)IDvt>OS(?bP`pS1dT0QC*SBse|mGnxVY;=bc`4_W3a+8HTRt_047!oE)?!n zwqbQywoz@>mxHV#cKitLn(xaM3}#>}GU#88Z#R{Iz0RWHU0+YafV>2}W>tixV!MGEs4I!lkLH(f67 z6%VB&rihIPom0?TD~cI1qg3V~cYWo8O;J%NHqOadMHWu|iipeogMTVZlk`$+DEah5>U2({0tye5i7nS8$=0j+LB_ z0)XgB8pS9!b~xtUNeyBQ*|X$4GgV8DHvcOOOa2uKXhQG>H2tg-GbXK}fzH;5~{T@%`~y zN9AlsDZY8i*l)A6eHprI+<_|<@6NwGz*o~zwttk(QD$xOD8YJrqdcs5v-59pbVlK{ zxo-a9`AB8n4~T)HZ=WRXbGB$fD^o4KCs)7UVGa7Pt8&T8T<=$hTKHGh;f$LpOGfjX z$SF^^C1}wxaegaL7|jQen<2T}AW*73=0}E%Ax&b)6Ufp`A#QNQe<<~d7>5dbh zxav24-PV|gzxbo+G}SU&{QQbxRhn53RItTz%z@)q4tj0@3{77q5=7IX<*rK5uGhgH zR<|A~Flh#25aqNmQ20veZmA8bxt20&+g@K1R%*0Ho>sRWtMh3NKJ6Rr^zGe_dh^*K z6$h5t+W2W%Jxl{^y~g#nF>kVJ^MxpbmoG1=CkmCPyAp?1C5R){qB$#Ry{@x*r$y}U zDkq4$?97u<3_HaIc*C(7TBeqOJ{*9}vP={n&9!uvJ!~BCoR*RI(#KbH%-p*T{Fc6d z)JpVO9ef21p4OlVnjA&~*S<$j4Oqv5a55Fmc~LEXru?!hxo*pAdB!B4C_A4A*h3lW zjA8!TJ#^LaxLFslsNT@5L^x)Rw1M_A zZ9JOSg0Q{EWDC5T)c=-R**7W8G4M7E#+*?s2{L}Z+B{9ZE4@0t-K2^M2K@$+H`<#8 z2+2TTmdxgIWNuOH=(MpHQCKTiPgp!0~37Gp0{?-x%1p+IvSiw zqa*0uYR_;zWe#Jb`A)@dKR!I*E)jo`iF=Rx?&XQFL%3i!fghpt3$;)#f1)UknVWcA zinvajBw0n!x+|@qT%S1-xja$iK7EHl+)#t|3w8IL54$6qdMV07# zJnES`<7ku+%EV))BjqrG0d+;E)G0JRd9K$w;WQw^YB8W=D)44iE$*0m<@V1mR+B^u zkMT7<`10~X*-kP7_$fLZxKktf#xe+t)fXe&A};7!CYTfM_=to9xgMA=y&ZL>Y~Ahr z!PC;i=$CzdE||SNhWY*C36)P?zbgp(sLgq#8udY>?r zy5ORH`wiU&sXi7Rf`NV6)E?+E0d+ca6@KfpnNz>M@`-{Hu+P)SMgsP(u0FF5>_7|R zQ0XlC?rCA0S4eMGX$oUAZ0~(Q&m!<4(Q;_3q)jk%D&>yF^5DHi#Rw6LmJ>-7n^e>Y z`z)4qQ;y1~4Q#5gJ#tj2Szd2i_5AZwVghf^VLtd1q`RK%2vH)27vHZ_FxxIY9EtzJ z+s};0*GO6DI;^=vg^igNAjR$Qp|%*U{xX~lKRAV(o&f+{C<78)C!DW z&M1H9let6ub^`?kaxduWxPM0(U6aWrs-LglTF2AZw8aYZ|3P4J9=G`y^mATHd>AT+x+pI@)?K8U^*gaH)= zuU+m?v&*efs8ouO!boE`U{(0S4CwO-`eB znz`^uT}1LALs4BI8rE| ztlweKv$PMn7!Tdw2=@Kuax@QTzP#p%vhI8vxCUEx!ynW6)+R%3IvyLwh0ci%VMa}( zvYK=KE4kltmt=59BFyR0Wz=Cey!#f^@F7{`fr)(83j#@p?rpBGU}UvW_0O2N z?#stOqb^^Ace=TA6cBA?LOl@h9sQlzT9*iw@2a0x?o48o{(bK*VIfB#PSn8&gpmF; zu@{YgLt~&vEKw7<`FPB<%Iyuk)FxYEx`WJ^^IXuzI!Z>pA5qzcD64=Jm$`VIX_`xH z#<}`C3ZVnX?4+d|@X2|XQpPOsb^Q(wLs z8b&Iy?zY>z$MTw1FR3(_4yPEY&zg=^n7*I$;p^3&m1L0*TCepf;pkkwL7hsc9}8Jb zbj>&)ZHXTT#qg#f+>(C)nN2~#QKhdve#KxQku}j)osjE_?2J{sLN2uz-he5-Dp)@C zhP#_yhhy*RydVw>#DL_*dQ!*5IW$9~#A;h)^D-!P zD1E`C;cL@#us1^u7rp z&cyk?B17p1|I3^UrWPL>Ni*8#z-YeE(umQXi9taqPT@(yr(cDc7rXeCfhJaJ6rSaSFFSsNO#9vA4o}6 zoxVnxJ90sJpsYh46DfXvjzxuy-`RIgVh&4(o!3JMypQkGkDqUNUO~q#q}lQs*icst za?~78mdX=fihrDw>|Fu$w2NBXJKmZ5-dI{V!J#!^3ZO7rX-I22F)$_N}uu0p5D&k?UYSLW6*T7~yN zYD#Y|cRwUUP31}VqAdE7XIDP4Iy`n6ootO0U70>9Y?Fpyq#e^EWf*;BxCEPhh5)yn z`>Edo7}r}V?J|GT9^1ef;sOLj(QR|-H;h{JM%eGzx@|A+wSe!&?^giTXyZv8De_#q z_A;m-hqt}v=Cfm}g9Rq?Rr4c-ror>2dIk3yUO_=Q^8;$Ab(`P!4v$#Y;4pH9uS>BafZqa;{(mYffgC%;6llYw=`7s0>vkQ{d6>e&mH zZSm6N`6}It2Py58N~nyQfW_GgeChyUY?jFrP5FoOe5B@756Qq~9ms z6-_wyd}EDF5FsIi9CMeyHL z`f1&x;H?LCUgEM-d>HZjT8&jo_t7Iw?NadrRw$0EVcD9K^Yj}0` z=F{J;{S}U;{hQy?HV@56c5f;ZTz>yF>GyDHXO(m>7^~IJ2c;>~EJ%^v^#4S)Ie@uVZeHRJq9ODO5}c=aJ!Xj3n-j=&5qCOO`-;o9?FhD}5{VM<4DYnvLyeZ{Y3MTmSTEsaFPF6}2Be(dUt>ld2G^>AE-^v$#Pg z-Unj41HvEE#KvGbH>WBoqh4BaE6_=)yo%QsO1w8$`hCd;u7l=WiZ9sdJ!>aEg^~}% z@jQ=BL@1BL2+;AWk<%&4eBQ_Su48F0kH|e}*iB}BSObFS#uT4!k(tJ$LD_~J5Ugl> z>t;@J8djCG*U*{f>6c%*py$kP@%2rsLmagA_E&d9noI|B^~;lYR?NK{k1vN)o-M=3 zB5*#7^zB$xq6kK>id6<;%A(| z*Y;z?m4M;ue9LgDZ3UQGo3!zq5nG#dx77SVp($642|xiZAbu1=5Ns}#^6l3$|u zj1huzqLB2X?~F5fs`aqG^mdhrc(Z02|ChsaXaQ+xc3B7Y>s(zPqF3@r z5(ry5u6)dw;IRJW@HrLOF9CX=9`4wsQte9g&R~=B>^TT)mxJNfOnb5h)`2S2psr23 zL`jbp&;=$%eTFL>t=rr{RE`UxXI)pJet*W-QfS&FH-si$asVqvC|9qP?ze}z$NI|r zv|z4Lt>;vnL*3O%>@jsVU%lqVUMGiVQ@sbx~(}Yp~9zddx|F&>_iZHD-h|+bR4_vMp0B zK>!i5JBzHlFM1_@y3NY`qNxDtsRgjVi%>geeOFV0*#+P#ti()JsyWmwDPRHQ(yp+r zQ%EFHAE;3m4Pe>JiFW+cCcQXR>vWReqUdIV+h`#rd>K*vaxmciRM)=H&eAyJG6eEv zE2vY}jp?QtYhPdUSPHxcSsatPzpV}HEhCE4^isw5r#uLt574Vy3<0E7 zH@1vGgLp}guE!G~O^6_LQ6tF*_>d|%5Asz}ip`+d`HboS#n$SJV_q04{pz3xig%+? zBB2JPIHAt(t15XI4XUK)vQ>Rqh4XCJR8Dc5092_`%&R!SLhp>`tBe&1x#gpC<$j3Y zB_WmCV>IX0m!sP~O6jtxMic~C9`nUoiqX;i_Wj9Z@$J6Fk@&AK`}sSQoolhfSlyHS z+h3gzB<>yxvVx*R+K>}LET1SNPiv*GciyRvt2@x;&>od@t^ziJxhL_e!v&a%PsS#> zU1qkeKUI-2i9$EG0SNedzFYGvE{h+{ucf;|GiK*^KX3Z!`PFB~cz?x+xc|Ht$)j`S zC^zk05ND#-x4?q3-lNX^8=L4wcQyE^fI8D@-`^=Ce53aDo30?yO+RKT@dS-+Bcs}- zV_(p!2WJDpHm~v%X^3uWgP|X&s$pgmF7#=?sXLr++7(@wpxK`6$T4TOZn(74GcFMN$gy;nvdaLLIWr zA0*!)j(-363=zH%A)Hv8M_7Ano|O=K5d#b`b))eo=m({gL=EdiI6XsN6F>?U3VSIA zMP?!N9{7oSQJj1y0AKO&>5btc%`M*^}Rt}4fTghuX zZvE&Di~Yy`=T7+Nr&IzBFQL@!2xYmsVMMlJ^dZe1Uo;-;1>Skox`+N`G$=>HE<+$g zb|sMD?Fu7IUHu7)xf!azoAG>__-Yy-S{mILLk@?_qgcB|$T*4`xZeC-{ z%NbI*Id=kwRV=q3k<%;pZ*7~KrJD`xO8HcLiWDKzD8U`!d0-dyQn=836Z-p$fA&+LQ>aFQ@5MhM`ZS(i76Icace-x{$7?q|Zsh%|wRR;h3Q<~3xG z9k;Ubq76RR%3|?!s>=T=W=N=>!wxP(quv|+-(8g(_jb;Y@177!DhHEo5A(O$g`%(C z8C)u9Hm_&zxTunBq7pf5p^bFKJ9MSpk|qI;^o~5-w$hp)(Z7PF>$e9}itq6W{Lcp# z*FdsggX-*)(F-*tDD?e!4(?Rj+6a&uXZ(AGxs5hDjCKwCdksa7EC9k|C5Wo{7zZkn zG%2Cs`@QV@F4y{fK^{|0kqZO z+7D4pe1c*UdR$hc)ou=0K%!6bnuwAN<+~p4EN-*;oMLq8h}tD?Oa0@-)hit{!VO_} zX^t*Y0|_;c7H>y{_Z2bY*kydZs}dr5c33aZbY_4(;rrEh>Em(LwAOgBZuFmisVV)PPPf2R? z`xnydR#mURBs@Bfa!`H7EuCjr-N!eO$6;O(pYj`m1r@&b>&vsg-JnET6bYajdc_ub z&-DuLN*GJgUTa+-OLSqhIbYWo&t1&{s`z;xweRZ5ulp&I9s3TAYq-ook&i(@o0>ip z1ETxHF*@gUBs{?CP-6m6VOoz?>@J=4>1z+(g*6{~3d}Ib$L~NC_wIT)meM9@+Ua_5 zvAzCOZv6V%3Ia*O_)5iy?fY7E4~qg-8P!!Q!IS-~SnwhH>}p%?t{Wu*Q{sMt6Ki~U zr+N3th{KPDr|2^S&;?y{*Oq}S$Gp67dr#@9(np|ig%QIih!6!30^i-SmhrLacw=B0 zHjF%)WxEPkhhGe9u_$AU$7YE$SPShWg;m>-P$4qImImdv9cu~O)#}M5?SGuk&)Fcp!YuWJ{%b3 z?InlLDWpk^{TzSqGs+|D40Jd{tQ{6+GXpbH)!Py$%C`_T<;Jch+SY#Sd}HWR3vu}C z=QTq-Iybp5f@RuGL9S}t-t_WNmb+xo$^B=JT>qqW`O+|M0I}4+C`pi10X?jya4X=~ z^M>?Mo1q`pSk->t?F|u(lY0ZrA!*FnMSQM1^96k{ZddT)Y1|D-(8*b67+#Z4a7NFg z*(8T;Z|>-lZC8%4BMs-hwkmV`o_F~GO z|BtTreORsn3Hq?oNgrW~=hVtj(I6bc*ryZ%zj<6M8Q79A?rnME;uGCk3EM}Q7_^V?C`6{5JbqZ zed8`Ad3Pad^XjR7X|3Rk)1O%yjCfeYJ}tTC-iXuxF)2&Y{LN!jQ!yqKdhO7aa*vU| z!(5NvwME)lx1u{Yx9%xC*xv`}-Cs{J?G>+npNl8x z1Ml?`3Enni0{4Luh?vy@cutzNIlTo^DpmA$4p%g(1gw>BFMpohD)E~*e1`tO# zsGB1WLtq22_MVx#1Px7)H;h+tAe?H;)^dy`O5^ z%oDlsCv-2}qj}x#YSRgbNxcWmE_`AhN8D~4X@UPlD`k&m%w^ll4?$$$vduimLFpb) zdb~4`PK&O&qF0jj<+Axxx_?Q`*PmQ#*fhXK{3C+p5stZm-X+A!kq z1C*N=%mvbYJvS9##?=GGm9$CsbxouWw>y-39R0DukQurTw9Y4Eb{*;4xoz>f*Md4z zn#UvJMKykJD~7C_*V$b1fUAV}v8bnvDQVo*q?~G3I)RwA?Psb!eF|f<1yN^Qv>m_> z2C*}E682(q%_dJ=()Y?Fr5~zw`?&gCdorv9DoYGp-jB|kv!VVe9KXTg)68U(@g3gt zX+)t(=>^3}5()XeO_vL#E8F$Fm~2>)dQwb+(^G+KUodwxkJ;kFbIm$$5B)Ea@tdr~ zf=bHR4wl*Tldkk=AVdp4wyV`4Op06i-vFs>119C$9nZ|2 znhx~cw;Ra?78OC$T33alUyBGa)*(*5!OFy>V$491`Lo?ahkm$9sA3NF~v{N&_d65U%^znPdu-U3#8ugvBi zU16(5^i_#O|EUlGItBhzTH1;H`mJAHZAdCCK16MTw!-)`n>-~gd}`tUZv{YGB7 zW6Pl7wDV=Lse%tGinOfu642WF(Yy>zIl9IEbf-U_qy2;8?E_h9bLyywR%tgVZ*9|p zAVCSjYSP=V0aYd+nB&r5Ufg2%=rNrz4HkTf^+g^$ION9Dk3fG6uC2KtT`!L+ynmB$ z=F==N>MuRSZzmLj{aeO6Hg8e(IansRys3?<0O2v9oeDUSBWERu^{%S6=bklin_5Ar z*zldM+3V3h!2~N6UR8Wf6U(i`Ff%iWpno%kIuGH(v+qu))0t|8mwgzt%#a8?<;5c?&e_?b>3i z3z-fTx3#4l%l1Uy6eJgSeq|HU;~|F!V0XcZ_Sc@Lk2QU_2#gwJpVbZ|E9u=k^vA`q7O)c zG1LN$;PlE0=s1IU^B&+Wwl)SHI9GLrR%@y)fz|n%((7LN^yU|B4?RPUTrj}re8NM3 zWDOO9lyEIS_N(_Fk5T4_E3r!RGhOM=#!4RT+8f*QV9-2jEpRwhH>C;|!neDmt%WI@ z>;=MuZ+|-XH}UsW-4K5_kkKh|;LMcAfk<}4*$@XZ6xpJG%<(G-BHJ;Jt^^@Zuq>=rn7W|jW?c>Cd;dSea%j_UZ~ zCxh$65KU5W8@oI=SpTy@N~@Of^T=m4W}#wiRop0MZOpI?I`vB zID`tF*~FB3#{-snb&S_W;vi^ZjWi&E0D_bu;WVAgyy3E05r@pdp(BdU zJ>63K4Mn~ZnbM}dkGkW6|PJNK0&-=b1P$?o9^GsaqK^r<6$F4X6tt< zVuX53#aOn$r}6Pk;?)-+gLt-%GJAdz#knyVslL%zkCVj_5g+Hh(JEX)Q~Y6>)d5rM zAaABut_S*up(9y9K;(MvJn$}IP|B8KG9Jtsx{a70X>Ao9TX2&MVm!U5(>l!iiv4-K z7&dk~(W}j&DlAg}Li;_?;*CXe;*QY{CnVkJ{S0O2G3w?R3%8SI0S*%5(%r~mjf`RH z=&D>$9Z%=UuMxLwXbL}bD=A%1sdqvjl!&eG9w7q0iD+%U7PiX zT`m>34m9zLYkN>|_`F{SZtkh-8&UYCQ6QAtCUV^1!6V}Vk%7asQ!O#3JS-9tk1rfS z75aj}*@_5bvBvF}f%ubS(D_ES50mQQeQ$&{A(8Xo;(Jz;M=kFfF4q6?t*72cr!oY! zam+*h;5~ccC$3_WPvL`!P|+c^{i95${N}PV{cTL0x5Q^Xq+YcNdcL9fBG`Lo&7pP$ zT`yaacX|_ZXkkShq{V*A$z@2iH5oX4x!`-93St_;%N%-$N(n+=EFg_&(u!ZzO>i@Y z^mzSPHW3?Ebm;PQX=Xi__ADE6i66Qv{M!t6aBU=O5D@W~-TUWt>?an>=&x6*ThN)V znh%x7%OCi^XGu5tf#h#kpDbn2P13y=fnex!R?#b2Ps7)r?;L{{76?5lTr@s$Pjchu z`zc5qE*&_7dpQZXlx};ipQczd$JG)uJRFJ@_8I4q~y!O$%W2S-0ggzz`Id znPIKxijWK*t7l?n8TcJxQXuWMn!Q2lCkW3#<{&w?**26Y3r>g`Ubq^i?F(VxO>Q4c z6x~=QEp4e^kAm~UKxdh7gEmgu`*%TYB9p8ue)#0RCjKJH>AaoKaccPytUEh571@${ z)ZDdRo4ADEx(zCwf&AC1X%R1`JPFQ^RsNL9#VwSIB>w9oP#o9a0B zz;5O+xA}v~^_9!zH!I@XE%emuyw@6y*tZKp%o)p!oWD*mM_cw>Avb34_f-i?_rcso z(`zPXfsKlxCx4u5)4V6Y!-^K?zh4@#b*DG4-d8C|h$SD=AecoVtWRq6d62=5m6OK4D@GMB0H!~&8;_l~1$t{5E zn?jGpzfH&G&efm~-R^Bd6&jtX(7xZ zagwvz>^izs-=$~Yh4Z68V8{?iSXc1^T1UhK(GgKfClBaD1W>i!cT*+JimmDs^KZ?c z-SYjRjko879>DlXEbMtgatwEZI#~Sn<=k}=T>P0do&tzdDMl>mZs}H+A-c6?v-RJm z>W(|2uIsR-N6plrGEXNIcW0`rmkS24hMAbHKxm!)b|752{yakhyM!Ti?o|pkockbR zA+A=4&HAQCuy@VdrToaw?Od5mvDN4g7yrWPolCyCLvF;Or!1ShfI_}8+m(*FcePf- zOS0RyPw<%$?poeAD(@kPjahv#8U%!NjxzE4A&Zu&Y z{rs7eZ@g=(f5{hWzfZ>epwbz;5$Z}yve(N+j7_n#VYOktXGTY{3qKppiV87p{>rPT z3y}3 z^xGSJiBoKg@#OX&WyO(z2N)B_yL@9IeiK{o7K95(PcAlMN%{5xOm@rnwo4ipNMH$; znaBh_fGEZk#f$bG^LjTA6RywqD{pxk>$*<#jn_ElBW`;_kf3cqLj&TQ+3~mPrbtC_ zDy2OrFRsdP`>cujA}BY$wKUO0c=w~gaYv?2>lH<&SCDdi@Q+54jsvlh?3v>3YB&U zp?DZy4;iB>(Mi(eHZ4dUMB z-C^o_+mWv@3z5l3U$~h#b==2z-z6PGv92;)+57fq(NGnFo1Q~Ld)jSIC~YD|$Fpa@ z2Afh(!)4v9i4Wi}neY0@vOo42PLk&xVkWGq=-kywxHg=ga<3(|_5@iAMc#`Cq_OH@*#2^MztH`N#E*jF>$k79Yl$oNdZ6+( zW*zoR?ag}YQQfl=1e<~2^e#`!?)r&0oAaPcdI-wuw zpeXkex3x2xC#92{I)6vE9_~(+eI|DXGzlS?x+`LS-~~pkk7qe5nC( z{>O|6THc{fn%x_y%;8f(% z55P1=f0_JE&+!XH(}aFdQ-2-cxX45M0b~x3V#f&uLGb(~=;R7^+)cnHNVt_vCqS_F zXv=J75_{LWSWrYNHC0@6#C;;vde6&Vk*v413L^V6&%Mk`1?3=a$F4mCH1?j%j=d@J zg!DVoM>h33J*1&YRsUgnOTRjp3XtFR!)sW{0n2EOfHx;w)BOHyS%7S}Dt!ksbI#?c zge<18KI}{Jedo`)+~{)rrD1OF@_Aoyn|WZy%EsEeU5o>knx$?wd@kNa0#XN7fysm^ zBqtZN<(D)Lw#EtAwk>(=|$4wgG4L9*%z{Ue^?w{t^uzd*2Q-Q#+87UCgg0;zHz3FQu=ps$uRxCO4E zO8MWQE*0Y-?lDl%1*%*NMEzSq?zgkLy~CN!0rf?DuYZZZ{36{9G6f(%`Mu%k9uTybWQr&vP4{6bYZw=>D3uEQny9kTAj2P_`H zx~o~8eyGHs>}k{?jOVXRJl>b}m^+_3Gg6gno ziu{^c25g~{J%i{o?%UVnKVwbsDBL_?ie`&XJEx5q(xkUWE+l#X3d!AIo<@G-5cj|A6&}dG z%mTf!az3xx8yCxXT3RxLu-i;n!gYb4uXb5;jtUGR4wS-+@HFzfve%q~gtuqBkw-KS zANSPz`DxX-J4U{Id4DWP+=aU@Znqg1UMIhdxkc(q)y!F~WZ6g2_D8pWeEfe-{GX2i z{U;8lZ9jMlKD>+vn0a ze1FNp{D0|<37*743@jFq@mh_-^Aip1FRo5sorOc5Bf5QYX=ofT{y$@~533;{9z%QJ zSTqv!Phw;fH*3KJjQ8gPB7XH8k z$3@_L3B#+3Aj)gPp7TY>2tG)_aZqbwK-h}`sX7+;avU;w^oq}TR2vMb9*C>XO;_a! z(_&Y~kWym12%gSrZ1-)#oqQvyNIv7bEc?AXyO+SJ2-F2XP)cWYVbHXlxikcVRia1| z9MAxF^`XC)MONDl%ZL0+tix;$gT767#((aU!iS zjMa2mx99^>#zxkd+%b^!9mxdHpd8(?1}C!xCM(7TbKOXzQy}It+Ik=pEJ((2GXt4> zSUnLqk?6H}72N72{dORt#C4B^0A#+c6B+MfO{Sj&;wpp;rO%V>t7b2f;n)V(E`pZW zG7Rlax*OCQdt_8r;67y$W-;4kL}As11T9+lVOZienLIbs2`h|b*Q0szDjFw&1j*0q zD0zT%xmpz`Z2Jbuz7jhk*bPl#3`_(`^H#zPKhgD!CnP4#LEq;?;-4`gM=!+!Gn5!9 zD+IEb0k=a~)yp-mmk!?h*ivR=<@^2?z>B)tP;R0C?gpx$FsgSM1g% zl~7>qc?LUJ<59c@dF1Um?N||v%`QAkO2Il3x(WCVSuqEVGgvVY50LuS3X?__ZBWrz zKdbDvPq7LtZA_=Z#VPR#YD7yUa&hL-Z7#|6Huq-BrtZqZzJq<&O&{|WBtyG*YS+mS zq8Vhk)L?*rGz`QP5kjY6yuE-;(tm%97q^W%IF-YWO>C|b0!O|p-kXz6BTs-oD2nJ* z#F{khh3GOzNL{XOXvAfeEy2>6>g>9ulKR2w@ky5t+Fto;rgOz{vHp$IGX)3tN6$=?9f(FiRFymG=8E>4mu*scrmLAJf|e<^xIKo z)3@JtRuXgAPxH`OA$+#xXX62jKU7fBzOZa?v+IAG>N&@H=IGpZLHW`tnixeko#lgfVH$#}a^kk9?*iOHf$i#tTa*)_TM^JJqV4_; zbUX=-zpBUg_d37qvCd4ez>_Sr8~kYnYo3ErG!zcx_onJ)&S-DwMA2@e9!)D0cwtd! zb#?R@{E&IJX1blQIjtUQ2@x8~xQnqd$UsZ3!WN`Hq9ICPX2l9g83+8*Tr>)Vs-Pm5 zgUlaBA$L9!2DLv1q^z};%z}BCT(*E|a0g^2=4d|jH4{oc4(oXe97Xg=B;FvW_hvP4 zHG~z#g_;h6O8B@$rCfQ2PB<6`In;|spkYO&thuOhSKt{A7v|*TDd#>CH?`7!1Qy+( zY5Kg{bb#C^0ImWsN93BToXfChuH+~5N(*;mWUc~6a66FlXXFO3vY41qW(uKU3y3(n zp2o=sNBTcPnla?>T02BID#}v9FV6t!d)43$HOCq;PZNB5GOkHn&3cLKu~x``+P}E^ zFo&zd=UsQ$eByVJBSDF?%L{2`H&PZ`GFEJt1IK)+tt2-jM2A?y0zQB5i6Oi2Z;zMF z(5z_Vm1I2tH>)T!Awv+8>*74jR1ge#bigHu9u;0c~Fy{2If?Oxr1X6u=O_?ok!}uY$`k+>Z@dW=nT17?@0j>4I$A-k;#sH4VeS zOCCyaS7#M6B#VW6V(eiSpgiVdwoABb8kFm~c#vEV9pqwSC2jfp*@NTpiY8r9(}t5h zB$4@P7BMHYjC`F(b|?Y2pcd4M@$2SZ>vOr`m6oq|pkC=5B0)NM^}u7Eto}-@Wib@; zWqm z{JT{098CHbqoxv?oFQzwI=YKP%zE-EoW)^R6(`$#_4sQhaMOkaYeH&-lTg|tv#4%xaEcijLB_g^uydQx@h_eXbV zmCsFD50+u#U>+c3Od6TAzI7fZ-~)|2&{T!%RV{B#v%md8gwebNL0ai=dVt&rxWHgSQ1x{R z=tG+-gdiw1Yp52q>;@8+12=)rs}dK!1&3};^$#d*EftOvDi)j&fkN&k(=KG_K&N44Dj}2s5j-277skoO=5c>2A;ZfY_>9qxn$3SH3oTrw2?n z1;mc&<|4_B>_01uAClV$PR}O8<~2OoYS~*{7)|IGhx*o9SN#4{8XB^xv=6V7rFg zlc^X|P{_Rp2>Y+6a&Ri?!DRK5)7+()XUF+OV7jo3aw_y-3WEr=Ct+GTCjO8cg#3wA z(p&0l1&L0_ZXewQb2XSjRvBbqlMnSQ0to~QW98Hq%?uS5Y`3Z?tzm*5dH&P@(0nfK ztZ>X}<`=*UMcZ z27oQ;UqY%U!tp ze@^l&a_33jDHREom~_V^C8ZS$m6UD~nIJVmO8Sf&muv6yoc%oSxvuy9_^uCYyIFwnk8zLj ztC0c6fr?)F?lC2Nh9}fB8KYHhGYb_44+vW6DHY?@)etq)2%{)YQ1QfD)J|%+hn$IDheK%@moYa6#uD1_ zv2l1!$$CZN_X*Sf!eTxAJyN--3o49KS+aS|@|6z0R+j71fn)*Ieiz5*Yry+A0qAnr z&WH-D*1ejoq=C6_>L)YW>NyQ%5p$F8*0??p3r##N!iFfgN8Xh&6reN<^0zP{h9ANSyymKt?t_LREAlge5Rmm{_PU;> z8&SQ+&9SabP#=&X97G&dG+>V$&9%6ZR?-E5IZP zjK@1CP%;Esg5jC~RfiAhm|ulen0?<(8p6nHW&javX5YeVuLF9c2+W_=1V0>XY$O|~ zEtryL4U~SgyA2%V9zQ?&oLDHyx>G*O1H2^WDECU-9{8H##2V-pzwGx7oP_YIlJzkd z_4pCF#Bo%K_56|!%g6^UYikM%vJ?(FV zh2*RB-}EiXwQP8G3E*H_-sf?ZCs+h|Xy%^PH{m(xuYqQT2XzU0-X+<0N_h&MKc0^I zg#{9@Pt-J`9ieQkSU02!gWhpWEw7VC`vT;iT$#!(_7G%bI~uLf*(f6aobC1={1EMT z&Gbz(m83SV>TG3`w849yxj8syATbwC&Aq2{T2_tU`m9tDLsMiw#vGkumJ%`W%zO(@+XT~^(8WHJUAuzlcW+=wWke}XpF28bQt{eURgifhp z7XaQ|^~XfIr6YiCd&UYJ%Bq;soeIiNE{~ zVVQp2Cua_lf7LV-qxS1oL}cpSrtFjg9+GTBqQ;EyA$7-F5{jk<>gzm1Le8gX99`@} z7LzOplg|%PIkqZSr9C2~80IBe!?ZrrK#mX24c|?BY!hJzpoa1wcmp-rlpdvT@nG^b zSxn~@OE7DJ)J3N}zs3|D@m`rA7wVs~+4wb|eOqJD60(TQzuoWC@O1Y78V^RKW9vdK z&MHED5MHE1G0=NoJ|3@L0o3v^m8DPzzwX_4bfO<*=(*pcRm^LG0yFuc`$?S8kxo8T9O!{43u zab&L0lYWjT4!aXY5?fZLdwJ4~$@*%gpNV;U!5e)%aq2quE_CfP_DnJhG4@sbCnb_) zNIec5yZn{MvpMbxHD%0kL5Bti!Geynx0wl3<)(I%PesuA)IP@yyDxGFDw*q(n;8;( ziuyEbP`KTus5A5hu?SWJVau0`0 zpTA%}9D4Lpy-l67&JR}&JE73_6p|WKF^f~-aNh@aW(p?jj*-_|ABFTA!I@Ip?jsl` z?JKAP(O<2$m4f^zl95o-uJpLUZRd`OcRd&R6T?9A_6st0LXzahZuJCwVt9((v)$e} zI=4JPb%$3DnPLYG*rGG!3x*2dm~7Vw&?r^*UmBDoAkhIDI2BTqk0M8*Xgh85K)LO$ zm`-&-1>$U4g`dw=;#&kmpK+9M?8ng!KtV$u8qxOJRRSYSchf-ypJzNmyZE9++?{k@ z3q)JXwSc(fn{(5T4fQa!h!DO*^}yI}ND?p>_&bIDrJ+5o^UnKXhQY;m`$9VnHxcZ4 z);GRj#_@tHeiSqtef)~{WD$c%72(Bt?m4^K`!aW%6ZQwNV;W(4$%Y6!dRPj719vH= zI#4D`dCa*ytRgU9=*htplH&fm`^T$X^9NzbK>nA%_TwIN_`0{(YM!OBo&27%8Lx5n z%wWNex#dPn!zG-kXC+EN^vp0{hpFEzzj6`}XI-1wrAeQQj+5#-)`RQxKj1;9au=K2 z=<-sL^%YOOV&(=GS%qqqUqg9cUiNvEyUVjJ2B*l+!%xY?8Q=XkG$ii!$1UWekbW^+ z@)`XSyy5+Y_A>0Ra^%xNtrU5qRTF!KRqH->gdZE=$N?!056S$q)d$sv-}QrLUsAsd zyb37m!*{DXOR#WeJivG zaw231-!P54E0clc34P1Af)GBKn?>JzR%umE7QkvPx0cR>%_&VoEQGck^P;Viu$xOL z&e8@t@DqAS2Vaa$a}3^#`)~j|r8(EBb+rOO4*pXK)S%_T*YNlqgA_v21@Py6KI?-o ztPu1{L%BpCzZ?Q^F1<>hG-Rn4MH~WZ9eq=k&(@z1-=AsG{5tq&a@bF3I%XUzK+m5T zE?J`Mn2U+v2%GX6WqL^!(0{3|1P&oGzHMY!N_?kIDSWK5Zfawm4Zz+!n|vaTyy<1^ zw-qX4&a>d<0xsGvgCvUpi>9L2jDL%rBPTe^bPe@3rFs&7rupfuREP^`xC4n|8 zm==3QgY6y=ZrSbXblza9M$xF$I-&iJM4H&Lryf0?eadAHPkd!oB zB;u0bpWxKM7=s?-4y+It_OKmi*K>S`i8j^=V>aFb zVjE;!&~RY6G_wZ!!L(`ch?f1(&mx4u3Hb)E)Z7IJ!U^&wNAE_n4^J9%^=n~zDK`3e zsQ<+z{P=|*Itp< zcdY4(*rq{_?LeqJE%M!Ql71-36{5$=^30|Wb4XidX&0`@(GSH(b8EYBNa>UsVR`R8du}u#` z_gh#SSpR7V`KF_eVEo&GqB#0<4X&+D;tEXeyfEaHQUsP&KvyF%%(&-T0f+awiK>bk z-fw;hS9D6uZXF=6wAc!P_gL zI&1g>xXoETWsHxdupp0w9vVog~vR zFA|IU4#00l!C^KV$IPaX*@|1XbnAakr)&3`)C+3{N69Ue@fT}UM?ma$qNUOWM=wM~ z)5GBCx-tL3vo8moq@a6$#S7LN;>N==xWHW+{!~k29e`JdMI}3aV0A2kS;&1O1I)ln z`R!8aZ6xYVqUy~mv;%#7wnMB`L3lzr4A$S52o2$H9A*c4-#)^>gui@Pv>iyRA4iTJ zh@7=nfDk-et15MI1W+J#LQ(}m=@`KsXn4o-)+U-(jB zy7S)LQBSOk9vYAlX%&%dImIYo6Wk!U^OT8qMg4XyMCFDGgXo94#cq-&cXrS zVUDL707K~lyzM3~t3wi2rmY1KCvk=W$g5ESE`Ug=DcbS(iuLhfITjgsJc1n7)rC4+ zFiJVQEt81HLus`asDwC`MZvgd59+lw4;$#r`0a>Hx)5_vGQ~~DmJ2Evix2)Vci;e# z`-MFkqQSQyr$G^#hKh)WqQ=3FQJiVQB~Tge;H|M7LFNVYz%5Yys-H?42k<|&-Yg<lG$AG2cfU!y-}ueN%I*6*DU{^v_7pIBu1KcFu^Z_}2yL>*DTQbWDgd?kyzXe+ zA4A?368y#a=#_hEd0j$A4F?lB3h>R-B3y00gp`Ys01cQA(e}wd-u-Hb?k8Uz&&5ey zK%$E{u(Y*5GGZIyJn_WaPkFe)tie<55l?JRs_1tN59*HdjfQuV53!yVvz1d;fqPd0 z`><-&qKjjt?vkBTe{wVR3ItKVHFblKjVAEcE5HGNvPkGL*hafNn&;A*VhD8u)S!b^k zpI0rUd)2po{B^*1>e9w@mC5HLY5Z;wKv?$lWijg`-{HwDI|%s30nq%$(IMEb23Vf5 z2MsW(5qUh}y~w`30GN-A)o&L7%hbnzG2`E^La%J?Xkgki3yfOJToKl>gX?k$_!?*^ z@$d7MqjijD%oreq<_ciio*AI`cNZPmbQwQ9?X=HFC?}LtF5&Pv@qw1f#a+wN-4Jy1 z477ZS=9N^eDy{=Njon%N*W-=+(oZGF-~_4qZ54v~{j7@K1rHc9K9`^fJwqv&-kYK? z8BS->haa*k9S>G!>1ua9p?q~w*-x+_Q`L*@f@6{0=wZLEpkpKVri8!cHpVA_7wl>p z&vg@KnsU=eW|u)U)Uje%l1=GUtG=nnl-(g?b}AGkDv)gi*rv>PT>CXvgRF&sXM%M< z=ndhup}zGSJ!ug9gKm-NS31pD0J!$8_y#yV1b86xX#dPvY*8Mvqb~wgC2PFR+4@~0 zkDs;k#)D)$4qQ5E;9I3bIM*{ayhh%n=AhboyHIWxkr(pm!Z>5x5v6vE#0~l(uL%1G zdDAgjv~M-&G)zDPfGUr(;jlTu_-M245ZVGGzw`3!sNX9f?yz_Q%|B165 zQOLDL9ddx2{Uff*;#zjQOQn&Lc?(*SmU^J6(NO=_g0w| z&a$V&8Wd!#XHXERX^bSX`76)K8xbYIhRqzQM{@`cQa}SZOh2dsow%97BaF8VLPdW z!EsvUxBd=5@S*urV_v2Ibb#3x+$}dZKp1UgcB=wgy#>k2D7NOL*M9Y@0vgH2qyaFr zP4ieImtOeG8BSthHP7WleZB>8Y*V|oNzzQiBii=}c!7`{m_SR{EZ7D@J|l>BDUQB7 z4&Ro;;b~$Qz|(mJ8EA1uvqq(mRnN$c0(V;$VI1!d@l$~$tY=y3{xB2`drN}+$hPP6 zKF1+DjxkQ@srS7PuT~3T{6AR_H41h$hwQ_-Ds*TyeRQ2=b*OCNvl-|y?E^7T6VND3 z5rf$V>!=V2=&kG<2xkCyQbxI=QRQU40BAr} zz&)CW%{t@G%AH$wuAxI=z4DRge!%lR^9ewZ%?#tN`SWm|H-U0r~-qjwugBro)&hdHFoln0ZggW`+Gqqx2X>DY~}_ke|+6;Mp2neH!d zk^Tf;DQic|OvJr47J*s4&itdpmeCpi=ND>!-O+LE(SbT?-7vt_6Boq5YxZ@)3tENc znEfA+|L0CRjMEw7_jil7o3tb_E&qhsHDb2*(cy772VyH5CGM_5hfH^lW{YWzZ|g|@ z@$F2&fd~KxuJoRE3vq{Zrqr@n2U~2Wd?pA)`b=Y>*RTuP>4JRzI2lRyCV{%o3+qe1 z|7cjU&m@2p;{uAR8=eI|3HVVrP5uKVTShxJ9iJKj?8Y|Cnf3Yvlt1pw z1k!{L!Z2u_IYdi_?(${^2#6~PzWdH!#En2$rwJTkUhOpVm5-e*1-LSOBneMdA$QQ*g8wSo4$?XsaO{VkJ2DLd-J0PN z$bVw_KDGfS2e0*W#ibykE~VTXAyaG@qOS}{yuy@0Qc^ijaIT6%2Q>$+MR9%h=|ZKN@D^*gFUm>4?D7kc25n9--+RTy0N*?8MYR=pfZSsZ4zOq)7{LN z($f6k4ziJB96Q(T>Crz`n}rgC6N7g`v>jY}Mu;IOT#4m~L7vWhIvF~n{iSZ-LFv>8 zo>Y&2fFOD^iXM-FlJq)eMHh z4d2D^e6uZh8DpSCuPO+E9&bbN@+q2fl|4J=;HXUe7ET7jGV8rzKDtHAqGX{Yt=GTo&fIT^Vn5O1suERi$)TQgux)b@-kew{*S{1s zgYv*lUM63JkIU3#Uxrs!01XPOpkdJ4GlS_SV}ykIL1$}6eg7`HG-tB+dsOybJHdvn zb<|kzti{h<66e-M_`(C`(6YE@uvLOziYxn+pk6i_HN3>$OY!7Pdk7%SwQYJ@_Wq95dQP z+k*Cimz3i2Fn|%(XM=zN$#C)tDn+%(5pxrqe|cXvp_-A#-C!yNrTaH+K3(fa#j0*4 z*~L$1+lLS|))(>@nr9dvc>_bt?VwOnH{ja>5{2S!&?>6=YKN4KBhD_~b8#Sb{HUXn zoD<$6LCD!A(VU^p5Gp+GXZ1Rg*(i1|WF=hh=>5I)>(ue#dDq_^s}EZ&@ZEV`$0#s- z*>{{v*rk_&HpD>)myAON=H?hk7(L+9h_;6^37K#P)WVRoBf7$ZN>Aqsw(~RA(-G=$ z8KziAPK(wLds8}*}jsX{#xtAPw~#sB`s`b0car_`uRX9vab5h zv8ljPiRqAfr+#;Q{sldIe3l4Ol%7A}FasIpr38ou+vm#oRq4LAe|WruzR}`VYTFrK zrn@IKIzlW<_B-k#{_e)FO8A~-)@@$OrwNcD)H5YDJ`Ng?>CLxNGw*>Ox43hSNrKiy zgQb6c?N+hL6H1|+;ts@m5uGcEQ&m}p5A!V@r|(sQw8)x7 zQq@dpohZ1RB^8>$6nRcdvT)!kJk&F0^phLo1LJ}LZB9r;2xnsdwwq?wFsOe#`Bli- zpQ!(Sai9LPeGXL44>USw<$9pQ{&)`0jOR##7CyL#o9?>`pSr(1WCY%3{JNZB@exz; zgAPM_WEd9OFiwBkihHo!+BcEZ0qum1x3yLd~X6uETM%B?Al8IQXEM4insdDx?_{QH1rN)gX3*HBChOKF}pn|}#&? z)9)NVBzpr?k#rDu(rasgO-T_B9_|ii)6;+%R?e7> z!2pXJgO|6QweoVyGo(~6bWIa1+$k+SzhwXD0QRavun*FSrxewp=uu8xm~1;1X9xSq zWFJu6j{v?2aa{Zc`SMXC8a>PEK`7WeFW(DwbwI_CF`emHwx|O9+(agn*&502yZG78 zO?3ciMl*$Zc|Sx8p7A%@8Oxe*>`Q6i;#O_FHEzhu)(X369Y;I#0!yLTE}`D-eGP& zi;lg&uIS0amsj9GDvr`LMwhE55>|m}iiLqSv;WF<>oUpON&=sSoG>=FJB#(W$J~7; zrqg-KzhFSN650o@l($3Wtm)99mEku*%x;C^fRg$zSy%P7fEd0DI=OdVfd+{}Pt)-g z_TCfpGf*l&l~}jVx81!vG>VcP-VjDYYJb=8L)Xh_$6bDJ2_hl+=IblGFx~=jN4YOf z%)!QxSePUFExIw66JZkZ z9nKe1t5?N-moJvBKiXZAgGXqsE`R_7St5w} zPNm|%F4fMT#0x{1MJ&Kc5Vp1jY9L-%D|875BWcko|3=u6P;}Gwk7W7J7apXBxNdIf zG%w*!yJzJ6Z65mbU*X|8_|K&FuMc{BU-aB(LKg$c@!U z4yMO&AkLH~*Fn*%`hP6Er7%xW6|Q(se~$;`VM1hF3@xfrVs ziK`|v{l_1B=Qk1RlR&@8#Z}m?D=v+4o>3g#a{B-EPyV0o(?1yQzwX`t=j(Dq<7#bp zu?Pb%$^%rx^-X$`rVoF%0QgA{L}>hVg;ve@EE<`R$CjNk4@iyK)D{4cl8V_m9GMg)kqO z!%cn?nk+%HXoW0q$P7}1fwFXR`9lkM#b#PyiPuAI;xUpS&5{TI@$rC%_8>isT4+%S zKb$$HQD<1N2}iJnq&`Mcj1ybI9uU{R-dE|EgVvU?g9tscLFHcNo`ci~6+|l-J%5;z z$npOV-yRpX3bPdj?yO707n+j4(D#k@n(%<*eK~^ezuZS31<)G#!IM{%5BX0!>u{z2 zMOgm18d~x7WAO7X@53RzESgwo*_2;x#Ubk6n)OTOXu11}&AL56FIV(Qo?@(%-2GR; z&C;j53SvJ`FA$p_Mq6{AiMW63B0c+WR#WsBs5n8zw}%BdiRPHZhzqw83h(*)pMzuI zws$j*=j;b6c-ko)qF{b=zkz`5Ubr?bTVKr1Oe#cX9TGVCyS4oDqf*psjj4BjrU2VO zm?JGL=|w=kX&;Q23Mc;<{{DO}?ilX{7{WWDHEXzoIecznA>j|4`}e1?Bt3slYd_s{ zDbXCX)&`-wNW$L1?zG|YO@KOP?8Q?)(StKIjL!(d;eoV0z4ZTZYZBn$TUCU(_9In1 z5@V3D(gp6COK^Y6fU&6*B?r^&n^Pyz110&y0LbXxSdyoQ|NB?K??c{GcK?k-&q5eG zCw86U?0dCXF*r<9k>dIam{}W#NuOEcu|J{b~EC0ul z&!QW_smTxW%3`q<2wg!t*sf2bZOJhRKTou+T{Yo>)ni$CQz1gL7g1Qb7|_%o5M0mw z(FROxD1iy#g(lGII%$J*3}$9y1-Jn_>Cn|wT+Wb$Wde#r6Bua-5N31+_Dp7g11$r9 zs0^3{PH@?RbS48l9%oLUeuuOTNX zIS*ph7}$-*wzSjxLJ@oWLNan0`rv33sK1qKus|s=a8Ui1h%Ugcb*_M?03!iz8+h*C znQEh52~eIuOBCU&?*+1bRd3|&YXV&B&|)>l3@t20doj6zShxgr$9Yp1G2ulAE^JFu~1BTknt3)3a0cSz}somk}$ZXN#xV3>mDz{ zqrP18vO4?CPTa1)mLVV^8WrqBSe#+nAMLUOqQDkTqfeF4o|?nJX7->L?xie9zbBkQ zAkB*`Qt`42WR@tS6uc>N0!Fi|TWcGTKYz#}-OIDtem{i1-i7)So>WfxLiQ>g?#cR* zlhmXnA0Peq9l_Hm$zT{aDqR6`(tQ(zp~9<{>*IrUVATtnka6V4Jz6#Tn&g>mppZl- z8>5Z2$lib+6zcx-EICI(B@0IrezO9!+XM)srZnH;X}-+*-4Or@?zEO1juqBW;XBAT zkaG35O(_jBD!5Wref8ZpjdF|1L?Gh+@!&1|q!$?A)+<0O(PJ@+KMBxzR9F|J-Z`Re zZ0om5M^Tn0S}G07yTT79$ZKV<9VNIc>s#ekY$WXNiAJ6)BqK3_2b=&_;?fneQ~%`o z{r#Hy@aEX%$miETeyw^`FTxUV2+IYKAI3n7_9Ae*4hWAJv_(P~tQphK&RIYp=Zhgf zzcIbjI`LTA461kF9#$;L?pp&_^KFDFXPvv%`403OG1{Ga`N*CJ3x%SARvdIpKr@j1 z9D|A^t#Y?8@(Wf#Ec_Q;0EwEWbKqs=*JTds+6L_{uHUALi;rX*`q3V1#S0GGb8imx zCskC#JWBw+Ev;D5Hz;xb?}_41xw$(Aw~ZTs5`DQ7_O>xM7A&uP%!k)ITa42TdG(@g z4ji-vhWQS9p1jZNzAUVOXj_G9{Wtj8JdSVSWnf@$hZw2EuvNHjR(nS8OA^p77Xaag z5h=mv7=sV7)Y3N5hV4DKZ<+fyirBmyA>#qVQKBO(z!M%^bvT}-mKm~%>_q&9A#cUe zBX<|VEjxykHT4`LKH{DcNX75{_x1Gt_Z*`k2&IQ@7@~E z;3^ppYLq&W-e^H<>A|4Mc$mrv*^zbIs$*m&dI1fjh5T?I-7FZBR2X4C zrfzdwgw>uf$edl;MCc-D$HJ4wHR)X9qmcX4LMPQYFmFb3gO%d%0HVL2mbtsohv-yv(Q|7md%)@z{mT?sFz0-!jJNFBEh`*J=b1Gu>cJC(oWoB!=N8gzw_WJ|zkp00Vp zok!By{KO?H^Bmv1z;?`Bss%D~#sib&29D%J~;t)5o6@XE*U!-+{>35 zAA~(v*5m;O?8;k!F!j$_BuzG93c>yPjDU_dv=YnSwa*#pFg7btI*pjQ4lssB0sHCi zJg6QjsjGQF7&*4jzaGo3J$xI+rCnmkTHc?45l`YsqTQ{XQHmu1~Q&jJP_vrWu5ck-t->~;m8`jPT7=7fEV z8`fAml?KtTZ=de&$Miqu7`f%pcJ&PNk7Iav`6kzaudYh?JYrWCs_g{49Bd>dsIUz6 zqv+m~AJ-XF9TyDD>Rib`BhD?zh9>2~Bf_8Zwe!C|>KJ!HsfUoX%#wvyCzncW9|!H{ zgj%nzggjQH1#*p4XYXQbcEo3}U}<_Cq(pB)=htvK9FWt7YQWS{(^7_CMp<7uhMa8q zN@R#^%i}(O%X29LN%iswL-~3_D5%dK?W3|Q_MKeQx_x(M-;E%h&&qqnYC&okU zfrBg9q#98r4@Ty0^zJ&w_;&TZM==nv5-%Dt>qwvths|eXJmBj&p0(x*$AHz#Tc6v^ zcK2N5@)r;3ZlrtPSlTuSRW$hwv4wfu`Sy~?E+V=yC#AzK z#tO;|V`%W6t<^##?{Xz33O|HQtl9VHAVlF^fHBbI;cZ1~&tswSjXFr$;e5r8m6ANH34@YZLRWm^aK(F*8j`S%Zm%v68N6G_!S2ftH*zKxbx(O%O{-ZXU?>12O1{D*&el|Oa zSmHFJZ9nMPolZ7Ex2WkNcsSxMor|Wi2NBHTRY0fDRAgW_Q$B4@ia!oPm#7)br|I)nt(0rOgNhn zAUc{0KwVTgiD!XRDo<`U*Hoch2M*&8BD&nY$BH5Zgl;h9Atqls6{9v-Dw z=o?oR)L!aDyxwCDDT63{z4G-<>;uhr_Ms3kAehCmig7vuQ}R}>{dXvhe6N*XRm1Fd z<|zU`fOMRsdF2VV6rSn<#5ipB!Xe#j-)0Eek_3R7&lER7s?E43bboL3BbzIfZ;dEd zWhq@AVmc0JJpHnhkZkzeEXl!q=nXO}Lc}{v+=<8ait&D1DCc2v>mF=J!J)hf{qba{ z5_a8}c9ZKMj11%zWPoA_{YUT*A#X44f~}kPO+{M<$7i>z zpTJ4TJ+_`D`4dt@4$qH)%u(pgXXSSm{+~s5Ex?9N98`@qfWF_I@<3(~FexEoO9grIK#!bB^WbdYVIr&vI4xxH0O_@yXkFkg zz0ro=pvq&&-C3F<$~mdunrTFIpFYeE86cH9fwCEzM8<1ZUdVc!G^1iU$@@JxKxdba z(bfO{ddN9pw4k@Tc=B^4m{=QuPPgWVc;4Zhq7n{@oV+K_uy3AX(jF!UCCV61ab|_E z%AJlF$)lEdU_zXyD1n8Nt$sx_2}!O=kA5h@(O&zImq<)9^g?=Ylye~1j#Ii&+AS&Pg5uU7d%q@m>Qi4mZACFY5C_D zLG*CV6KT9+P;1Jj+z(QfjujYscu-Cq?#1vo+_Dr%H4#ZS?T&qlV%samB6Sq17pNX& zz7sl^LvlB30iE4TJTMGj->iU!w``AiVRgA7bZ`X7el{oj+WA8K_Ojf9x^)zAfil%= zTcF~!>RbfKFBZjCp)9P6#WHeo*OTnLd0h*5QkkxBS4l1-;|h|9xih03hr+N_l>jdp zrR$YuBGbgc0sn>CWEE21c~C~KEB05|XNn}Mztig;hAr(g&WSvxA)8xx>ElWyqKC&B zC8hrGhJV-0Z_$z^;Z(^PNVLZvJqcM$huT4*To_gE@I$q3%#^V5&~&iMS)ib1S=(ab z=4%v#i}ts6zb&>ayAbE9F4Fr%vVa+sOQv!X_L=ahv1mKehG*f(fU>JMxhZx&cnx_} z;XWp6D~i$%#Vc?>mi){-?=>qF4}qT-SfL%$hHcteBwpCuq*ct(D*-;46R!*CpAJ#qdJUI zj}K>C!3MU5&oPiA15ntb;h!{GX%AjPr^ydZk^n$5rNoHt%9*l$t1zY?n$n=bhR9OC znw&dW<#r+6{5btnS=?K2vnPtFu!*zatc1`Nd#eTz@;Ad4%ZI0lo+EmL!YWy21M2YZp#gEL~JI@-&Kk(#U3V8Fm;oL6p^hWu_o+3;pUq(HH z=?6~&x7lI@Q&;&apaD(rUi!{fBr8|y3k*6ubA^LBg`YO?89sNa9`FRkm_$h5$1$yq zz{^lpVBntqZw-iVs<+`5sOzlFuHFwv%3p+FQx^k{bepqzQW&1>yi4OxKvwc=r`}@hfS@erl(9 zYb=K5@vtN(TkIBqgeNpZL&!mIaAVUI&_`;mjGz7q7HyRpovz6MD&jPlM0JOlNFg-Q z=1`kqsE7%i$T8Y3H{ZE;N}RokY-UaR5VO%;+x1|J?#BPrv^`$5!m91u2^p{AowR2Ee z^&E7HAR6rXJ`_)J=o;vi)x3vZ-c7wGJtQ#*Csv<s0m>V7*Kt)FZ3kE* z%cdLh?iyjNis&nr0g;3WN*XA(>^@)RImv{wlzRi(+`Z0b?r{gU%V$ngFCbu{!wW{%Ylg1gE3wW^9~ z5!`+L@4_)UVLUa45kU}anbMJXgiHIo+KDU3$5ekdO$at#@4wK#{*s&qdWd$zcUnyD zaHGypvmej~8Bxc|Cryq}`g5l1S>crR>>D|*9SLcr)4{NQBEH4_i7&O)*;B;k6Zr2T zzhqj4$vVP308ci7GZG~!m1W9-fWcEVl{^42pT5FXq_19pn3DBorO?Am4!59;w}eqZ zEAc`kt~w{0(_;&o_gKIWI!w3VZ56o9_}e?ab2Wm<83yIJ%$=j1P8B@&Pr{~mP+TE@aCT>uB#Dj`M^r{?+!D39I z%KYT)7Qah6H8ty(*+~m%+$;+9heu)T;eGgv>3P$F3uwfo@R%8y^?KH5pP%S3AG*-h zap_%b-*f4-HU?0bCa&pU`Gl0r6RV7T7DfPjj`4VBF)4jVJ~_yLRPY=ItgIcmRIkC< zZXfgEVi>TyF^J&tFntR-O}J@jAbH@m=0SA;?>sbLwUF~(Pf{8!Lvk*J(F`OuNB8vy56q7&L)jkN)Sq`s~9@?4YlTseniw z)nULJx;0)k2Z1aleG66qPdJEe_ZWuakW2F>r%H++@UBji>kwoUBkc(N?YjCiBKW!F z7QA5tM_JRNXt`q^*%S%`!`(Q*#hF*Qj@Y@ZGdB{fYS$(?6P3;neYk*@p~`P+S?hr@ zNlIUg`-sGXLe~pJ*?!B@w3yf&i4|yQU+%T--R5$JhF!F?+kJ5&eAWXK345BkmNo$u zLN0;Sq9;K&1h*idx#R-TeizA8(*H=NrThTxe_3l3WR4jOL4J`{;RfV=6u!Pk$y2cN zu&p$cBt{|$Yv0)JaB;@HrxKAc6Rn_&1OLG7rh--8`hdu1*enkB5{_cH)k6pf%qF7P z;iPdGk8*DCgYih;1Z+B9iE5oz{wgqo%=PkZVjx2=6ARuczUK3SYCyx0WD ziR1^%g54x9m(+M=Bz;gALx&Qb%qM>`+)gwDHG?5E; zSWSbw9v{?$15K2OfPI^M$;I|SGunYe>&lLlQoOdI`SxUHHlq~>+1Xx?oqe%Wl# z{qNs@%%!bA-2DkgGezeFl%b9pce~2@oN3=D)e5XjXxiBXmwFABA;1pg7Qmg-7!WAZ z8QPtzW%!^6U#MH>FF56zR+pTnY5gg4C%|%2^>35qPybInSm%7-16x zrEVgKxttQ&4pt%a`JH!|1ppdiprQT{Ff%Fv`?4DWIW>fLJA&anz)dSnfJj;=?&?%Y zb(#Oh&zUuvJ*CVf0CB;{FuEC;NfdLKXzh@n!H4CGuYn!d1{C53QN@6>?5vzb3$I$T zENXWXUpN7ll0g<3bx!LJl9_R67afuR7UkuKFJ#a$lZ`679-KcI)zEP>_GVXjF5MY=bvOuWazCepGF zVCr8YM6~g_y~kpL^4}TIgU7IY)m>WXr}v#PnoVx`u>jkWcp=(MDL&sIi6b#ka5uof z%$UbH?N4XVa+sjqfPJs>cYnr|;yJ9rM_?6U=6|lr;YYpaSp}p?LZ-<)Z6-Wek#Kd} zTm2|*w^q#Vs}Ci!Q1+J>yWf9uK5fxDK*OOSTZLk6y=+z3G^pDbWi^j0L@JxnX2qtiDXTRU5&oY?8{Lq_xyKKt-A4NIs;q;CD`^CET2)I zRG{W)seN<`o7wHGn(1T5=9foJwd3d>&19*lpZY=CzItx zKtV@jC6u^u>xBIcWj*h5Hs#P?M~)jZv*zHX+@hej(-`k<1Csvbn#(X96IEJWKdR$0ou6CsXR7@IuwqtaD|Jo3GJ%jM`Ij{ zhsIw-bM&>AX}mEae>mMTod=fO9VL}JodggwLwPc%U6uv)p?MBlK+(jw+=GrgW9yhj zgXqwNZsWviXNkF5fATnjmOw0`VDAtFLWdZbi(5T_fOkqTkRm{UX!eX*QV3pK<{~Je z-|1q5TqtfBcwd!<6Ux}n<@WEKP6qV zBe=ae3v=f_AwSQTymh+p9h5wI5W(=B^HA6~C*WwV!A(U(WT#Yc_~oR=VKRZx6JBZ zgcAyh8>TNsz{K7+idU{|UI5O)O;OUQm z-%VFU)5QnPiHLQ%-h7%wQNQkeMh>J%Cu>7cm|J!C6LwmQLf*eZ0_x}YPl1auCd|f4 z&I-uBCrf8Y#1;Nl+5IDH3W{!T?fMY9enYdf)*wKvd%IiIk%J@FMBVsYw70mhlY-$b zSyRT6Ena+l`WMmQ`imoxAyPgZk0h8r%QYnENv&pVUtuC=3N?V_Po?SM?YhJR$Ejv- zlBE6$}%m$+%{_^}x2YU&o zvdxUOKHR`sy209@(2R8}a6G8BZ!Zg(`fCyDENwg30;5pGu96~M?`=!^nw6SN^C(|9 zri7z4*J1%beI9bSFG0EY$`#1YkDXNIe$Kq&2v14j-l?Z-s8)gT9#DefQHEQz>uo{niFlk?G3jyjuN zjTAJ&=7WNpV-C}1jl??H@vk79zEtCuzFh$X$QkWUfAW*7*~r?| zlhx_sMX{Am&G!Q?h7M)PT0DjsN{ZX|?U{`BuSq4gSuF)xVhy$imFQ>$Y!tRZX_?LN z{2>Erzyk`u!{?6HP#wfi0ur{Cs8(8r%}FVyygMLs4&uzmH?gpGp)oZypeDwBdz-V< zm-Ia-qLi)Qf+DKvC8I5GeC<}BZuVS_P9VvXj=b)(iEpnmQWyPY$$F2L%c9b?FG$Yx z$lsScft7?r>k}Gm0m&DE^iwZnkckGczgeSR=8ons6#X^Rg7?|-xab|oVsnr;)SjVo z6qwHyoD;$!fpU=b&`TIOBg{J>lNGmcIf|ajzmxATIW|yA|L9i!F{Vfv8E|!eOM#_r zE!X8h;K5I(0B@MPlfV0HS~`=EAfG@?-uz0>z*;jFu&$NIlgF$&Ki3P?RKI4mhsL_b z`sD<%DNm-WQ^hYTXP=cmv3u6px0mSD>c0MMq%!kK(vzU5DN$U1keyg8o3&pi4MZmQibM+ESL@U#oFuY#3(Peq;Yv3-!F+;D^d5c3%8BrPR&C^W|jT zmPUQ{Z(lz+Bm4O9nf|pU^Mub5T;0-|%GQ1lYaRN76nX;S-h)VE+BEt zO3Fl+alVjpzE9?|>^`k>r#$x9x7Kd~Z?mQ@)3zUvQM%L@;8H0%dYjR~`t2M3Qm~&|gC*C01d+_tGpJ?@%cNp+p6;6CI>ovC}oT%t6Mtebj0Ks}F zWw*5D1Kwtqg1y+A)jNT7VO2cQF}7usbg5=kF|BD|O7 zKLx>a)W1zJqF56Lq2jnT81?H8{F2oM4}+p*0iF!CqpwcfhC~dLJ}8uHUNEC)ONqXu zG;ULgIJi}ZqPsz{RN=)BAibzmd=H>?GVZ*4v}~%k4h`DAU%f~WfxL`~rNnsF z<1Utn$br$oV*Ck`*MBu_fe1i*U{ZP$9YcPsQKTUwI&35^6Pp_7+`IpNN3K@8=&7K5 z3O(8Tusf5>aqnS0OAwMuy=u__=@=r~wsI|904Uidrjsm5+77=?$BMaUB>%}GYrojs zE*Y2d2Nw2xp8{KDT4m;r&3iwsc+xQ0LY=^`+)~agEIU_D)z8+@4t!%Rnz*mTsP|vr?7%godAKBP`J-@ttZm?Cx=n4Ow*GUo?#)2mnk`h@J z97sE5q9?AGKWXr@mizN>HXZ)SZI_s#?a+yv1qrKlb@{2RS#^9V)szyY)G9*(E@rH> zhh3n#wqD2E+dso&h0f`gG;9#FSp~;O?}Jm9&QULLDET3LUWp#cW&%|ee<}BEMl)7l zTh^_W3oLC?jPqi`vPLy^e9u!a14OR~%%Q4~D*j1(1LggR;)IYlYq|ROCx&^2`=NS( zDEQEvt#r%qLx50<#HMgRVdR1fhvcmwQC(jEFyTuhB^~Hvnd_9rayHXBKEM~>7v8o> zbGJ^aGZ)c1)-qgHNm!m8WSlErixVZTdiy4fmH}ktAeOW3eKe@7bHP>7KHvA0o0Yra^}HlU$Ff6~w`HSW{pWbTE5G#*-Q+aQa3nGv>yFneZ;k zf1Cj<>i&AZ(IjDJ`X2LFc_!Lq+xJJLkwrVr27lt;5OynZHCdeKcoisEgS=2%-3vee zGT@H&rA-J!K-$yL-Can+z~!41HV6Kkl*m|VhwP^nnG2DR_Pb&PPTn=TPLUxa${ml7 z1Q>2pw4K>~`qKIbhmrvZ9VlA~{Q#q>%{I9#n|huRAcfgPY4znELT-j zBw~E_KJwYi_D)jmIfvtl_Yh-|Nfg&do1$w@wTbowqo<7uCTBl387&N~fO{_cuE>7t zK4U}cquTw>IYu_f?;5$!#Eh+O`aEOv?Q#0%p}=Ikrx|OJ*YNoJxw}{NVC}}r{h1`J z~Ej*{jRglI;BxrI?N z0en|h#7$GB{v)B?J-i3hh>2B+VRy=h~B!{}ph zvwB9@x@<-8y@IzYo`)ixWt4I9ubdeu+3PF^MWRtNWOUMvas#K`<2H9cP+a}_wWz|5 zE+=?h{Wn>T1bud=EPre9bK}rE6YD&!HUXsrclcXL_+nAC_uN=^q8WwHRnhCy67zrT z+|TJ?eSX}L_IGEeoRM;@fAAHqm?or^IuqF5Ow1xeeV5^rsw+pSt+-OMKaygPG7l-*i_Lc;;|j{Ix2&X(yy|N6gpswIsKhcui!zYZ7C z)PxcaOC4RD&~U!?LvG(69uVXi30NzmbWHRLQ1zm{M4Gr0C%;f;G^ad=+1p|o6&Rd;G1P# zex6pT0t^A~t2UKEF7u&Gp369)0lxvb?0qT$A+m*m54A@U1KZ5c#uEz*D5%3^m^!%BX zf5u4e)Y95ReDU4}@?U%oYFd19-r`8I&oTtrPP1Sst`3Hcj8wcG_UA$s=}f^W*OzgC z-_qaLvT(qey8>>2O1<}%X8`M?1+lfEGaa!X7Pe!pD(~0i+IpTue;MP|E^&m_V|}AD z?{uyguEfR8FB_HfNE-Z}j{rRI_`n{Qw9v$uXfYnEP5ZXPDv4gqQ58I8I$dMpT=N^h zLI=Q(PSrck;Cv=0Dtfs}KiVjy8w6sH<)${VlrSeb+!-7Wk0fp%XphehrRKGb=sfrV zBA_e}{Eq4F?$lM@;9DIm+2dZp&h)Qv0Jv}(Te#FerCXpfT{gPP99lR7 zE6*#fDR!$?6j=pcrR7{)Gt*RgQe-@rpKM*+(%(Hsa{jBw8cDEF`l-v3>wjjs9+#Ls z8ZHnn;~j?p2FMO!ncnvP)wI}SnxK+w?{ftFnq6Nt`lKywvBp`L8RnKF0apo*(720US6QEl0;njFexW>MnWGCAZH`jmtbimQq6!Cx3TQ zoaJK&Gfg}z_%NB|PsTI_Ko(^NOu~7lrQ@K$`2+;nCwyqgy@G2S-Z|{98~~!evp7Q6 zEhql%?IfVgH^Us0BCxskImsrB*-ld16Cx!teZB!E)&tl)3ZU7GEEH$LLB*j3kcEPi zHhd}vX|Yzy2sdS0Nu1d z;<<;H6@90qbYuU$#~*Cqv4#_)i2X!$0DY;Pkl6XPi!bWu5hNv!?8{!%{bEN^ZwCT~ ze;*0QCrnDb17d=*P|s9H&jk?R>YEFhtdkJyzz+ilIi8!CzVh5gXM?#ti0bYvkog0w zKzx>H#gPD3-iA-3APFfnz4&e%xRaLUZKvJOKL7-Ctm6cHLkq?tEVvI+AkHZDGzt&! zR13PZES47hZA&3lislEva%5&DlAP!U7xVjt)vcpQjIBuxPeQj&e8YvgoB_UI8>qxyhvCBMd<2jl)gwj0pAkT#vNYG3vet|Oqlr+Jw2nqD0 zXy!&4Lk3C|#FzZl_Bq|ht)DlT#PL4mZLwqO^y-|4mw{^it$ zM~DF&DNM=_Xpe)QS*3Dv9rjsZ*!6mVPx@dnH75n2cQ=%Yr9-xIdSyFT*wrgnIQruq zaprrF#MKA!ZoGQc!c$wzgPLp`8mFR7zY!jSIjSEhw#TCwW|wT8TEI_Pvyvbo^D%h( z`e7JOAJB7C3>9Aw5L5H+%tg6VgzL=`5!k>_1fQpZYrF3DtV;jl9`MqA;KPr!jrFiO z1TJaBqyz{H=DsbE1w0eB*GYn%RukmJs1^rW$7SH7f|@1ZO!(IyijOHGVd+B|t=@z$ z8xLQ&fS$u15FB$LaQKWdb&8;-PzP637X-KDz=2U|T`~G0D$4oG_Ah4R6;tNlpewKh zK|Vy0$~;;e&%ydedgtCGX0+zBx9g^|W;C*R1{!6dH(_gFbT0?1`92y*5@ zl&cOU9=+u@MjpUwX7*DU(~}W=5#fq~-}+S6?w=F!{!l@$h=|-sP4Swl!4S0nLs&JRrR_Zcb4B zrz6kkQG!GP!C@~0F>U>T5x~XCLB=4ifwb~~45wow83W?{zu#*9Y1;XDhKtR~S zKz=@C;#Vq6IGPVEJ64U$^_NYfYn-2e+FEtsM)nlOtI9#Rpk;1~pW!RgeQWy4nZR6bck9n@A+MRzFt)QS(Qt_8SX9Cm|_-jx(&d zk1`85#yMzrx$iIx@}W)m>K5RzqA=IqW)rr~D7UUy$jXLR;cIEHCUZX=A%`~56lwrX zr~)zfn5%dgjTDAtZRJB|c=;j8zoHD`*A<|0bjZ!d=BqY(PHKcWI9Id+xe!N8;P=`G zqP4Bnamm#2d+ZOR!WKlOe+@p99p3N!EAj&LmPv?2qyh@>Oc1KIvY>*v6HL1~z^^hC zdJx!z{Of%ROmg9i1@{&uR#O?j1VN>K9iodozOh@oNWKTCFP0mpzVYRQgmM%*&a$9? z>JTDa)Y&FL`jOd>hDd5;b1Yar|MMj(i~y!a`oFFSf@(0Y!$En1WxzzWUA%Z9QtDOL z2|s;X+afr2oW3lB7{42Mm|UX}k*lf8?TM25fTAlo(85oF5bLOnN78T}C{^S_iW`&; zy#);6a<8D0==_ynnj7CxRj{6Tv@FBbx#EP$CbZgUrolJ}p9PWH6 zWM{nU_unf^bc1yv^b$ReN2i>BYMhDp(Q{T25-?Vz;1A9hX2XGixw{5xtF)0a;9!x5 z@%8K1(lz8u71(JauUSEAe(X;R!Ah)yPJv3z+uLxNa&g75o&aC4GQ6PnB~C;HDL#T- zZgZdBoPn=cA9<8l!0-6bKT!-~(YC;-HU}!cUswLN3f(2AYWEiZN;reGi~&fQiSSvz zZ2(Drf>7QYeYS`2MhCo{q_{ln>J2RNs!v-0dRrWM_xsR@V*wuf6d00>HNTEU7Ecp_ zl2M96eC-7iMy6+qh-gKfR&wBgm!GmCI4iAs6A}TNQGebo4~vEQf=QzM+A@e7en9Z_ zw4a1942h!`_W=Rf?`vH&+bPNfht{tEe9pNF-!F(SMUVR*gM}I7Sj+S1XQ^_+p4VC9 zDQGper01k|ubnGS+XUk_3L470kZwD9|M<^_5BdrKFdTaLlixp8sBO0a(3-rxb>YvD ze(GZcFRFhTL`iOZjQ3|V3lZnUS@GlxhN#&zLQ8w(H5meNf5*Qb|G(#ifDDZ)waX%p zaTPz*M@PIWOhd2PAD=pht6w5%Gi{5S^r^MW`4x}~MUZoTw3seZ z&54(E&R>q>>bnpN3~`%jd(3p6cGM2s?gLO0J94kZT$5u&rhc9{nD=3VUXplml8yMQ zw)gK+>kb2#j5NAeF7a*02Ks)^{pkT_jL-}WbXB7gL*H7|Fk#+UpJo-46>Vm~P@hYb z^s0qU3B)dZyfg&$4GZ^Lj_9R0n8W$LNVxA>?`V|_n1IFwHb3qi9MRdA3gOMW612;Z zQb1t%caSLZQO>G?L97qjsnU@!Y}5_xi$73{m4VD5*Q(ciKol5`kzECyrt)-4Uw=5~ z#x3J!Qj8u~?6hj_ekR66yR-k)NW$$CKOcaY4N?6$d@5W}I3)a6uyJp={mx)%konzw0QaE^#A+1yj!HS5AKi z=fJ1UFM6>8N7*fpJ$t9o##+Ssi8#*StFVzqZA!c&_OlWZH$k^j&nNr4KXKiGVX^nw zS)-g{8A#0>xo`^P?$-IEmU$I&Kt6VZ!b#Wq0LG=>T9y}-ZFr!<-nvE!r zR3U#^J@^P-j_xtp`r*?T!U8YcKazmSDS#PNW7V`CU$4^MV#^nX3aZD4c>oXJ-7L%l z16O#HD|l{M_B5Jn0W7Xm$r^Sdr}e3}mmZe!6~Q9ux^fGBEE+lBKp}`oneE{R zuH>5i{{1_h9#el-%<4$=u-iLRHn#hbWI0Bd*O~s=1tln1}l+ zsSA`^Wp>TS@ySXs6yEp4hmEbn#;WcpcE=v>vgAF`P4*?h^76XwgTs)8Mm91u?!7QiTmk ziN6>+ajgb)ZcBwLBV$NhYn~COfLs@y)F6_Zp-3=+f{>AaPZj@jtj=r+cKI-Z(!3BF zl^1-HTex-W_d#5#3P1A4nl~;oLq(4(Y=>f>>rMsvVvtQ3i1IPwK(KVIW^Fnx z^nOQel!Q?0O>YUFRe`3MJ?xWi@#lwo54iLtir(Lr6EC)7j4d((sVZWY`&_FmrPl+a z7aRS`+jZ~_aWOMH^!CZjP#bLH;ZPjCce4R$CxncxsD&~EqLy!vK2Vfo$5SxjfGJpY zhd`okC=h+LPx3{C<+M?U6by4&-TZNmb!A}zIJfpv62Z#|u#;(8b9oSE+M^5l* z7TtxtqYhM!OL^K8pScDQvjCY014b=Etjc>{@+jP5fP9CcA$!#&J8}>V<}m#~1bu7? zaE;TVS{kUYFrBD^VJkx2xq|F&P(f#GqpydzT{dQ=F7^F++d|eJWYhnMK0`0s>yf9S zK`mtg*V7Ns*NjvL6By9z{4h#>>Luk4@eW!LhJ(m@rzam?Q)N)-+6V8KLbT+3>U$lB zr5p_uM$G@TEdMZ>@oYNvJ_bU3Sxn%*U-?te?MXP~QizIxMv8X;3Lv9wrBXCPyC6E$ zNT4c%vqdp|?N;TJeqjs)9i?D}`2>o$iY5-wfx30TEt(Am5gA0_rPI({gn1sstT?~lrXs2i44(#Vlk9)Tw3V>oLa=O<*MS5Ok zg(4D50sUD?fQR1t(Uc8&0HkT!1-G-6;*lY8kzj9Na#CPPe3UD1xUf0P_7Gepvs_8T z61d;l@P>a{bXM8b|KE%*sVfkbyxGj!LwfBVtK7X`-!wCCa0VELA2aR*{bmx_$q+#t zEY{2vq*2#EwsGLh_&rSIY&HUT(GHWBv!ixFw_A~Qhy*VqO}NXt#|ge<)D!#y zYAg@7U9l`Y(U%xNyQ*5IK0(e1A5Yv{*m1HU_=Fk7HV%#@G5-y#@y74TIf;(Z(7ajE z2a)|{Jgo|2Lxs>by9ISVsAeQE*KzVNNM`1noL2$U9*A7wY(5Mh2Fq+m_$)IkvJ69I z{ih^3au93hUPYEG!R+S=0NG?^fR-3D#+9#KKrf#H88ih2dbg3WH5Qxjz?<<5!36Pt zA5wRD%?>|P$d4Vi-FSR-*geSV9DWfeMfy&1t)A$zDZS{Gi~KMbRl!9k(FgV+T$R5# z2j_iJofrI1Tfz%1_9ut+z;Cn(U!?^cl6*YHppaHihK@e{UN|r2xj3Gwdf!NJNhUjP zol3>+&Khjol<1}4GQY_zBBBW%AL|Xlcd{Jln)nAproM$?6%MMdKVP$lU(~o|MtV}a z)DfQpP98seTmiq8Tp4CG3tplM2Xs7ujPr4J9V#Hsv~%afgX_u?hI~G8EuiGp=_>#R zGW_$rEUOK4x<6pSNml02kD!XGC0bn}Z8#_*>sjx@EC5dWr`3^GG<%CsmA(>t_ z#ty_3F3fJb7Ay3Bllh^p=VH<&ZRf_Tnf}?V_!meAZaVQ<YM(VqU-DPbjot#oJG{`gsJr8&(HwYt7S?@- z4e(oMS*9)miH8K#zj436V9}JP19_WP*E9vNJI5J5d>EFZ zJ$(o^+FrYW(ay!FS}qimT)%qX5owA_2Z<`AFeSD zH4PTYa;fuG50@Ay0;a6Pc9)cM6vmMik6sAdhzDV}E2suX0|;F1>tTqF8Mn#IOk(c* z(sY#1M|1+CrmrM>&EnrSCOGX5>`_QzbP4M6y$ zCm|nd4s+3nQJ}HN!z~vg5{5YkT4C4_{r(C^uEkD^PcS$1GKyzJ%=w_7(VVpHxzC<< zyq$ND;fz@5H7JaBTpi0rA^=J|Cr8q&nSwkV{=LLPWf!jE#=-D|@QDJ}&f>1usEc)g z^XQoM$%s++7yfHbFVk^E^6oH$&ZG}ODf!L!)l!o~_HlD&s%w62yu16uRz01P+Ng)- zzn^BmGdp%++$8C`5f8^zuuvl>Y8Z1*y>|~9t9g%et4L^sDc&xmH!I>A2gW7!ymEL? zD~r<{lO|=?X{sx<*T*8WwFj^X({+T!QrGHuAZz`mXSKtnFmk$pE^q0|oOhWhvwgVR zbdx%=m^|SK$;)~QtHl;f&el@|1R=yX50V#=@%XG`bOKf@LbaX5T97a9#w5b*iu*;^X zCEjp%@Nh6t3Q&-cj^aeH4&u!`#+aqwyaAH%2->e=>knYppCTNL_H8=|wWNyAl_@1g zf8@M={rWUM2-S!^>!Q}fXGG5+aNfDjAtM8@7E<wql%eUl3F}O8ZW+%7{mKhM&K!2odUg(;bTFJ^L^m=9Q22SIBYl1?TQC<% z=HSCGLL3<~x&QU&w|C&oS`u8(bHGwMNY<11UEb%3D*-R7Q1GSeG9Z!d0-Ue6&dckf zJK5E6By*{P@yGjj??%wb^^sj^b}Y$xZN#o8;kLXQUg?^;&5((_+R2^#fr4aWhCeM()TA>BVhD*}!|HmfGci z>hSVG_i9E7Q;(%I%BZN#Zvs+aP4y)AmmkW#bRn49v$*9W+Z+=yLBcas{?Sg|(L5|; zwgXU@Os_sFHYU1_z`)4uij;X}SqyUstI-ep{Djco9sm$#vw~>x;^J@(>fG8PuoqNW)JlbzOPd)iY6sOYbHYMUW;9N@v^zX5fX0=C7aRd;tMhffzNN3C zU4MRD|3ETo_M4uhBgUkuNk3www$=E8%(i+JOuebVv4uY#8WA1a8_0*Hu5vM*tgz{E z*T|qqhSyC(T8|l6{hXdCDMkyhp)^LuNQX@DrcDEkbJ&66<;Ckwbnky1l*~E-FTgnf zw;26qW&a;z$Q!_niCSwEjN!<4*ZR)=`>7!)?DX6(yJF^ga)~)qtMI9>E7Z99us0NS zH3L?nlhMZ|-CuUoR&>eVgEUH;08#?(tdt?hF{lcisG^J_sEkY8AJw=XjS4y!Tr*<` zN_&v@;>=?%;I3*h-WXC_62Gd04fA65i#kl2eB)kT6YJ)04sP!ztrikuP2V4&(YRDS z$4#@$r?UFZ`__{`&gk0s=Hn_*`uc@3Ejdgk`FgcY#p1Cwcb^x@gp@I!428)`4~(U1 zb)E-0&eo6_=L!iB2*y~xlR#{bm}WM&d6u3YD?lP#h*yD{oo;jklEaa?5_Ev&{92Ho zcIqpE6kS2*aZ-KlbZN1R#U1z6=*1HddpX~dJ8Xs({N*TOa^r{!-ugufWy$qsR=&aK zAKf+9hC0-FGwA0aIK_5SmlT8N=F}$s9mI8ByL1I?r0K8Pc<-X|Cf@TUiN)K0EZ@_+ zdDZu2Nl+9fn-vv643>~)(vYYb&c&`=L%9FYuRiS0tpS~1GM|wg0Vn!w91;|3d99ZMR&Gq{s)lAS?Uc3Wb~2Elg}qOrb-Zg4L@~$k@_Caui!!GCjy~rWwTder!y=SFY@c z_M|4nwk*&PzheZd%gOT~j_kEvIbjL$&s9);5|Y)kGzOe#fH4 zlKnQE^$&&2;`N$@-S~1W_8?ec1dX&5srt-NKigJk6G{%sGsZ7c#AwS`151`pdyYKk z9>b+`6mb-1-lv;TtsMYK#u&*pGWjf@-c1Qz!nGJ+*f+X-J3@EdeM7OOS^X;AI1QQ> z2|#GsLg038GmO_b5;2o-Q4C9d=F2d?rIvzZG5m>=4ZZNeM}FhY%j;Q8b<}@zu>>k5 zcBbEURJ!S0f_zN`PDg8}-6D<0h0as+ls>3p(C}{W?8hz-Zs%wv6WjmAl+cOv__i%e z;8n)`GD7>pDh@i1)>~@{HgmBGv_gA?-N!WPH02tK&cmSAbEETKs00@o{erec?ncLZ z9Ck-n>+P3MiXMI4xiL;S8LtMvHlw%8Zm<}Pn@WSQG|eVe%HbL)T0yvwyNV#vG!`cxqkp(KV=Y88dBO0U$IdD~aO4tB^gfsh^;KQT5mG*1E7+Dfl+tlsV$uqV4Ay|mB?UKftsk{f(_OE( zr&-=NH3v9!oD!D+%*k%t3Z58TI4o`n1?0@hW$FEtU$aW_Lnmc+mFc{0oj7ZmqaOPI zL&v6oSNhC~PC}te45V;j6~5@4tQg`}NUogqnWknb_}6&qolIvmTzJ}p`ecEEqhP8t zOhlt$AnJDo!|R3C7@-fC%I}1XwG$;&Z6zb}Co9gUo|m!uA-6Elsm|uPqBFl*=h5W6 zXHQylfL+q}`qOxzHetJNoE^rowSqpzug{p+dQG`zneBmqRbEXVEOmK3<)|}T>U7w^ z_iDelz+?P?s;E$fEa$Wlt_zj%@$#S}i}!}q-H5dJUVHcVwzVIS?fIEsx~=7kM=Oh8 z?>~HY(_#Ld>ktI0=#}YK^|nH}g4YfTwAd1Z^Kx>-rAj*9ORlcN#lt-z;)Vw^0=T?o zeWu}~pvclXv7U41&W(hh@Bhuge?|m4MId(O{_mea5}+UsMnLxyY@%7`y728X?ew`j zTGW?`&$PZ%xrbEWC{yj%!0PZ4XGNM0`@n5ff#@aJKGPc8v#hPKhR?{WV8hUvxP^^3 zt>LFA&idIsmot-}fmx!P2S|g=Rd+GYAV0S)>)o&oVoV#UcCasSAT&Ysi7%tOGxivA z{25$1rmM0X_-lt<-#kN-Qy#-M8uI-5Bx_~kd06Q`-r`5c2@BDlX1GB98 z3vL3J;P@%%j}`g<)0cUzJQeq3?WU3iyRzP2D=fjh0B-OR5RzO#seW1n6mbNlSIpSY zdv|3AIO|Wx?;!$#_-AZ6zrHYf-HX(}EUrO+%6-$KNd&Fd7U+S&8B@`* zaaW>09GWkxjURaS3pFO5f(EpU3?<)1i3g`S(>2O@oR0D|Oi-%1MF&^ zoPhARbznpy=NE3BR|psi8AWJ&VB0vRMF0-*dg9?#WQ0z(`~ep{tDZmgO~&g_?<&aJ z(~@xkdUspTl~ut6S%#UNSPJSr9946*P)G=f*!WEV3zNkdOOyhJGAkJ~fJYF)5d8=- z3mX%$as^=I!($f-5s{(X9Y8)RT#Q&9G}${q0}gCJ!egX519vU^dE(e2<^MXg2+mUU z?3YI?{Qg-{!7lY>X}nI7?aYMfWS4==jCbEe&biow-PNOpQO4(`6TT?b5LpuP!L=#C zq+r2(aqRJ1mhM4r4T2gO;_>h@vya5>6buhXIqM^fQ$`?7>67`7!>aKj(fqoV*zqcL z*VWtlCr1hA{7G3|LxOJ_tXgFoSfVs|F94Z!_V(_?ON{-?612B(Vh`!oA9=xoEnbcD z8os@R!mwC6vN9M8^RC^% z!{nf}<>bGJ_eCjU306QDQws5O7wN`~Snn9Ke6;quLM-ttWJ0k@6D_aB--60a$)Pm+tO1Fz5A4=812D z#zK<|vi^Ac+>QiEqQes=v4PUr{#yCrann_k#d>r4f?V*VEzCEZm>`nB{35*gx7gpy z#S62m7EAuSbmW>8UjiXXx+3WT6KUj$^8!aO#p}pE%w7X_EdvdipIZ zuLf(HdVP1}cRje$uDg=rO-w~~nkAls<$B@6kuG+&D=TsMmzLkxz*l3g+4d_HwbMMR zD_GmK)r!)_B(*c$fh5oDUg5vlTIo0Nd2dn8BX@sonNl^|t&0{yL|DCn3vO)}jKUF^uQsu5 zF!pVM)+BA=Yyn%+tP-v%P`1B&uyNX(YBc>}^t;^US5@a_aA%!xO(%V8ga9<1OVFpS z;a=NE>UGJ@wZ9Tdgui?D?q>>p2k-s>nUuz07t3F$@BZf?Py<5$!hfm(Z&0-ttIVGX z=o9Sn`=g|tCY)_B53M6QY(}I}br27n=bA`HYt=WrBv8C8FJ^GqAy#YA?}1mS+keUo zj1Fq4uq~RZv#sMon0hP9q~+dr4;0PM|Cvqel-xmy)lEkpMHZeeLF4YBvUc-^4``?~ zN={dmt#Ie>M!vRxA)&>y**#uV6%v8wul# z#b*6RZd)f>8q_UiTCGt3q>94T*Acn$vxbP&oAi-C%QDAtsKfbgLt75=#D|QmRkJ;S zF9Dmm5N_F67DRar8E2X9PMm|qu};Kp~bwPb?tdC(Iqq_x|m zet*R4^!aSC4|Dzjn0F>qs^9 zI>-YcjEI|!4!JoO;qrD&HjI@y^=sx^;mI5f1CN;L=`(@XK%Gb14FX%vjRyOi2c5{NB4EW@ z(H1MT8j$2{5G9^lkI&h_xNbn{qys(e&#w{cdYMn2U&{Q92{}Dy zTvOvWHFn*QH3G-8$WPCwHY5>NQ}?nIHu~^C;5`<64p8JjYB8jiK&mQk0GE#Zenw&> z>Sxu@KyDr1TF}jXLz%^bZ6DIA_E~*o%?Bvh9-ZY=6Xv&0b)ls?SgA^rXZTIrMnd+~ zfY3P4(_{WMdurwrk9-=uZ~;pIHUH|MN+oiXjMhokhCXxscsb6#=YHO~3RGW72fLc@ zCg3tXAS%zfZuHGqG)smj#Dd_JY{!l{h`~qq?I5<*j97F4DUF^*1RCG#+1MTO`fzRxWY<^$ItcgyyMfdnQ_H9x_=L!@W&sZR$$ z^0#;H(Ogj7Qaq^%}ZWMh=w!Qru+4C3EaNA8iKoambdFG`I3*GJ&G z4BUguWD`jI4B*!C-Wi87+=k;t`B{0523;S)NmBes>l|HJ;0z^w#FwJTq3Gwj$7kU< zwrhCg#ohph$ZM&0oPg3ur;TZAgQNP_`uwRg`&yTT3ohGK7j!=$Br0@-EFbSqH-6@g zlF3btlcTYq+4HXshF@}8);TNStCcp9LCF3~LEu#MsT>Q;B8gw`uj^(Q2wOXMagO}C zsZ8cmuOEs+6|0Hf@dk%&a-T8GTX4Kc_Ms2(JLrK3a-cV zVcV1K?g!XebpWgim)8Yzh%g3^Mnvb~rEd>DZGz}7We)WMX-?{lXITN=m1|^eU?7$N z^%XoOV~so1f)tS`7p&c*I-n8F!^jk=QjiqBK9_6{$x=%9#_ysgrrVV*0mQSpfi?PS z^yjLtQg{XbT^=BnAT4X>EQMoiU{{KZ;+nEXaMeE$$DF#CEXjJl=L(uun`Z80q4*mr zgA&3v-O78C`%Lr>;l{GUyvAe2rv1Z7DMGeC-#th*|B)JVD=nBdZ85(y_T%Nw~o>#?}M!NQQ7q248;z4wrgbmBg?4#<}t~&?HS?tpi(nYPHZWMUY82 z0Be=5;|2eIC(8nZmYuM)gkVU35vqLjyr4lD(M`b@?YjvAqfUp^hn)cGJaLFQ>F3aL zif*|{G$jrMAJVz8+gr~*o|=?|73x(m+2}9ES`vclfd5j?1n9`Z?>vdD8e;Vy>n1{v zVD??}$wY|nT?8d4-y@63k2!6Ll1BktP3BleB@*&`2YY|G6tuTj$K23^wxkL}6#EV6 zmw{u=oNlkR+5JfvOS&A#X#1GZUzM^YEF!ERHk@MM&lNOr@4b+}1_F>|2MAdDqXUm; zGq@wsH98bIMX?c}pZJtDh%|{>f`>z%$25@!^0kAq73ebSScL`;2Qr_qrwKTLN(50n z(qC7&=i2uM{fcQZ;7{^aWDpB&{1hnHAEI1vqEXL!C)@Txr&I}1K)TLCHC*$a{oNBF z-D&4Lfz~Bdny%buKEz6lHd3nvD;0_VxI=MY@{nWHvnpRy-Jat=$mhhBosHyRxHzOt3Z zfL#2ZMcK-OB5t@j~kI@kxGf(38IVD zWd&%6NiBgOupM_T{~q{#R9JLwk23M%@kobmxWcj;^C;mwKpZ__-kB{knzR&CUJDI) zirU~^V?cvQ?SAIH16V69;Da)gc;<4sf^InU((+fXw@_NilY!PS7)p3wVtei;HmoZC zj;{sGqNgv42_E6ck9s^tkN*Ngv)!>Z;I>k$)p(Q8^o+}cT5}_!EgGg57nzF9x>-Cx z%#bPSsgZtS@yt2l%hGa7H5TX(x1H1!$HqFhT{%E3>vn{b(4evtqbS?)N`kiI1>mL5 z>{%5boN=dTx%D46Ix>R}QmjKUuXO~?K~79Xp|&Ou63rB{e_t*>)W4AaMa<8vcbhY1#`uKGEv$dkN{2 z81+(7Rfug&!tX~xfN~Xv%9GbQE|OS+*I)!_w{o(WSb)1KrLuzwjJ>dRPb*NuWt`lT z3~3xmj33ReP2q3=BBMhh7bhzETe!sXg zCon$$afyj@Aaw-!#Udh>6i0NX`=2GnGK}j=m2%dTCiN{43J$vd-2Zs_I3g@19WegJ z^#sRpLM^v1OGrzba$be(MFzK~KuO2zldnxSS|Z*oT`)fE{E_q1Dx)t>5v21v4r6Vi zmm!dDoY&~suj(W)+ajJcT`FtZdDd*o7d&eg(wd9mfW-@aCComJ?u*V%aZ>B78nrUi z%El!mAe9onwl`?oQz20%ucyL6;|&{>O)0jxXE5X2xI(p^#Wm)6E&HlJciRZzpi3nM z5!1;J3q$+K=%OWvwel8feb;V0U6svtMB+xPxVC^oLEpSai*E2)Zm>UFkadKdjI~FH zZbB*29Q5}PVMW&a#@B^|thZX$*@iEjmSQkstQ1^&lSsB>ct?`S_POwBuh3nWcK{UgaQe7~uiyNruz_}JTf$W!ki0Ca8Y4E~G#Z#S%rQkoqF^piC{?Bz zVM7`q-}5cFr%(L>qsLSg%;t#V@1lNA6gX3qQ!n?`YrJ3w)y9vrp}yU@r!!DS;xkc3 zFkqFb?ofaph-g`n_Cy>B%T*n>+|6&a@?#);+iV~SlvUFl%=&bqh(Ffh>K*sO@_5uK zAvkB71$yZe5eb`j7d)vIou^5SwVOviRkv})X#1|g;8oFiS4muUckt9Di`h3meZ@2w!m!Y^3 zv)f47@|Gbht#LYUSx8JjA0+lP=wemAh}zF3Y2e%R8K%W~Ot}aew~rNdAKtKYW+15k zZ;5Vz&Q8}II(z3&WF`EvKvNkRu5S`f&lmCsql7xm1E$`}yEEHyUvWCvdS_w)8=`#d z@s!5BR}Z6E6yrFy7TI&P*)>B_J>2vss$I9{B(JM^^k)_F!SFpT=*Bo~M$9S~WDWmF zjgua06AgJOF1RokVYyo;zql|xB?JV*ggP~dg!;?o&j4ze$Aa;7!yS{KDNkVeEyStJ zN>};S&|_Rxu?X<<1JHYhr8=>x$K79XxuD^)&}A?ZnRd-Ku5&_|Ste|eq$2Y`LSfnU7#T_~j-#eFzBD){W- zHg@MTMbCVCdM%hFGIc6~2BL4s!<)0W{(#hJFm$DM7%gUwC4QwHEl@jPmdf}!-`QsV zcT*a7UKplUvxx8~uNKEujjbNYT}C2mao~C@lfPAv)P{rwL!}v!PI=?l4^z9ge+NKJvTh%qQvcIq!PJ82m@{r~59mKY<8~XT zs0FZ%P5$JvXr~F4Q>SV}&c2OXy51u?ePMp1Gfno*e*A5BFgoB0q*V}M!90A|YDl|C zsVC$QM#RM}`DlVG^d$c9mbdnONL?P*1sm`j z-~xf$D_?H@W>p&-YF>ycFDwwC#p1^j@v9!@VZzJE~8 zQu$T4_#|E7<%vHsb81`eM~8DuA5bj+Vb@Utf^>BKK_rg~bR^z{>k_=I!awn+r$oUK zB9)*9{mnJ72wWDm&seEk2t)nI;$L1eSQ|>5I6qMZZ2uju0KQJp zE=ey=X#G~hbD5vfJNt5un(wd@{pRibU>djNS5&(sf4Z%P+n zGsKtJkGVYFSpgBi?wEW@e%22lT)!?*H!0}~Acsg+)T zy*W#0w?#D=09dV7yQHBA+HLrF^<1J?Dt=`HbrZW}QR51J^ZS3+Ir)Iv8b{qg+H_%V z`h)}@sEU*P#DQYYG;ulmIVO3dzV+03aM4|`1O<7^a7#BNxv~Tgdfb&|Mn+b5BeAy33`34l6w~2 zjiSC^l$%F1GLX?5xkgE2Dd>dxaaG4($^ZD&lc(GBN%0+VMI_JLXYKDK4u!AYL5%Ca z1Ho$d?>_-(SkK!NJd8_Qj6&X8^aXB@9b<+DrT2ShAI}L3-;Tq!=Ft}r{L7!d=gT9@a6pHJ^mN7$Nk!zRwGwx)Ny5sG2)u8 zyN2_Aw5j}c@t?op=(C?4!ncqCUS5P6fF$_0xo8`Pll6=5-oGzxwI~E zkia$o+^^?SYKREdJ4l)leazKZ5`z5y`$6f+ZRi@fVu|1*GKf1r`k?C=4Rtsq;DJ+t ztEf*!o<8E&fo4UII%;SYXPE|Z6u+MxGfsg+_`P!KRmB{l*Y=CKkKr4el;F5N4+<(Z z02p-rKft(W4wC#K9Qmv_0n;)(aUiddE%6@vXLW2LdbR^Z-zqF1`fh#;X&;uj5_4eH zk=*bcMDk_kmaTTb#_d6R0v9;zP&C`4dLhA_wR#%`98SR%uSCS=k@ZO-&;&e^nACsy*0Ysuidj+*EA!Yqf=3YtlUs5 zp^WI<m3f^70hC?UN8ZdzW2 zU7^Ii!oCmH6Kkaa0`FU9YvZ=m0@uK&ru;RM7hG;fy7?fEhOZM8Tw=;F_x+U*BN6FC z^7HFhO-~4Yt)1**)LdPTj%Jp&5Y7$Gz%cfy2TDpxA=^p>liFUY|E3(7XHJF;{tD<@uRIRt+$sPIEP= zlvV_?!(8|!@NJ$s9lxIu+&aEYgb;9K$ir&W2YRn;ASNB4om z01LnV|MLxkGnbMb zJps^P9t=DU?T9mJ0j|~%hS^TZG9w% zQwz!$)mtPJoN%;4os`s|gcMko7iVH`nm4KtPl)`tlWHpFCCLrG$%pXPKB6uESGYnS zF~1wo8vuk)?b`JC-5qyn5<1rGc95CF`n^*!y|(5d4e5*7G0zgrs21XzJOQ~TFkYjO z9v9eJLNr1)q`sVi0EWPA&1=b@fma59kCfH@|Cj}t)o%zLhhPavib?^}$r8{`z6@~N zTRk2+C)LIC=&)ZKR@vOx5?hlMhGrnoM@vnq8U7z#*Bw{${{K%xh{mNtQ-qQtrKnVB zD7z&sqm-t$h6Y8~Xi2Fwj8JLsZRD1A(a;p_NmKjxe0Q(=b?^1_=Y8DA>738!{eI2o zfOT?+ZGFND=@&R%h&}~BRc+G!AdZak1xO1tWZT7)^^1C24z5VBn8hOy8%wPyT!kne zdTZ_mY%j^Z7Ubg-0gw|Jht{C(vBo4gdqt=8;a`0){@b}zCr|p(o(2A?j=(j>Fx^E- zm{irWonl{L-*w0!lqb}|XmH;k;O&ngj7f-F>ml}mDt=DT&W^GSeS}-aZmZ72qAPq{ z2vhU(p-*}F_gX49S@c56>eys95uY&}qH|q)oU7q~2o;K+C!*R};JoaZ2&p>)1s{>m zD;`fP_V-OQnH-vmRz)tUPB?qAIjilXuH#C|w=c63k)PjgFY3(BGYzpGDXYFluLxJO z@kGdf#K8*aBxc_4LAGcbLjfW;F5`BbWToW{YVe8x!@rZyOK~WruQ`H?7ylK*W^-X>fH8ve$i7(PGQTu z8>h{I>k5zMl~!+7xVH3=T{92U_Vt;C`M{fLYZ|a$%(5GbgG*8jJo|hdrMa}2%IMjA z#s}`MVClTi{S^F=&z@D5a$amvh@5h!;W=xCT>v|1o*|lmNCDNpJ}r0t&O*|M)rHgB zLwT&+wi4d(0%6-+bt_mJ#R(>&do)g`_2>7wmr^ax8rU6mgjLrWOz^s>FRZg%Ct(ZG zwcm$uFd$dd_1f-T?QyC^ohpf#@|)5t?_)CzXsLa{0JHG3*d>&L-_R2+yM@gm*q%pu zM<|~}{(Hf0@87>pHDNKg3GPQPAlR7SwmY-hW9|4RZ9rZ39_d@T116eT(#jSUZ8WO! zLu<2MN~gC~cdCOW%HY_=776%c7D-$n{TM6;2ooHZqqQJuJ*J{w`n7S#xdRuvuGMNJ z9u8`1V!eF$sQl%%`@3~VWD1(}`H$&;pQ6F~#={B3&Q10SDHPy6-sV7L^8 z3-eSfozar8RLqvKQ-)8odVYrp(cf>hKmhSX4xNfFq|Is()Ky?#K+rFMCafw+qi6Sv z%h=zLEWdZ!L{}A!G|ULAjpGkBXDglmL*W{Y(pe+FY8&kLdH|TOh9HkM1hN@D`NeO7L!coYcwcZnIe9S%eChN&$Bhg(X{tm%_4gOv=fY*ci2N+=$HYzwrRL zC;}mxR@F@UfZPDALVEL_5f&MVTwc%~nb2zV++@3)vPT9n{8atBZX$W-ka8o&0rdjy zf3nS;*_SO3uL<(&J+zvStd9i>48k9%22-^QMDexE1v!kzd){=Dylr z8KVt#fQe6frK&muJ9fxbEZvq z&BAOE#_6ITB`B9y4J;^k{;#Rae5%B9v3gSv$_fwKNKO~H!A5%T`+P_`j*!R40DH=Q z_-eZzVnS%btU!`=ly&Qsyh#=vpJ-Luiha~yugAm(cU7tjqj#cy`K;M33_Kw2yBPAuPMl4fyBYBk2U$xSW5)kKn&_EU!&^ zi^>gRUa}XzzOqmAR4b>DsCwg#3v1wkrw)0JN7#ww9X`K|B96Y1;RfS8DybKezx!kV(&NFRbLp|fsY2Fl2`u2aCtRh~OvHTH z#l3Pu5jIY^Q4J$P$6&M<6@6g~z$NyyYe^z7!oW$PQ8&{v(ky#m_W-(U2o9aV^U-QF zRlN~=w;laM%Gozd)BAx1Yr8tgtd>T1Pb2Y!6Hl?G{I&ps%ky^%F$7P|2F6M8R8Zmw>ri~IAQiX02{&v=jmE$J5s}3w#F~icDOm~NQaoX2pDSr;5${7HbR8aj8osrqr`Pp#VYtLfc zO*eeS5N=b4Q(Y&7cwVneCzkfT5FfbISo%CS{e%F2kx5tSxZP}=#HweC?rHAMrhou=`6MwyVc;K8*=8{)5^c?+r zlkqBqn{BWfk(VS4xHaJ%+0En4dEtbe{-!XEj+RLC&?bbymiHj0fojOKZ-&#UzApX8 zGy4P4;sXZ4qHLE@ZK4%B<}tfgBVh(}jBW9w9_3VAmEkT+T_hS(e2+@1p2mP^#*)=- zgJog$B1>koX@qBj+sXda2GNyXzUDbvlW?mxxPPm*y?=uweaf_ zMn1Ka>S_(a^ow^>?94k$*j#eXy@N4GDwZxPOr_Ks-AxTDw5B1jm+eCdV{tF-8YGm6 zRq>LB*xaKfd?Afj)T$c!yjf!`!!#!gPCf} z7D>k?eF79#gjr`AJ4*YdKGQ-|x64b=rv*Y$QB-v5fLu)}^|s@U{yJikYvF|whkKL3 zA83rNKyl8U;}$O+WUjlZ?v_FhR*oaG4|`fj`A4djr&{1dxclwuIp}l$e)s+}X%+t}{YvW)OimAC ziYlf`!qD6kL0$a9G=fPjXIQayW|gRdX@k2#J{6Z}q$xGdj{SD6jN_;nas{&cg@`^V z-*H#56_%Ng>>uvWtIX6+FL6E2!?2ua1y+`vPOyVf1{h|5fU3M|P2qRn2WXXOwO6b$ zQ_g}pI0r$l%4`p`EeIn6F|>g}ap&D8fJu-hLd2xnBQ%rZ`n2$M%U|$^t!9o$fB7AN z)#fqS#qCn-To4*?&zuwioyZOj#2vW4Xu<9pu5dO+5)pF?h-Wb17^;kr! zf;MNH|89nF-n$$zeVD&Q^k3yyyBhpkGMB1dgmeZNeGPVogA+=Mntszyrs;?BXE)b8 z-yk`u=Vi+x4O5icDf&v^({Kn*_Ef9A6B#@2exPA@0z_X(&)WCk{5{Pfh^I)R(`~cg z!L_YIa!c;_M}mfQ?Y|k*9Oy9-gR#Ae>N`S**UtjQtHb8i5*9Yd&TRrfGUr(nxPh8Gd)v4n7O{b&v7MF#tg~c%ErTgSc3NkMn=L|u( ze#XDLylaVci#%xyh}`$X%EM^o@12=nPDP>r?>K*EI{w3B`KgJrkS(bu?^e69C4VZ% z9;|=SHgsiq@Z@;wFD;kRARTiOr1HE;@ge`ysh@wR_Hhgl419XncQ9+4L}SVGRsFye z#2C(j7ZznV)UqHT2MB;imkia6!9YV1`yD>jtD0uzY)Wx*7dZLUR+?q*JpY0}`O&NW zS@R8c_uf3-i!RSlH9dAJt<{TNT;;{7L?2EoV1Q0651#0&6I3nA!FZmOl3F(&p}Bji zS#udge!`o7lP8GyWXT?k-aqKKPxLE;i5DLB0?Nf>xIm|?F0@%XuDXwm=b;}0*~4Q@ zLV}uUFNk7``jUL?a0f+#YEj17yk5S=z2lqP@doMkRRp3q5WXDPaqAHzWFAAh|LSoU zF$K*saCQ`17qRm9gKA4=+Q2Z^!YEl~ho|D?!!);m{5`YQWW(x4^Ez#s;uBdG5_zHQ ziKHXy#z;DLOXo@%Uxfmqo0nvN^>h>-MVMx zk3!wGe$9^3?~Jd-`o(g35OFc3d`1MrQD$ea@B};lH;kE}xgVbgc`&b{0~F~jJ{2Fc zoGMXs5AE%jIS%2R*Fq>w+tJ94xC4h6EOmXRQ8Vzew-#!s%PtO!1K$AwuV$_038EUS zDzIBE@iKItWthTdkTr>@hH|$s@ol;?>N`5s=PPxYXre4#x%N{h(Glj0TJ-hF z%QG3Oi-%BB7Bwa?UY;R7hH|%xw$B-LZr;)KV4ZmS9Bw^#hWbe~0JwuYMe`xxg`;v5 zOAUq0e0*c9oX%fu2v7~0NH+D*1Snf2LfXy0PmO7xR>kihp*()PZL}%wIz1P3fd4Kj zT)#&fnyGg2i_qx3L1`SVX_oo643PgfSs(m9F@7!HR^1h@r2LdfK3%ctQ}mV^3`w7v zh*tG=DaGbn3o9M#SGm9x)3Bdh?3(j((Mw{wS3HJ|eow|1FIs!?TIbG+aH$T5GZA~M z5gh`A%uOf3INhb~jFs+sVZ-AE6ETo3iz;s;r5^JAo{lLj#a54|Es_^Go$FvzFBQ|j z*0!&9dq>ut=cDMWE;u@p{t#Fu<+x*^dqv)N#42X9dus>XVO~nn;QHrp<2Y_Zx9n|_ zeu-A)7hI6I*MpK1O{;yW4XHpp7%h|f=B^UMT(R1<8=YrQlT{?{e^=P8nJ-NIr;c;N z$S;Nu4QeZCjXYPGOW7WrOl&hkEdELTFX|O5r|j%{o%zPB2K?@XwN0-scG|zZ>8PX2d={duM%+C(IjIMpSVr5igWRvxR+Vi;SD<1GgT#D1McF)cO(wXH#8gDG9H5Wk$ z2$rz7jO!0k70Ji^LwU(mvX$1011iuqKu@b?cLijsxJQ?{XhXUVVp1}SZaD^Lm)AR4 zzl52kw&G|}OY{>OuQftII*Uf?-z(j&XBNt|l%C_k<9w=RX{!#lyvY}#xO8 zaG#i^rkrE{&)n_m&CX@iF8A`0b9(Q?X9g9!zft$pr8Kuz7eUBugrpn2Dr66QpG)If zl$i5*qqp9S1#9`pOUT9@C!*Ui1;(mZVyPJznBU%d#wk?s;yt&|f!cA6=O3SrHptM@ zYu5CR^7$FuNKK!k+Xs`7J@ShmZj3~m6uigkDXmDOlfz9G!NKSUg4$UhRKJvOhvc1N zOzL{xYYGae(S8f2LOsnlffY(Ue& zEPvMa%J;S#W|^gndXOAE93{9!8>QPNC;h0UYFs5=@0%gc;g$sU6Lo#h-x6sSpTI*a zp~DV&Kn7P+!z zcXRiA@s8zwG1_44D;0t1>y4OY#`m=1GdtJkB31di9KU>^>QP?U>WfE0dmSGZdSVxx z<#UlMX3$(ll8Xb_oCD|xZR5|!nCQf5?NN<7DT_;W;rfPAg@f_ds zsig;_qO^vS3bZv@{B^gln@@6tfX?)1iX80 zMsZCnbPC(Qnzb(Z?2&r`?NtBM2MHBtraAOh`)KAgEOl6S=s0QO4kv^O?f&(28SHo; zkKaG7LpG~ieN3V)dWuXO2=aii=U|wg2LVT!0HJVwbO{Y?sn6UzfF$3BtQm4C1MJQLwu+wXn2bjZ~oHcF=9YYQ1P7I0XGab`$7FAVJip|1YiYWy_@*KXs zwPPNo+fBk)2kLz`8~{)%nm1;{&&5&a1(n0gbf^MeBcU62KCN2c5LBRI#I{3WbUApKwUKa2|rth&Ob14`= z4pUsWfttdw=O@Uow8^7KnOih$hIy0%o@A9RYDZtBC<_0Vv$C{7nl???FGgmWBL|sf zw}IGsC)HGQXU&VC7iZtR&bmk`vRS^o`tC8sn&^(fFLdjLi|(9B#C<>ka727puijEU z7}~fRz^qso@5@rPY+%~$%5@5b`_{kO21z2B>d+Zv;PqTD?Ed}dK*U_gXn|@iDVZ`S z2{Cb(LAiC`lB9@CQz^F1M@ygWv5MZNo44uCS__4@w{GYsQy6ybn^ifTvUS+|eCRdu z!bmiPcS|+KT`}$L?L|^MhMCFhJB`VElfktkm}P%JX&Z;W{}{gUo$)v-*DY%ZDxyeS za%m$B}XVCgx7j9?gAP-pOXkT82!ej|r@6Q?3U-<#w0B+hfp(s!=oXCp*dRzRNj>CU^ za8;}k&*v75vHW_cP`N%Njwu;4M9&tjzY)rWtad*oVLYC)(f7 zOil`$oI2smLgmwC-OZ&M#~*u|r`g_M@BvL2?2jCchsHsBJ?RY1+!g^Z_H zK3wXuqs!UByL+Yl(Tyh<(V zTC3bNXA2Cn>{~Ot6hVvZoF2cIW_D-B zK0>vS4Qb6wYVNY=IaxemAj{d(1~w8yCd;7IBZzMCA%@Se$XqbRBg(t?&>W^tw%6% zRKq!`))p9;g2QcDt+R#~1mh!n-bb>UKUuZ-(<)en0ver~iJNiy`PShsw^}oAc5yYp z@;2klcFW&w{1CDmcm6DSft}4wbw*PiTM;Q1=d@yu1bAm&Z@n z`Lq1ojM}hpgQMFrUagEv(S2knmsO%s)D-<3Jp=#y=&p-|bFPp!-WM78WZf<&*)gc{ zYlcuWtHQYuSS!ac^8gWwK&2n?tu&i9<0)5Vj~-dNBc$yS;Y7P^0|7 zxW<`4+h()$Y12KLb1+WGOn&1@3==4ngDZc^j+C7b|K@Brcfi80Ss!ZYyP8{mi}4J9 zH-7Eg`(xD+GI^6*!hDWO>})?mk#36C)pZ!xxLCTjr$504~!febcj%$*>lC>71XM zSqj%?;fvMnp2Zy(XHlOA+h@EmLys@i+=AC|Qrn#DP^Zz*jpolEa-2}?3f3~8E zQ2&>Z3?c(N2hX2o=n6KDJF$QN{A+0 zLb0!qnKszZ&_(r)+j-)DIa4~!60_nbS^MyAwx3Gisxc*V8?2s)Vl<&XtTQv7o0$xj zbhLfiSW%pCw)(XP>3}>L$KPC9_%g}*w}hRY+GBQ}<{1V?5UAE3&*^fpFDnUT0kujz zk(9NAr`C81l$IaD3+=?mrWmHOvhvWIOto&0fJKZlYh*ega~T(x!*RU-kWDUf-%m-3 z;`)FTjC{}>=uIOsQ3P#}iA2#Y077tZo2|)YOk5UUjVaSMHS0!tEY7M6Ij1{yolfi> z%!p85;wk|{vdAo(NS)l@MoR<>5B6KUcx{ELiZu%{#%gE&0Ex?nST$$DGaM$6n6p4 z<@Mai_@O1OH&I`hBIa!agmY@gn)YD3dEb&=h~qM<9^7?TDqq;@sjx$e2Ve477ezYk zWR#zEXHfO4Ym=ic^K~bu$2+`>zMT3qLEHB$4h|FDZ44uJX{DorAhuDYn^j4N5<$Hw zi@nMN72#uo7)Nfy$jt6FVEOHgr~#B}H1-KDE*s%_zn#v$5{(aBE<^qMuqA&F!p-`c zE5&^;ct4)|^5hVoZl04C{TPMvQRZJ5(!~tC%o)U|h<3@+q!p>mT!daxzN zsV1c=c3wQtNl4EVA1~FkNy9cUCe8G2?}tORf83e0?zrbD>~y&*VQzO0BDN}|!bk$G znX7>3j;a8G3Sf%-7;qHc4I92R++-dkO>T4rHS5Q77^!ct*}MNi!p%=hRwx`gl-D=j zkRTm+gwu&D)hzbZ*)9eY$0LkP;{$9RDElpCQWh;)p*J-pXCmQ^EN?wB?&RxB^@%6N zOhcWWIYTQs6mdFqZy?~BAhVL%?`0!RQyWS~1KOgAXCBfg^`gg6#fK9;!k8_~F z*peG*W~swZ@^0QM8sT4KR$~;KWZ*#j9|>qRS~ov08|W>U>l;R3MI_ekGYLuulez*@ zgXGL5ulbdfqPy6;V{$0!0^{3om+R(@=;jVZ_o=;Yf~&x55fl(fA)H_xkvT?Bnu+!8 zKP!ig^n2PCFXYq2`*cAeXl62tXH{M;rEr;JzOUtVpfk}m0;S8U`2?u^|2j1@P84lC z*;flxdJ-sZylw(pSmPU|he)DlZlY4}Ie+niYIOjkZ@sE=7!vRDk3Snvd@{ zOTrW_ZFm_IuM@joWc=d$&8^EA+LEwJQ>hb&&Qh-f%0C=N^{gwXFK_JrNT>G*gOR8> z0$XWo)Zkiqb_5_2OSUh^;o}C&_&!ox2|ip^8f>$98#Z+z3qmWeU*{Dy23m%o!1>Dk zZvYr~xuVjuX9t(l<=r^NGmKiE_$cxr8y@e&oVSWxf6aGIPE%13mCc(>Z%H!hUMHwT zY&Ku4(#mu4of;2)n0MuF(^iJIa~@@jBI`a9qM2n)nW%Zo3!Fpo_X8p(^*i1VP*>4~ z0E&MAors-G3YNFu+zWikZhbkV9(M1Ty=0GCt9gDC8DoJwwXfo~&dGm(S=r8l;z7Sc z#;PJwF`V=c*Es}&8Uf0P)l4tW zuB8l0{H_hvHlT~vD@-F$cieijmU0=|HC$I|UWyx1d;0^JxbOXlVuHl> zeI5Th=~7H(N&%$0=D>Ey-QB&rAa6|rWzgcmg1;ALhZU`(-x&K ze;k5h$mEEDi}-E_*)wZr%=?!6dy#qoQ;^g7&bd#IhDIZOMP_R?jBPKaEf5kfd>4LK zl+p5z&9WWLLR+wuGDfY#O#+ zMQYJWQrx5E>3gb`BDaJU!8?GFZxLA*H+S?WN8FHn{saxZK}IfmFTJcQcwd2Z!hM2^3VFL8>qT(92 zGOznaQznH_M7(5s1a_T$?XFS><);U;p#(W0;rUawc8;E@%%UE}%u{Ua$ar5F{mMp5 zt)pTxS;LBSdnkH!hzg7dEpjj$OB@;3ht`Y zxyz^K?>uv2z3^7Y#-5*Zepc0XFQ-x`Uez*rFvxrNY0gE*#3|Zn5_;r#8x@j*)jKG@ zv^w7!Z^54~NL^IbLPfDZfVvg6$n9TFd%25W>oGGZ9{c?_HYfT-zEqUXwj+2Ds~T6HqH53AtfDQ`EI89`)M2aYll3c&3P{8V_aNn=`I*C@{c&vKX_WFJB%FCe!~DXZUuo`Ktjr=0rb<0wV?MqlLj8=98z= zE?!w1ms8CmWL~2P6RXN}84_Qx02R!T&g$9R3YyZX8xJef&E38tnnq>~09B%f(l3RS zUg_buOeiEXp$I5!+kXlR(^gcuG)V&=ZGCUBOKdf5ux_{4yU=w{diUSocfo5Dj~@R# zw+B$l(XmRtjjjs@DCkCPWf@p7y9^ZH-cmBp-`-Kd8Bn%}Lg)B-IoUdVJri@BW9lsL zU)ITWuM8o%&YQ03F^uV9H^`!P;k0PHRC=l-YhV}5BAs5`DgJ)X4I-e?7>1(flw2s| zF7bFQOQZt`N{F^^LtgnGLH^e0P?c-gT)X}wUw8s)8e}HZjq1g^@DaIPdp2)VSF~## z2yyAEVrq$BtTaQ`!v) zfyd>ZU{Fv!pj}W(9KuG9%Y>6_?rbqgi;9*1@0Y*|S`h`BGB_n#V+{}XUXt!@)@;aD zH)>SN8qDf9zeT=WagdZuPkrkf+GT(LCarG9(Tg-># zIk$t~)=|Q8J#;Ze&!|Z(>de(;rR(-=(&BJhno;B!-iu( zF?tbZ3ub_D%`0C~vOD%FU`Nx7x+tzq7O$6Ak5o)|-dO1y!2fP84RdKChjH;QZ$6?h z!vknX2~a>X?0c;hM7WnqY)+>8eVxs@9|!KwQxA%8+VQHUk(mvzlms$26*66v_YQ!o z%E*m1lt-P#+hQdH>DYW6#{A}HMTNZI15|Cd$6?fvXUi7(P8o*mKPMOeki6A=Za8>R zE!1}A`!};Zu!{FI)b@YO9(6>gd+T?$D1ZHTUa&kZBSI zH&eJ4cN8DmDEGZH+(jo?RB+}-JPIi+6z+y_iWse_@UC04m5Yh@x6@nX@Cfvxq-$?c zq-!Ax`2l<3Ux!?HH;4KAZ<-wo#5nQ&rhBR(yPH$a%eR@B4~;iQrW!|LrS|GtB=7-3 z`Y&dSC-bV|^On?~=2qL&1wQ6I>{`7`^Tzr=Dq~_2hYe;7@Onp=@H?$ER~2c^7X5zo z_D?Zsml5UcGzBT1?^pONMnr*$O~jhW+=@PvcjpPS;wS({jXr!WGynKGCOSl!6qEcQ zj#*XkJ}Q@-(7Lx>QwUBNLy$&E;i93(_e3Y^I-v@|BK+D0YPl#_Dpm%=V$U&3>@s?A5tcVeuT* zg+C#G|L?=|Ig5AtiiZC%cfe_<$zB*?!vJ!5-y%LEL%~fB;6y8Y6g=0@2H$ zMz#**1a>3Myp2-vU7N?I z8?0$TZRbqDi7%%C!YvsJEN=sFDVvWzSQ~bSd;FK5hyg`+S9qPCaWhW(-0YQ+vJfSd zL3Zfie9xPXvzjY(lqi)19Kwz!ZlF^s%4i>W&o?|u#ONRQi*_7AX#S;Lq+^n+h2aOZ(j-x zjB~rf?t+tdlO?dlL8&-gsy}-vrDzF@ww~LiD5ILMrVz{zhKG94pJhS)SFnd}{rSbz zxn@EZf06}h)5z@0368K|u-O+a3&bL@=LV0~&uWY-e zz0>6q71tmCzBW|u*};=!`n_(~Rox)~@vlrju^o6~9QUrd>)OH}OnJrUsNpGJY83jWrc)ELj2>hG;iUx*NcRd&kn8;&tt_~}dhcss83 ziN=9oIJmTNW%Iv0cG_M8f@;}0pk7>~3)W=#meogy@hrNBwU^+yhl911Kj*iCX;?em zcFte<3AC&8%N8y1!UGG0^rMEuNZBEW?uX9jTn8myIN7%%@wg_mXXddZ_9IzsVWJo3$QhUfAiyqw@JZb zd7NqiOz_JcD&zU6q!iEx$NM+vh0z=V(o{PP8xB77)1Q|t{HdeyHO6=k?vVLc3Kt6GRdJ(t*PsUeL7`EZs7}ff>I4L(I@uXaQdvjy@L|;6su*=+x z;S4W6NLuTk%>0w66n6cXL0{A9s?+5uq3JByVl&A03Vm9E``u%(&F~-YqHo;|$YCrd zyVuRHkq_ed$I9~>@@ay_7dfzm>dNfOH|+$dEGj;KEMO=JixX)oco!_m6`*kKTXMZr z)I5NkfPH(X8>eCGI*P^NbJr^iCeuZ049f7~fBy4iM_zP?3n(A_6}O*GIgag}kX9VD z7tM7Oq~RHBIZZ?HlnlSJ83FJ^S}b@h9~-fpDKsnzPz~OB{yLHCy)?vq?u2Dhig~!X z*qXjbe6p(#o+Se_i4hoW{}M{hh?e&l+9&Hk!kLhGgmr~}l@YZ!4sF^Ihxsc#pKnDf z39h=ribAZi(OfDLg)gIUMNGfPw>R{8R*HQu4Fl1t3g&KVwpLD=ptE26qXs5zK_iU< z=6zzp6p3553wio)O>dl8MVdwk^j^+oA>=?I8&GXNGKM%J!HQoH4CQSTG1+?Mn+Je+5c$5Y=9Y}}Mi~15Sv}8S*PssCdV+g)p}hJ0<42J%@D?RK z@$Afc*GYxZ2+e{~Bt`Hsd1o*+U&|NVMYhjY`8-cpgpVos6yH;F;XAqM%k=)B{++Mg z7NTzUDv6#=Jyr5oMg()toPAbVI|R6E_Jh4P(J>}o64)MpMKGgO9c1m6#BS`b<4Iwd zmKnLd|A6N_#{{wzqLt-?rKBEZost$OzkTg5l1Gva^@$;O;DdueXV z_iuTDayVBOg|e4us3;bZXI}NkH$Il9x0}Q!$QWb!wnpBAf0Z~le=2d#y~&?fJank1 zRbF-^Llg}Vv|DtaJ+QoKR2RoS>hR@{Nr7yeTTOZRo^{occ_#$-pMVpkh-rfgSU}Zc z^E9i5mo99^+?jsbj1^qh&fZVIYJ=G(VC6~#w6hlHmOnjGjb3AtL5-Snx!}C_p|L(C zdZ`Dq(VakHteE%O;P7X^qW1H9VYMAB{f^CBVJaCX!MoLBVVvTS3%#Oty(k2v)2Yh_ zxhPx$p)Pqt!*A#zy|7<%0~-P|^BmLt z{q+PtHMMMPm-#!H_7f$SKmR3aSLK|M!T!6ch1LBXDj|UFIbq#-zygZ#;zJ5GF(cL# z%IkRahJ2WwfVmtE9H^D&h^4HROFc=R8^|1j9}+}R&~(ZC^;Pwxrg`$%%dc83r<{L> z`~3-wYmJ~@fw@I608=k=T*+PSh}@k3H2|D5Vs+42^afn?z7NYTXRt4JJzllcVZpG# zqPud#f*5|%uUurgKO=k(G!883j1kMkBKyJ}VaMGx5?%`r&&VV%C3Klkf=!Hdg=DoK z&&@*%@i#uzP10fXQXozp_l{r|y;>QIu5y^gwV$<*C4U(RsAKSx`0>Cn0=Vhndy8r0 z2ov9>OP|pUPYVOTFWQQIUPN68cqjckdy*@j$ll`4xJGfke`0}pGhUnOY(&cpfvxRK z{P!o%KOq{-F&M3!22hik|5OC#P#ZS5Juf>$T9TS$1QJb{$HI#`Tk>`EW12_YvpS= z9q+pEz4O4V+0U=nMKy}PUy;i~dPVQONa;a;O7M!XCYKesW< zvN3J!BV(=+?E?z8k3HB5LuN$$n&+=pQ&X0;tUiWQq@?S_*l@xa&c=X~JjSmu2E3ka zIOlW?h=M4zk-Rhh(RL zWo!jA|NUxSWReqXDGc&)17`O167rI-9P-j6?$+JMxvQl!h=zZMdpRIHho6Nx@L{I?*FP?nQamm2 zr@hG21;U@Gk;ue5esUcx$t+PK#LbjsH;P1aqTQ*1Ewcz*ZjUSJ&tYoJ2U zoqCJ%S?dLQ0S1JEg)n@I4f8k577{~Dex)($S)cjd?&8^1F(!G4E)JidcFtIoW z2sA(okp$CtUB#`IFGzDq!zpl_4R?qu358KFvZ2k8!ms?6`1nyEt`U&eZ8fe)0P9m6~GN=`w#;$Nnv! z|BbvjDMP3(SerIy-FrsHv0*=#@l|$TBQQyrnqU<6W(A*fy_9oK$PmVrm3>EoH?fOx z)kJL!b+TM+#{dQmhHFYVM7A}10nsV^h7WUn_A`a0Uu_p@bvp6wh%5jQY36(<9=T(^ zzIxPQj!ie+jLn%MeHlwJt1d-qn$z;nD{BWx-Nh#9AbgAf86@ufRB}8r?<&7N^!Sl| z(?VemnLKqY1+Shqn6^bEbwY-hvqb#WNiq)yl8e9rlP`Fr4Ps*%ji-4LsBNs*t~{}2 z#0PuY|8(scfVc8?M61OsGZMF9(;r)o1zq$ z%o*26Vgo+rEC0_G8!0%*4$TmPj(l)I1HFvp&r)ditTeJ?Btl5!*tMyNR{Tyf7F+n8 z%gFDHkxsFrNVoVvd*Utn6qc=)T7UL7VqA20~)T5j(O*Prh9R{Fe^% zG1D{HR;L6VscxuZ-PG<-NIWA^W#9yi`8wyIwSaPt~vX#d6bTsP#NY=S>*SqtLNDh$7xB zDD;+|b@}r4Hr&xa`vOLUw_uer)0G)fsq)-Nts z&0=Bk^Y%QenEuTa+t?3D`i6Z>f;w7|GCdq+s8ae3}0rw;svva()eTHL~|_ z|32GSL`Dcge1Q&qY$E02{1UW6MXO-O0PmADQ1nbyvaBz{H2M%gvbR6PZFum4MpI6; zY}ei{vY^QNsT$y#dTz)uf$K)PkaBrW40jKS7%PtT%3kWCP{jHh%_EEN5pA~i_D}Rm z&~5!Na4G&d+8Zm0FS76adRn1`@>e5|>$C7rdKB|(0oW7C5f?1jDaWJ4L5q`Nk*8EL zpiy-Kc;5kxv|Q%z!33$ZjMlW7L((Cm1bBdf%V_`ot8GL^!1>VxFKqc# z)ZF*;tut+l5`pN`N<4QGbbv{_)>Wp>z?^6UqgjidZVWV{gL?A}7w~zm^9DmlKlN>` zaQr6a*Rjgn_0{hBGt9{jjD8v`7cX+BoLwHm9w1%4IrpQPRdN&jLK=nN9ckQT`rgqz zM7T`q5X*D+bJS;*_H!*?u3wncu`1+>ZmAbbj^cbpsZ5f7Uy*#;6D#YMp^1?_rH19V zFV8A=J*%qPRk2w5*zvz`@Y0h#hAr{5Wd`LBG=_J2OEM=;v@p_evRQmMkt-%fssDpX zeNBMW?8Sq2>+c%a4Q2p?J3NMsVo2RwQ&mp=+dCABb#&ZF8KqfSS$Q?~vPQZz3U}BR zpWICC=KoHbOJB%`KB`F*~&l|K|%SgHNthjGQxP3R>q(3u@Hu;wy6!6L-?>Y4^Ddve;)>yL)bH zXn3p~WG(!RWvq*BalNY04$0IGj=#> z0HB*F!6!G{Zt*R8)}o8U52$oS4ft6*Hl~M`#X8n zwHUumxU}YefySTT^Qfrb#fNfsg~48^Wj#l(s$rhdz04()?xh|8t&xAy@0`bz!)1+) zjoGY9ckZO-va-MOyTK-xB+c95cIJ^Bsa4YhV0c)LZSn=MO#V2 z7QCTn$qKP6TzG?!(P$RGcD}9$*R^sZ4*H+pjmI9-zc1~^WnJaX)nz$P$w#bP z6)%{Rkl;L4(=2X_&gg`?ItwJLZo|sux%waSf;PexaZbh;cji(kJSYE(02Kdra=yuV zfJ`qFt}vf{ZQf|4r_^+^C%tp!LF(gs;xZm3%)C3sz1xg`HUm+%5ZW_A@fAV4o(0Ou z$6E`VZ`(~niP&TlIk2=cH8?m}46>DtbyO!e()<+!$k}oi#@~ofrS7}55J|iH`qWeesIcOVJi() zmeSIv&z`2@U(+?$ur&1g_l5KorwgH3_WQfLTS5#Mo{?)PTn)?%pxJf~tK)B#2k75I zkgx^IcR11q3Y1_2p|hBHHZF5@g^~%AZ;UZ68mm zsmJ~Gc&?6DJSml?>N@Kz3K$s++3Rw@fU?Q~O0{!dn2pG@*SFc$eXH`8)3Q8N@Xs{tOUu;uW z8yM?37Sg>!2#3tco?IcPQqy$VnQnQ;@EJW@$ARbTNw`hbKXd+kEsxOWk?^6pMCdhZ z5zT>DJxnm~f+VI5@5}($t@(%(xTMbkD6qc)51|BSJZ#%x4VQ zEt@mv&{lP!VA*Qz4d}C((pwyi^Y%fp-Ot;x>llqM`$(R;d1w3F$0~K4>g8NfY8=v0H{H<1#ae9!|V&>aZX4m3?<||)@ zOE$)3MLQO1hq13=tkZEcbw0sNp{T6>5hwfuy*impAszMYmhQ}>+{zr4p$|{xYr9ix zb$O_sUm6nLTtQo=si`D22t|1>2pWij1wBR#lO0W+D3s%LE!BSxj3aWah2stLXF3GW zgP(5k5@GegfdhvMP3jpe-mLN%(@1u(bD~r#dvso-k>}TBPP}xsr*PHU9b6=yIb8vo zEpIKp7}Gz$Sc`bDK(n7<)S{ z5?P0FX>QlNEjPyF;`|XJr0shQbrON)W&!`v%JHFn6x}|tbKgFDVmf6sEBl+PGkg$N zl@I{NjFOPsyw5p4_YogSa5WFy3__W9I#?In5|o>ns$(<=(`c~xD50lamOnQZUx=Lh z`5N-KIsLtpGmZJOO%BFwTD!6n5>11yJe(hz7rD`&VpJ~U%I^DD_JaUsL3YcSK=SfU zN_3=7kS@m26k4Ycz9yKS)9g`6KkR$U0s)_}_z+MZJQEqnEqB|q-ZcIBX>xl+3_eB3 zFb3+Z|JFPt*JO39$D=a{GKTzu!bF(#3lxU#RB623+acoOTyV@eX?A?0H-DkwbMBf4 z*C^uL)bGE@+5CC1{ncwVl6GmN3h5_9wkiWkCJnS@N@E>@^8LAX1@tcUx2{pU>I}aX zl#`e;EKtvH$o)bGi3CGKLr>^acYbdRy1ccbW?Bybnd4zkwm4r<;mn+9Yg+J%W@3tgDx&bngNzZWH%W1wL8$L0 z`XlQ1ycIajZ!5}9p`<;oWb0=9sQMB2`5ouSz0yYi3`6(hR$Q?b7K|YhBf2{1t|wy? zmA-|v9?Tg(jS$SZC7exF3H6u~FLk!2w-e;3I!hL^FS_NHb!#HuS3S`2J4v_51fgC2 zAryMazk3?5a4J5UG-d3JgU7AYETDq|lNp&-v?aF$Zf;H7%}(JfzrKo*MQ1H3T4AO> zMJ#1?iFIaGL&JJv5M)m0d$JpeE}`T{?mWIPMvckksQgIMWb=?S3xp9)_k($7VflOP zJ5jUSm!IvHyinHL{EbT59N^B+c=5Hdx9&@3I~MAY`yte4eK?3@mu|>M6vyPRg8a2$B zK;FM9V0zZtz>RI}pdENYm`gyH2q7wk1^(lQ*V61`*m>zp!D|mjr2v0tEn88a zUFBjMb?a(!_KOD_q@Vqm@GxGU8jk70o%*HKyY9Xfuw6`vpcaj#b~uRV1YYdEfj&x<@3p_~C@Jl$ z&);O4w61aU(|KQAU9Dq?M00qTwmZt^fRjtZ9L)*_v*jhGO$zXaEqbFO*+tJ4c+Omn zHIB>NJY!o3 zeJ?yUlDU_z>b?7Lk#zX?#{4^Lg*JUgLXq&$89^!uI?hTUQVq%m@?dgJ(J#R`^?(4t z#RGRX|Ln7){SFBnX;%moMWKvn<$1aM$urR7w?-<1cZj&OmA%HX-XH2qj_3lZYsk}`;$Q_5j$a0uP8fd?<1LKuPovxtf31WC6C-LL~q(+yPC1EsQmrtTc zv^sP)LZD|}m}4x%uWREs@crr?Ul%FGS~;gomIO_^t`VOw)v-%8U!QRia*{#PmSUH} zkYsE`P38_p@X>RwRk1eNKaeQzwc<|Y20iP8cp|%}M)d;S8i3}6@&_5)FD&Sm!c^Ud z;Mb3~sGe&YrFxWD>J=(d^o z$g3`_`{GvqA-AKGRxos<5yh!q*cM}rfkijs6@u*5)U7EVH`mK1Y<}$>^7ojZlsmoX z-G>h!&edamA=9NEJ}bleLonjloKt59{oUYSJZ?{4XX7(nblH?~ji3D%JlxJh$36&K zlRP*A4EeN2y!Gmf&q|ps3Jd!R#TOLCB=k2~u}j@dBwBrxY+0LS(jVapyp%j*az+hK z3AAGxJJx1Tz&SZlQ)|7HRC-_1cahjC$S*eOP`q zY7lOC_x7c{fX36wHIx0GC?nTWEZT|FPQi7*Gj2Ot?*A@=XxpMRFz8RYpRVgq#Yn)T8|$-j?;6|X#5f|_I%^G+?Ek)V{XX)==sqmZT$m(&!dG$V z?c=z(*e<=qlC`PCyQ4Ry-a^@Q~h{>ADK{F2LM3!FIv{wW2t^ z26Kryuk*E2?cU$i^$fB7%TnhXi0P;3gkYkY;|2M;vFi@M-XeqyHQ9A0j^z`(Er={(P&l;=4tx>M3-xYD)r+wtN+txp+u&a>16xY_z zs9_}??NS=;Q7FhHUtO{Jtjaw=25my~RBFFMO&&NId~bOd$WDMuar4x{-0^Jw!D@HG zL6mNn$@RRwWpl=csV!woEpKLp$G2-gprdGA8Z;i!YazktJvQ;sP_@In=;Es0NB_J5 zA1I!=lSxp%t*AiE5w7^cXz=8g1?;DM_@fZK!%sdlIr5>hGLPJrn?9zd1zr`R$Uobs zKA#a5p&Y**vO%Wov{P5aV1$(G2DkBy<~(eF=LeiT(DmTm-D8I&6e*ON4bK(qmsfTI zWV((r^trnWz_L6z&m_NYg*(i_yPlQ!N&gRRx_9WpJ~wm3O@P!2yxI_A$Oz7Mmp)8oGO zSv|V{6l+kjIbGXnUDub*=pSs8a!-4G?KI2=b?vsfcRsk%f>OW-oWnCq#v8e8-t$Rv zo_h;1=+J_l!$tp3RZ2^3+PE|r)9twrRe7N~x9W-0sWYrF_H5sh+`?R30)WV9QTcox zpEYcta&yak>RYaSQRc{@3^{xAgA5}AzUT?uKg9l`H!Il!@AGdv#2Ptt9uppYIUg|3 zx&WPGe*TV*drROGq;}xmcIgWjE)3OgpKh1SDe(4^gU#N@2^O8oGWPW`#a6_@%VX=T z@om@Sw37B0H^&<%Z9nKR0(xx2E~wc=*2(Di2sAjk_+S z@@AzDplaZSi)mTM#BU2EYd&^Jxb$`FH)Xz>^yIhjIT(EEmJ3zHIbLFJ#-#SMzK&71 zKgKsr>xy2#_IPYuR7QKA5i364&EohlgLjM32Q@P@^M=d&b7K-x-aI}Mk(GaAD+8qr zHH0;#n{rWG018vp@zK<>p`3oh-J?w1aIvw*6g4AbYC?|L#g79kP4B5cy2)F~3^hz# zczjo-LI-b~#Z*lBJ?dL9FvbSGrHyunVs3%`O^?0w)NDi zE(Kb)A>GnWIuFyO!=uo9qm@|T-5=bz(+2oB-Dz7@?{yD-4Vb+wr@FahGXY+_0eDf} zX0{>2@Kif&fUl#>iRsMF7qmYuU{n4Jegw9z(*xf93MdrD+Cf%U7N5=RVa$QMD27P?kN@FR23$qF z`<-EC{sz*gq0!#0im7q_8e+H@Ia78v=IM1>Y9kkCPkpfrO6l>EYQsOST@p%EEnIP= zuY=co#hsAm6f2>R_KP}rbsmM{Tx6&P{A!swsK#_tS(Rfp(#YTqu@XA6T6CkRX!P_n z?SVG$XU&a$N=2n_X)_rlQYDF)9J>e{LI2g^IZob zA8a1{rsvSLr0x4xO%DyM%n0HohRO^lJ0F%qm4D6;1L{jBbi-MOy9?bJz1N0m5_oR| zx(45?nY*vj7@S5V{nNz4=f!0AB59*y+qEI;1_`%%S+}yN)BXuAYo!#MgRZT=!K<^~ z_i@9u8XR{sx9auyn4ZHlF5In;IcB}x11sekF70}^a3n)*U@G1(YhK}#;bhmF0lF&& zn}K+idD~z(q(QJh%+skVZwj?<->pFo_;ljo9`)gpx${1$wvWWacb2MWq4TNAKP5-W z*tY8Td3*zl=Tj>CNfi|~^~hOCDEW>1T}=396qN=5!}{^UsgUQIw_!#dzU!~C+^b85 zW9%p2!y~^%irFI7g52cBkl^0ur)xT#z?w|x)5;sUoq)@g(eO^OXN|Pu&?h(7RdxGv zwjSR$*2@)c>>)%Vz6W z9NCb-xV=T+c)U+)#k+uCYjeblUiO;xjaik7%E&xqBrq-%(=M=dMT=g>9y^=ju9N>` zj@i=fzL)SdCNM4==cX`>qBkJ6)updDTybaGeJcK={dJ2lb%PT{I<}$a73ujxiH>cB zK8!+|kZYrH$g?CL%i3&hWv%2u=V=MY?osKE|*Lb7`>$?wPlTj z3JN*BYSk*-M|`LC{oT4RA-Th8G#utHPX7J10E`xsq#L%L{2 z)E;mP|p>H=Bu7$&hMN4Xxq$>|2B)i?ON}ZT9MSD>C zM4{F`Mkb8t)Jb#YXs)XH*ad%fax__#eeNy(wlCfa z2~U<^8S3wFAJuZNWJcD3Gg^is9*v3m$lG2YY=?Abmx`1Vk$WCEE);I67TsG~)tFE5 z2(fPS_uM($n}1=0XS+F8Gz@`l5u?)gx-jaSCDT@e;GYaaCFa_r(_E;7QzUMcx=q+iX`51P$KA zL3)Bj0+(szK9uae@g;$JLRRnh@(MFAVB1t$G*t(s~e{w}E{I$^m}U17^g6_ULLQqM3esH!lrOM6()#whQE231w{Uh~># zg+!GL50N_NAOch?Soq#i)&S7|^APzD7K$f4v&;be&2ZpRs?Mo)r<3DNKf}N0A-8HfcDQLlV-)_Wwt%Y;jZ8hYG+xi7=MPRS zFW(?oQiR&ekv`O7@eG@fQlEkiQEjIjK45=J=?sR`2Bxlstb&Yf^OG!$kM@UP?zUXx z!HzVtzUB1+gT{j7_)m>i$hvkD+cWZ@s>Dm>2+SK2meX_zE8k&X)2H#kQB$^B(?r=U zK-)op0?NKP+ zt*fh>YHWnvyn@PjtI4$o-;z=uROH+Lr5EenuqT0Ynw98uPa&bGW#(Frx3jFx$C)=C(b5wPL$2){6G8o<|ghZT+zg6f_q2;{AZFMoj;``+FmLV zTNc!Y|1reWgtmR?ZLh_Uj%4}9JnWSSZa%k0TqdV0GWdXnp1rJ;-8)eVrEh7_Ed}_~ zo`<|q(5Q6)5W&9C5TdI{RAyZ75+rpgPSfjNPIs3FMM#uI=HQ#DWz}HV3Rv5YdtpBk z(`TvlWdl%@Y_>oz`bq8k$|*qF)~64i#*v~=qMvm#wON`)ORQ1#rBI$7GdYu&q_-!< z4W93Uy8Z6(zPFi}&||jgwNRO@RJ#dQL45QyI=Oe}>Y->_2~&-x0zVE|v3I>@!?D<& zH5mp?3KE+8`;0eSoD7$#MTnrP|JZ4>s}NvRt9zh5kf;^?n7dO zV36)l^9NQ{RZV=pLKR>GEerG}c4ALb+H0mS_G?X%Dyr4FnQ`XXvu8G)Mod&@5&3V( z6c>fErs`eh$?Cm!b!2CF4#=!@U6UrG&cnBOD7v*EXOC%YSQ^9v_&rq{=D$r(yD=Y9 zU47tIR(;Q_-?%d@StYy2Wd)&`vI_zxO8owM8v{s*^Pi-v0R?Yuj zs+$LclXNod2ur1e#@Dg0u{b$$XT(Il`z9M>iRYqb7dRIzO+23VVyuoWG7Yse~eceXFCj#B+fvB(?1CwpQspbk# zzZ1?AL;7EiLCqBhk0TDI;}HAJa7GC~&x-Zu94?hZd_g@XP2ou*Wb;5C0^rio z(NXYt4pzCMzPlF+VL5P`l7bNBu^kWf^oz4S`}GRF689;zW2&|Y@~*Pbxbn;dt`e=d z?m-qGMdkSslTvPob0y(LuDFHsH%9Kob2;0{4jh3UtJ#3j87R8mOPe^=}CWL2dNS_P7TiDF~rCD>td77(GR)!U!1+?fbH+AH!VUee`%l+{s$ zI0Q29g-fwjj|v8QSjT|!X!pNK*SC@ipI_3xU%>T!hqhl?Rgu!T>|aW-oNV?X%`}Jp zl3$l$YN7LhUVJxuX^0=8YtL3EQT_FtOmnHz$8t|-`GW+4#nX1U@D1l=K$;?!J>;J- zk{jrK4XEDc9VCuNs;^yNe!j`6M*_U>!LQ%K3NSN|!w=|76?s(>M}NX3k}edF*v1qX zEiXv-7hUF7AO`L(?5%?=j;kagC;QSO-NZlZ>eJyEG#u6lp@C1%gm=z*y2hGbl|-qR zZ?5yM+O~V)N&~L0ZJf6dkpd!|^K!~3%|aWYP9!y*J8o>Yq_y2 zNx^%mSIK^|BqbNPHV8w$#7iG{E@`e8EZI`)kKeL1Vs`R0x*Tb>H8R+uuo~v$Egs7aZJEajMjMD9+Z7w zIOM~gaGD;Qva5nW-XXGcZ>?~ktu#tSgtD&6k8@N`n2F1>MiWFs-Lpqpu_GsGZ0B%5 zK){)my83MF+LUW%7VbV5k^W=_JDSH)7ksCpC*OIWwnSsSY{i+(7bC5N*2qkzgXddC zg0&^Lv>+P_mK|8nDQHyuYhPHMZ@Bb&QJ-gfL}Aclx8Y!3>}4k8`0mrLwnPE7auuR(X*>}oE?fz2F>Lu=E~2Y<^&pQxvAz7p1?c5Mz!{8$>)zD z)YJ?9U4~H{z))gq9A0fg{~LKmz%<2~i5a;06Jax;Lj_NTFR5zS1UT!ru7_hu5BpD^c0BucHfkpsK#@QI5o zlPcw^7inU-CG${}3|n$(fqFiu>S+r7>!)83{g)oI*O=;!VW`R9fG;e=c~H_vSRw>} zM-Ms=r-kd=WG;Ga6#2Kp%pg!{2+?T-(weBZ}_$J#x#7))wII*H96QSfqDF65*>| zd(rnJ-_1l4b?f`;&r(v}fbb6ZuZEJHJ4my0D`UZ_Q>P&6U<+)m%stzajqWm!z9q`o zrwpR2yC*00@I(722z0Cn(N1n;?Nyd|xeljh6q;3ZjAWgNk#EL=Rz2TG(G7s&2BC)l zJR07jv^M2{ZmML<=hvcY*S)|f6XbtQNA40)?(O$4g)}abgFpbWv&@}2?0`kY&Xyi{ zrhm-JFegKO+Or^vgSU-fg$yU*`)g$A%FX^7RrWXYlK}6py|S z<~cu}?N}(JSo2}d-dq0`BP3O5#%*La(0JF`i5cV>E zu02H046a)&8=dPL>}G8R<#!F}=XTU@VeS&#N+adlk@j1OHV*Xwm2k}YqP_D2B(J~d zihQMOn`HeirR}XBFLml%nz}l54ZFH{_Tib)fc!81+FuJ7Vg)(;>|r<9%UON?D}*|p zyM%YXKzX%j_c(?He*&lLC^k%F3x6(Jr8yM%b5Hc-s|CEy1C3(4zV~<(canEdinUn! z+|Jw%YEJ7kxQyj`tjTKh{u63@!`uNA58_NIBf*sA;da@(zN~DavbI1AmeQCN=R5MT{I7y4&wxi)#2fEjapc?!qP=f8NvWHS6A0F$Is~K1WP4zN8ZKcTEOY! z+w3jQITmj4DxPycVbkg5gFLLC23btK-TU`fGbFKk7m5DJWk1Juv<+TTu3h`-R<(nf z=O;dwMv3&m+ezifY~tzE(d8RiD*oNRlT`&Hgf#7iXZzr^Swo_=GZKD|Q!J zl3J5mlgoB3U6)nk*yXd4Ift@1l3n*e!Gktl$=%>T`W11(l4OmB-{%K6Xvp6+3fY^? z9idcNFmhE)M95|H!<20;uNEA1v}f~Dm#P;Eu9UVmIyW7S03gPoPiK#3Nui2~(+Fd| z;?Mqx;2=W_%97fsQ?IGH05&ok-D(GGJKoj0%;(>N5sG7a``@pfEkx%#?XHq;Lyube z|Mb})eH)7zvA4QxtN8+=?kxrqt>HTPXy$$BIvSp%SA?m>>QoP=_4=&QI&GC_r`itv zj=?>kBy=b6QfE%W?&^aJofNlH+sU%B;pvnB*HG9GT)tv2zc(e^QH%eVoxE&~!#`T^ zNzFukgu1o1G9_GvNu?3yAqRK>zql~YT@lp0ELMSzkp^Yr#4xqFEnyv{{NR9_J^CD8 z%j^|aMG}HxMQ(4TFvs#b1yzKCkjv-4CI(p3IUT~J5JUh@VPp~*CR;CsmBJrp7$#?` z8)CdHGL6)MfZP0-|S|JCLH6pZsb*xI072htr{nA-7HbAu0pt27Ygd6~6 zxP2g#G$YCG%P61GUO^}g*>&X!ok&CMR2f8b9raq#kXqeQ63h>YQ^|t2*k-(fEY^y$ z-&A10Fl1OdE8pM?G7h6^vumBDr0*lIP96Cm`zJ9Pk=jx3<8%S^(2=hWf%dK0C3cL` zDHfk8qu*PDhtC@q=V?yh{>+j0`vu1s5R}~G_ccyx0}*s50pT^Hv!is3@y^!`S_*6k zz2nX_U<=od{S$Ga*}I?~av;^;yo~UyYwcxVeMysY-yy!eepG5L<*D_$L+re4=_>v( zGV*C(V9u1`71yMUz>Cg)wQaRLLp1C*x44qwf6)vC4e&cL+nSWVjzjx&by2Z4e*J)@ z1xvi^j)+yn;lrFqA^U1M*mSaG1K;{I-X|YqyDr1Q8I!92gN)Z>jv%$P|4h>Jl>Rmy z;@PARjDp~Fx#$d<+B$MD<|XfjxAK{4)3#Kp)m#tR)O3Gt*)u&z6 z|9yc?{Z6v8^+l0JBoJd`B3&2olQJ&pF6F;w&c16;ja@r;@j`OvG8)4RhqQsu4MNVn z@4BQ3nl)8{mWQCNzJMw)$k1hzWA2M-N!>N(xwO*M9up0@0y5C??+KNw@7`rTK8jF7 znrEVNAAXe-@AXc>PA23hY^PJ#^tO8k2iBxH6r};z0r5=<=^LRd|4T5`83luaQhR5( zZ>hr7Y_O+;7pu6kXy(4cG6s9r93&zUi(|9N-*9OVzc_R6o+lv(unhf<6puphQz999 zS5`eZvD21*RZOin!_s#lKLrKzsQ&9eRTalh;u>#JP^^aLr%PJa|}#;R5^YB`AzMe;Ym#ld`FK zy8n|HRcA{Z{_Ek?MK#q?SZXaXMy9*NxR4c|Z@)h}_DXC_8WAb{WZRheN|T-WcLB_DS^I4FKHz3|PKPvO(A*9b@4PECP^ z!~tL2(CFmkrD7%I8-|-VkZmPQm!N+!7I#i$gN4_ZOsq+iKI+C z^!^XBM)_nlD_ktaCS;zmC%5V^oYK+J$#7qOJmuR4IW6`)2aWFZiQOKH6Vxbe@V4W8 zV|tEnt4~pSNU%HQu}xISvcdXhwW&%yRTp{@FT8`Vxldeirx9p-5TjsypC_zZ-?Z7Q z7YNnnnO$6)p;Jtq+C>I>Y?@NgLecPF$G)c=e-vRDf8~UU#)*yN3jnL*bzcl52o7SQ zNg-Fb-NRd59&>Vh(H1OoXQcA#P5hSdBjtFy`*bzrr)$u6B@cVhf`VR+L7bF}-Dp3t zhd7A56svpk0XPqJBnjO@@1fx`@mkntrm{cYc4d8FNRBL$o4NjzrxkL z2Kq>Z`RR~I_O!Wm#Z^&XMHwPInW#$5=7ZCMgsD%GMr17$HGpMt7(4* zw|FNj+}yoM?~KXI#&zOCVOz~KG5$mK!uGQ_pf&dtyLsFK%K@IdFAkgT zHE#7~cKWu!@YsvUOhOLgdxIq9>MkB7*~mj-LL@#pi&Y3SH$RM6+j8f;2>14vxnpbC zyvRUzvQt_x+)s=jOrY#dn& zVJhY}6(nWL*2`YWX3wt?^pCAr{4OaaY6a= zmMlOom-~G#W%Y_LEt7(L(7-cxbVDWUSm5R-QiXzKV;bO>b`KTf~8hb;LrJ4r0_|u+LK6Wzaih;MR1U8?e_zSOSs^ ziO|6_(0WAkue46qq@IF>AkV-Jf-IbM8h!fQbmAAd{ZW>c?hYj359Pui4zQu;N7*&Lq)Y8*+35#8E8jW^#6RIqA(Vi&C z_g$Q;Y6bRj5QKl7@TKA{8#G6DSzwhGedzx0++J7#wlu__^JN?Wj{JMC@g#%iP^Kz|KL&ce$x z_m`BE&@aRDLv`HLT*5{MLQmS=Ev~*}>o{S-Cm?{PrPnu=Hl8g9KvaYb+vZFam5$$) z?V6xXm^Roqw+;>!g$!(Qve|9kEwRB?HfsJ%px`R`Rfvd74`Ho9wi_&|f=_%q7myk5 z$gi6Yb%SKxxd#wH=gQ@*0F!_0WYjnZqq8?qZ9o6UCacUoN5{dY-)qXMBP5SnR_ISI zxhbJ|R&zS*M{Xe^Gxu?=Sz27UV*+1L>JbBOAZ>&$Ykc_Sy0-SEik>2>8i6s zQm)}A<%Z+a_pbx(E4(GLCTt|MpxUUo<~b{${T8##!seoKVAoDMBjTJlc;7Pi)QPDb z9%m?E0bb~|dK~SOB`o_Scidtkyv@Fo0P28f^CMm^8DGcqY zs9VK>^OZ42it!}g@kV}W#C~IH{U9Aj9s{@ni+QM1mo~vuMMOiQ`Y=OJeK^U;`~x9&kth55yA=i03z<0SWu)`2On-OnQ6lMMVwyuFo~j1D&7Wb`QIcXS~XG z|2$tXtlmtjIVww)4HCB5=J9#9!GnP5f)SVO(~x%y`$P+OMc>G*QT2PeXy#A<>-!(n z&ODZABOecmr%IyKz`sY=Sodel9eqq#ZLIuB@K;_{X{ zc9q&PR#1@^up6ooy0?@M)-tvDYfUcbLY4>91e_Fc2M|(ZzuhoI@H_#Jvti0-ihC3NS>;P9Z07ar_ zJ9WBi3@Rq~1oxJ!`9)h^?28KDp~T^XyZ)gmJVL~g$KT(wm1TY8!N~AAMh65c31qGE zjz24@A8?9Ryr)~&hS&jx0g}MEE5{$hsIH?3olIXS<9F(M8(G(=WrCDFxj0vJuGuju&$(+V-^U~*G+XFsnuP9{>_l~%Exd$sn7MP9AZhp}Fqp2O998Zz zpIpiHcW23f$7d_#O|_e#>Tz{y_Qk*Ci2AJmus=b2NurHueh1OrV#-FTXzMek&X>ld zg>!!1TGZ09M>NOvTN8^;1+EftU-ma9zH7{we%|*wG%ce2S#k<`HAAOSq7g#Djm5a^ z^Yk~sbeJCEeSL-BtF%5~z)34jU5)zlztGDu?`}+N6|cLoiG3Glr+05Blq;I0r0J`k z(^VfHBJKhPXPX_{{NuyIrsqE&R{m{z^ zCJi%(z6|wHYmMAr=TC05(TDi^I(rx*g-n)6xc0=+63-u8ML8aWv0n3>9oAgFKH239 zM8Bh~-pWon@82&bSI{IWs*&OZ-xr4=}~ z@!hJ^VaK>19%-;^cw+p){*)>MI$7xx)~~N%3F6>uVK-&BiCq=s*r+srfpQ!dV61IE zWw9KFt_%H@7&}g)j2ft33SvmJRkgU}#Qa!^!SE|`gqHDwr6 zJAM=sSPaxaJkf<7qQp#_L)o*-e0B$!%6H`Kb@D+dr+vf`G6tK5rtwHMds)j17rf@8 znK5`C63AqZdoK7nc{DE2eUKl=I9QD0VUG^NDHoF-WQ4`yztitMCh<3n6=o};`hy?6 z_&i6uDqML2IE9>ea=GFyz97q(2M>OhlEr>)U7I6eSje0;ax`~GhYhnKO1W+7w%|X9 zSqW(EIC?;wwD1kqSiTQO5dg}L{tkBe8SFW)Q zJBKmcUlqHdVY5-s&fx=^CK^BT%P-5=zBq-Q6<&_K;Q5*J{IU+I58vFRowP=;AK*Ji zv7PN5EZ*LrAt3-IZ<_dS4rTg;9dF&lAaAgoy5hvy{0`y_6R~r6v6RgwJF7Jmm4`S( zN)OSOE|WTQ@4qcw*EYeD{qGwZ8cgMeQB3YG4(1=s8lW@9<=8c%W+Wt=n;_k59qYX( z(xW&@iu3(aS;qqsPuzR3OZ0;G?E?Vne*Ee_6{N=+@Zc_eZKz=r?9;wh8#(vgPV0?V zI1#Jj+6tKMnCG}+(S{X@_V%9ipq%i?b-{N^#g3|5@0+RrzBGyBT_)N4J==S#)fJfz zyUoljU92b`%>FH5S5XAeq+CrIv!6e1skm%4L0aa1At96nOY9%R+5|ol+1c5zt;d^% zLEY7kHBFMosI9B};E28P%9SgJ457|x=8nF39Tqco>`Ly7rPBb)rz@~zeB#g(m#Pni zrB%(jE4B_6%&AvHFXuXU^~|Ll_w4N#gFV0*llXADCOZXOolOBwVLt5&AF4{GFtkAf z^k#d?1hsS4P~{v-^h+MMpQJHB7&fil;n_}x(K4~sy=}$m2sWUz=1UXlDjyVh+C&-s zabKwO=f7TEo*WTk6$0+iuX#%I7f0;Jw?&*}K)8h^$jQkC8ej6Coduogw(%g(T;_mU z$5KgLM@~Lmd~Jm%K0DH3i2YW}!qD9$Yc8(zPZw3uf(ialL@dqt)}VlVt@udiZ0dd5 z7x1e+OHsQfEG%soJCL=H1OFD%D!RH5GOee2|gaq*$ zaG8Gkn%leXRCOZ7^y_*063aWZ92R3IRDOTE^t4S3uG=CK6dbGo*jxiFL!eKhj(%7!u*c^sOE8NSn^(6a zhix_0CF6_auFX^jI+|3sma9PT4+e{LH$7Px{p3kOMo_a|y?Nrc^~Vuo9~X=p z&j6b;7k~^%$r9w{bAxTQDpW)O7>-8%HHUISw8td0Kt4$95WCy?=SQ5zYK>cAokb8b z2<-*leN3YdFDUA&DB7|OxCnSz+I->O%yPwym*(Ia{GRCxmQ=@HH8Jh#d57w7)9hV8 ztOjizT8z)j6&)@`dA4Iv+Q$!<{9U-O8nI=8nkLa!i4G?feMBC@V8BpNbxqt!(ucr8 z>+!JMKw|{R zwJ$>zD17dZVr!Hbur&9Eh7ICS8=?;u3}qx!y&c9gM12(EFG2`1`6OldO0QE&^%-wS znqK7-#GDiY#9qar<*FFQyty~_Y@b7Ue38_Gepx><(bu&1z*ozwuI|*91#49Q{cQH6 zPPNPNLSKa?0j#N=yiTuzn!6s|wcXO365i>jzD(%&?9)QsBXD^S5qma3jG%4Im%@Xh z2KC2c)X$DunC>ojWtW+RRKK6^<^fEYs#oU5d%}eK>+fSvDjUXx7_bGr)`v4D8myn$ zBp9<5O)_i{^b2qM)MNapenAP*5?)@!Gb!=SqifqBE3B+YzbmC_f>}mS?nl7lk!A`G zEulPJqCA#Znkq-Ip;+SWoqn{wEtwP4p2on&%f-1x5ROPXWc->9dVX#|TbTO!@%SI^ZN!xWZX?`bgm@dj%v zQJt z%$Bv=uxxyL@6*>qwz@z{I=#LmNH{*}E(UpphK`&A&DIvbXrvC#(ObF{Awu@ zbVt(JEMT@M! zs;eUuuh|O|j+M%ZTE5c?{Xe3UAwVl#|PzPE(npEeg$79op}bjJQV)(mNdYrNmz1)6yQ3 z>~p&SKX`mO(MJ+}ZfN^Cng=QQ5rM!IiM3c-(SP_>4hk>K^A17f_gJ~4gx7sCiWjtc zfH`KREHRV24(b|Mf#vz#`}o?vUw<#!sYmpFct(*I&`CcHEl;JZHe13l)w{4hI)(wq zqPRE$Wn2@~c{Arb$_eAzB$}mb%%$<$+iF!U&2>vi|5va9BteY#THubrhuFW&qv=@e zo~W2tp}S|(-4xw3!UFB)pCrKTyWL$Z)zHyD&0g z{{6dWgipKwAvSI|eeB@D2Ql>Pa%v6EmbruZ_Pr*dx<90Xe_Rd$(VFv}<-M!q(c+AF zuly`FpH~84OSA(f8H-G&tGgY3uprk5yLz@8X7x zdbzazZZbg>Af+b+Tlwpcq6e<<_@eNCFBR~=9Y3roO*`xKOmH6i0p1@+p!my+Pkcy! zIQD*z$6RicW@VGZ@P^q9VG{Cv_Rn)Xnl{LmUDP!R<@0`SyH)S$*5PQtrG?qxYmiw(SEtX5)uj5+AYQ z?6tEsQ+Yo7RQ1(sCiqOiB-J8QT7Tk_9v9`lr65Kyr5!&1O*$|X1)+P)CcQ&ki2Ult zEW$!@{TnNXuabdWy)a^6IfiJ1@sc;bzRTrG1HYqmDj{r2n`!2ryP|rr?s&~Mn5oINS(IE?bZEr z&qiz(S$Ql)zDtjzv;QI^c>f$}WjWN-pkbAT96$LH%cbmToQe1sA^@%NgkCg+|BrI? zb9W3H!4oITN`HY%SFEuuAE^il3tbAAk0c*nibvt&E0yTl$;+e3?}Vf)iy@e?D@7tX z!+?9`?c!qNyf!FOfKifK@IrHFT$8K)A617bDtZN6MLbH+F8S3@kE>@6xO(;x3GM?r z@(g*hbWqQhcG9x1PIhwNjAnT)^hFewtJ0N9rOR|tCBpfb9p z%uu&v31F9dQk?E0-PUmBPZ_(};jZ%Id|=^QsEl48(tL*RIDGHhESBhG+fm=s4ga%! z7pm&zgDeqM4weBnKNh((Y$G0Bd2QqII<6AzYcC+O^n;2N%4|#1+3M4-EjL^m9lRLU zPzec^&8W0;F-@Z_SpE5tk>)(qpAd!!80T{1m~MM5_`~I|3oEm z_*2B(5q%O*sXmpIT|%xlX=?K*k9yAwi#O=D1(~G`TwrtA@Sb7+Fl913+;JxXqojxj z8El0vG?Q?TTh+F51?5H(xOmjbEltj;7s-8oSv7kz$`x#6%Mg3k>uCgUi+ert%vM$l z|HJLqgZcacI{ZPnP3`+hetP@iE+_0zoWL6ka+P3=SK-6oQhH}ja(=)npD$p4z*3F+ zq~KEg7CqpZ9RS50(69rW$?2VM5%rreLk8&P_glCmu_ zXIR4_h5N=zcHuq?k^>D;tZrz?1Vrw+KorqttWi$CVSDqT>FaE;D<)r=v9$YfnA66V zU+0U!#HT*x&^1AYZS0~zKMnGw-IYa>^IY2e`%=s9hL_*dn7MwW*FjVUJQj3twKn^R z&01LRdl>Er)xeGb1M{7!?6#q$P+ZimxpDlc`x&G;bUAF!AGILcG;l)N0HUxd?98(=S|wC#mlKlK2svlNP|0OpIy;5-qC zKxz^@*@3!~;z3|n&JQbS#&w^OKE%gXZ=_lVQ*)jww$LtL=c1fNH%!CCPJc$iXDf7? zY!qP<5i8m~nvEOo`%-6#4*IlPX48#`S{k=4)|0-CjcUXL)wn2ac_#Pc6?K=%3Z0a# zva*kkaZ<`ZN`1kr_Og1J4p!M%2D`HtG9(l}hr-UVf7q%w(bG`#82petN8h}8V?!+W zz)gMm(@K^Vku=+$oBbx)v$7oS=i>wlAsv|iSqvCiwQ@yS9z0kNupR|WG$FJ9H74K) zHzRjRKc99C=%736t6#b}hv z#OgKHK``j<9`ghIc%S!&|;=nl>SG6)umpJIlg9;IcN`pB<~!KjEsb;S^1|E z`#}ot{e~+l+n*UuC+<#|^}Gt{Uq1G8)MaNqc<`A*dAvqlIE2qf6;_)cF(>i>EQPIX z!UfW~lgY$RRQhpbB)hE#Q!mEQp4|Bl7iIzjc)LJtMcJ`HxZu!8-4-smXp@uitS zVgK9%<}JhR6U0|Ip)6=?QSi6iS?}ShJO+#lYOh|H5gwT}kL51Wp5v4Yo&j3r74!2Y zIl$-Wq%bSIHJ=pO1o*>{ngE%wM$4>xvl~dEWPpHDx|I4Z>I@(750HB}POv0ZQa0z~ zpR@&%w24n*McX$6vtA@9FYB|th31k4F_cpW!SxQ<$qAu>nzd~8IJ1$H{J{U7!ycrr zd6bQ1?IpZWujcA84UrE$&CyKKFT+ss$aA|7NPVl%&8E=%^RYv6BjI>lTw%A=hwFr^ z5F5D;PRZ9MFlD$*N|Jsn3SV*XsW`Wt;w+ zYp*7OarBy?5?y-2th4+THG2#B*Q;NOTP55>Rkp6@-*>5Dd&MHk@yL{&!_NI1HgNy~ z;)HEVm2+5MwR!g>+70xStF*_deI! z4WLAkInfHh`8S6$=cXkmOh3J4TgkDfD-oF5gjVqF`EpA}YItryDEFVV*LfOtxz6gt zaSKXd20+6^*Y@u9TeKD#5We7KBe8R;g$o3- z{P7#yO=-ZG+LI~%X=2cmD{|u~u`eZiF=j}f=a$rozh6P{^SWGs@n>}exXC4Zc)Uyk*`qUB}5e;OYBXcM=WiZNy?Wo#mXY-G(NIx!5(DkgA zn7wG7+HrAv?PT?^zmkl=V|LlN;;D<{M@PIN6?ZGvG@0X=8VVFt2$BN{VUL77f^UMI zcqt>X7;`r<-3uBHTeQM4KY%%!B~bU4q)MDV`8cB)3sJ0E3zc?KJl}9(p0p6!Tp1R9R6t^H|IF7t)TZw7^afm9;r9mDARhhu0mva&U z-sD5ae_lHU{mhzKZsWdj9CTr68~aTl06C)vONqX}=ChfNt*F=gi!4en|NX0{LNZi+ z#F-ghq0s&vZ@z8BDn~JniR6~Ve6G|8I-y=lEDM!nC>}}C;sY$9$IA$KiG8yF1up{^ z_n9oVX+8Fec4?+Iiy<)z)FDJyG@@%(m z$6WuG6OGtyv!fe&9SMnAIX69D8rO`G!}bLnSvq-CkOKcYS9Bgvd||B{;e@UO!bnd~ z->x4MJp|g>M?q5m)7vC&9?ID$u9D=*Zm@DL4TSf@Y|L^A?LphEUETU`2}Hj72%@J_ z)e}RohxAE{jJ{>i!x=5x1)R<7eXPzA8Z0;j zJ)*zW*%2-_-WwSnAKz;G8ytO2sMXpi)sOqfOVT#kS?uTR-y!JM>qlbdc^Plbexa$_^;)X1w-`zl&6NYLQ> z0>8iT^GJ>bBuF@D$XQeFQ|sMXuT-srw;ATOp}l3Rw%}fwy^`1zANSYiSKWwDNQOe$ zzs)1mAITmZ#B=*CmOb4hoIIXa`=kWi!DwtUomf4G^wHjjZ}s_)pkeu< zNmGhBDXrl@Q>(*+0ahWTRKITwlZF6@HAr~7Sa|B5))MmmQ;wxhufy$?e?|sf{)1@R zMTa{kO8QNoCcrodWHi}5ReYhr%31eB!#ObBW+fez&G32{cW2oPmh27qrv}Bu^g`Ns zpB-<$r48Ok{2?Ednv#;j{z>8On-LpC&bV&XAL*3gxp9VbY)1_EOR>!z%1_=h<&21M zf`mMb?ljT3`fW^)#c#`j0CccBCIOmsa{)j&f}zKd)sFvu?zIRka4^?RLlh#Oxh{;^ zIU+(+AK~)~iCLCGTTEF!hqS3ZHA!yMlP<0p3;VCi@zMT#VPQW$0Yi9^oYSbk{NZtQ z@QW0S_A7#RCl{7PC+zu+-6vO!42M}xzk5qiBt=YYJ8BI>E}M0P58dDHner1XoI2@y z*~<&yDz1G*OYDrtGd837MUL}+JSVL9e_Q3?CrR2sONFjSJM+u1-zk=ob?x`?YIZQZ zLB3IWHv2uJ|BU+LBI3bpdY2go>06UqwJvXJqo)6&hB+R8JWo+0(Gy=L5p$-*mSQbH@=I&<*6X`9b4_&H=83o zLZ0I35kH-5WQZ^+VV9@mdPXjV))@xcT?%iti*wl)GTaQ`&86f?Ub=*gVIrMAdi}`a#Y;JS(daM?65o!rM7{(VXZy^ClNwG1>5}GT3)e%>4)U+jC5icJ2Gvzc zFb6L5N5e%phZDbiQDEL@Bxy+eA6wTQSM&b=kL%JkyIqn{QHY9+rk1N^S6b4dp`;`l z(&D;ElWRnivf3(jv|SoTLupUiJMD3*^LxI}sqPoQ^QTA7_%EE{&@bD^dx|vp^c&ejqUPNY_}$GbJ=Fg<*J z-7b83AS2WIo*#vOUGXHMQDl^SqWmAY!d$AKcOdGv-$`-GZh@_7+pt7Qxr#uc|52S> zvaq!Da<%&O5as11d&X$HcFhTR9gK~G*VZpyHs^b~@Z;yY*50F8{ICKzbp%sujr=SI zEXIBA-MZ}SG8*^vl zGiu1XF_y%_BP0#;CHO(*a%mquSza4`O!VR8n^aV6cNit8#Owhb`0Flz;s0G$ZvAAP zkixR`Ox}HW5E`Q7Q1X#u1s$J8-Q_uyPmLWHr_`CX(9I~Lt1X%(WE)D0c4}n~_%IFz zQH*iTSbXGU8-TR;q&pmVrhe~5zaMdw$Ir=u!}NC2Vi=7>_2CUTK8+ z&UV<6Ox%k8fuFPY*)iQn0NyA7G6L@<_IZK7RcsUBL z873uT9?p2~FA3aA96EC z^hKq(GmHo5DD%lj$5OAGzKg_rAl|A$JtTtY)rjk71AHq;%t5M~>plf@5MAJ?A{%#am zZJCBXMjXoac7JZ5>}H?AFm@NVMNpnCT$s;8Z1xciym+;k&{FAduhU{&F|ImK01qHH zH{iobCS_~{i6Qi=K{zfk0>VQFgA?wBpL|P39LA zvF@T@ZPBm2YSvwqv1S4MLWxrNQim$0_{6*Gr$fv(GR(1zGk#&66xI_%5&zNeSqL|g zsjab9eZZm=!f1%r>Yla~(o-LWDU5N>!9uDGb{+(zS!ct`k5+GDXU7rT75X7&VbQ*8 zGZkUrKSV{^QW(NR#%72-%^__j_-orwy!jho7Xdw33$2&!^Cx7@XOImgU!S8Y#yApZ z1_mjmMQQsNo~vT6zF@rzH<=u$_{3WKk3GXjUVZ*}zOeg2`AuRIG&b)de~|4FdO%c6 zfv%^%#y%!Fp7bAZV}_y#=@AUgdiz*D4W^dOyJ6heJk+N%j99{KJsg*>2syo?G&mUax|NZ0E$ zGpbAqi6jB0Jbl;KDqigf-NSZR%QkpRc~rOu78Zwafgj77v%9^3eQ`SpAuortn3yp# zI70oB6W_KF4n_T-3v-IUABw0W$LWG)^pg#te_s>38#CQ0 zZZWKLJ`V*I5ni&1*Bx#7x2TnYcJ0-U_i1^6Mr%q^Z7z$_V(yx;u}GhmH2G-|ZNae< zBoZ;wvsNpf)ddoRZdH?D_9bw{p=D}EZlzqC4X#jKlwwBxZ#fx+~v9@0+ zZA)_3EK^fo27xmh9&u;-RwkJ~2)5M>?+&b0M46DVIqQ4O%lY|Y(-M(=7i8`qPfj{g;e~khi@%k+BLt6**b|S20I<8Qo;3QZ6to-@?4e{#cZ59 zeJCG&dv*hAUn>ip9`1cs&VMv<9W*q4XD$cQd_LSki2uajz~Q$mM0E%^EN-$9 z2ZFd@lP_A{L?hGcdU}yEk9YF<)*NNo{#)e0CGG$MoteybZc_q2u*yxDxWGWJ3bHb& z`EK_n4>iH4yMqWCUJuJoe0{U|xYn~;AZOMe7v|G~x+2~{m%MW4Mx!=WMU*$iwj%s~ z?02YVhLn0qQG~@zTqc^VANIT>uByZ&-3UEaU%s{L=P1ULu-k%UQ6Eml%D(VcJLA&_$GyyaSpiw7>Odjrf#8=buD^ok! z#zqb&8Nz4Z`A)y1gMB^Wj;Ot-f%n|vyT4LApxV+$(6K(+P&qx(I@w?({NzlE0>gr0 z>6)X(t#{d~3$J+HWGlVTCY5UK_TD{dao&_Z0$Jo(1C=Fw-%0qkO?76h+8J8qR&M_B zwdGBgQgcyZU{kM(^8`63{2C-H51cX2JdKXNC(?W2p8A(z|_B0H)jc=Q3zQ{hjGyK2=i=s+_lSobFYgCyv1xu3*~OzN?Ip zwJzpF8bhGh^Qdx{4{qfZl`aY8=Y}V6!SB;k;}_RlF~{6p5b zm(Q-Y#AZ%ioO*XKL{sy8 zyRECNo#I}X#mgOoi;0jSIxQb4UpZ<=AX)Qaq{bjU27`y?wIk#d5`O$H>S!GAimiG_ zZR<(gQ;rW;K&+W@ker)qkxRlNk@!X1c98Hs2=rYPM`YlTTy1!o;Gxi2=;I%V!{);- zdwsUC8ahZw!YS6nyUsWrI>})TMttVxlwOR_*$FwMdg;_?fp8YgmQdX*P*?Rxo^o={wu(BqYH{q09i*o%{cT#nd;128a#5OzDZL*VCdC`)DR+5alnDU&3jPqF| zxJvZ2hb*-{$S8HH4LO!)pirC&j5ktz^tJ>E0#nMUgzqfjjNc9i&ZC&DqBzuj8oibS zqPS;I6C-J{pIY%O_())wSuGcgE;=OeteN&>OP55&08*C2F4m%z=au>6?u4(-g8k7$}A6ZlnvYfc1IXAE|AH^x`*G|}ENSD0p6?r|e^>8fNeBst!;(-+Z<$Hf@W5KWnpFag(xGmq3cBPfCoIk@m={F$ zzgL=em#aT%jr6X<<=1~5KUux&Fz){A?ylO1ls^ncnhV^mM)Eb(l}ri~?bJT(*hZsw zOhdORM|f-j$45aC4{5NJ8}aq{zb5uyVBxUDbwlzRw@K1S#9nr9-QxQ*Um` zgp{r{p~nc#r3U9(jof8xH&rP6)wbScQT;MM{o(NYJ6l5c>y1> zZiQxEMXKaoS6AzyEqp1wd<#t!=!GOI2Odjy)ujuDvv4$sDi$t@Pvtb_8m=~R5UWb! zP7=3sO;S0BYCWSismuGb&~N)~-f!^BGLs#6Z~>`E)o+_fj3)71 zrzx8iC$n9XYcGZz(R`DkpMF9o#--YoVlP&oE_yB6?OPS~rQ?Sz({Aw?5-~DG_Nj1rjIFS+*L+Ql0j?% z-$gOLBw2cA&*_-VZiL0WB?&X5$u%TxCFJYf7uHYxr{7L@L2Pn-w{FvL&Vv-Suy_^K zxY$A?+DSKSz>NvIn|Xj)7TNoCwSPW9tJx76ZxAAr+*{^GDIDH1

d~c2CfLF3-qe0mFC@q1LljKn-J)*zwZ$=479`j=yBAws9b z`2OfODkqTu_yII z0n{&ao~)R7{CneQ8*(H zT)1+UeQiX0-_!q!u?t*sH5%$DpgI(HJ|YU*og z`h?e%wz(_@k1)XK4fK0FEqWv5iMwHNhpAtp~uxC2E#QY?M`(c=17$pKiYjJ zL~D{xYs*YL=9gQQ^6>dX4Gq<8fBuvA&)@4U=Uk>-{`H@O!+ACI&cKh07d%*?)03y~ z+B?!|HyPyA^T5e%YU5bbwgH*?1Q~AR@LeHz#f4LRN(xA^VJN4rauno`MC&ET+utML z@wIDVQmykS&(#;^x04!#`{gQ3W+fs@9sBw~-;YmWJU%x)b|Kxsfumx^AdhEsca~Hg z8w6om7f(_siDA*vTX@ixiiWAKoLHlUK@9=O+Wl%n&28;zA#=~LI3iAxr)VMBSb<|W zeaZkstEOY434Lmk0g3opu3jtp9nR$QD%IF~g};NnJ$Fg|M9ykrmEW~AmOXf-=AR6d zU`;reR6Je(4q???i9_x}m5%h)mNox@U9N!bJ*gQ((JayL3@1wsxr!FLz~N1{ZA{Rh z&jV!rh|)uO1wm2+Kl+ibQFF*$u*~AzqxfsmE*J{X6YVS(9b)`M$X5g(&PfR#k zgU;?jv_a6yXTiHVA1)fRcyqk<#T9+M8H{Vi)fT%b)lF*#Um@Sbu>Z6yeB`-HN+*!2 z=j`0FGeokhoRN`CVpdtYA!MW_G~Q?Wny8-dz?}T@)fV_9rl*71h)f5A_6O*4GNZ&# z*w@_&k`J=USv}xFQo{YAmZ0p82^ZN`L(r@W*s`yq89EQO^wg#{qT5;hO$Lt4Z0B1G zwsfYEY+#@&W4B4-aY<$`Ob#7f<3qaTVBtDG0(zsICj)UqDu2UY~^wHB+hwqFx*g$bJBM3PHn8Ju0CxxMrL{MHjBjG zq^31*A-e0dCL3g?XiU@;PS#wCO$TShx6pMs!8D|r6v?FBCIMGH^b;NQF*!Paubyj} zj#)N5M@i$~2M-;t{zNi%C+Nl&S|D?86VO)XVxYhk87@x5U=5u+JI~nOQ<*@pFNFz< z^!5b%$;E~*aKs#si>$#8KkF(txzK~P(Afi5C5R*t+CKy>2DnJFHhU(#-PO6wijP$x}|J1 z(o?(rRPp2cFo`kvTh`8^{ewEjAEAnrf4L@Gm|(xk#eoM*VRP?SIzHKyZ@ECM|gn`ksoX zzqis;!t=(4&|A2!xlc5tV-~(~ zv3*<7To~?>4WP z<%O?f87^$KwQSW?WptQ_wrH7|nYoD%a7ppr6BZUdE!X~df1#HJFJag3_9=tfq{gu| zdAq&k`L3EucK8YVqPo-^+0$WOaANwf5%}|Rd}WeVK6_cM3)<^slqpd}uOHLOQvm8x7ntaZ5hy%l-f&)yM)v;9q zGRy@|d7iUp00F?X71-{u!YnEqvY*6-xBug8X>IKrAr5LxSBy;MU3bVE2?WG=v8f5I zpe@SRh1hVczc`-p2xEuXqI?&wQ^@x@X*b9R* zeg*mTo0jv6Sr?@I9r6#*NRH7`6Xk)77_6-S`M6005%CU8(v-7P?4FnsEcDB${}m8N ztIk+lbhNBZ5rsNfH5J5OYo3A!koC{9mn0s3N&_Gs>>@_eB(Rm-m#yumP}cZndmd

QZ3;A{z^y1I%tmNc(c)bqip=UU8@!F-F(kyVSX zZb&pV&oItCTky94)7<^#a*u4IxZ>NF&gp`SnAe-ydqe??dKX+0&EOe@*QG!6;fG!p zt;Rm>ft1*XGiIwtCN7dz_=l+-Eq6dFlK^`H+D7Q7=)@I3%1G+U`q7-3hh@}V&FUI#CteS+dttD@&U0MvsfK3-1$zdEl|)2{;`oV~k|=|Ex;EEh za@;+WmTH4<;-)R7pMQeFs3Q)ls6XMSlw3E8TM2iv_WJpIy}#q@U9ojwO-CRT!Fn=Z=3__<*OwvD5j0!h{XsdKl2E%8#j9Fby+4hvDR9yyVph zaC?XYlX{Ix$vKpg+n&N(y{nV*-Q6Kko=Yj2HL_n5(p`Nz1p_|s$ka_;PV^-Q%D<^q8TuuWk2f zL9v6Ug6xn>#1+ySx$XvI*gLtxe`HX|!+j67f8|4)SMUvYM-B>6DAx{s{^qh=wHHQ4 zn(dph#=g6}HB*ypsrIF|$^$ctr80pJ?3n$9?#CFeF1vj+&!30NS0~-xxT{)xr&f%f zPP*q964{+68lR+&m~dzfdwZ@>6w=jduT-0k1w8QRXx`SuE93Dv$V;Lc{CWEr#6i?9xc>a#&c|yCMU%MlOyd_4alOr6dv5%dLYn`?RSc zxsdX6CZb$8{KnjPQAsbY4Ji%L=nR~5DJ9|Qo1qs=-VX0$-s6&xsD#Gj5P(`x5!i)` zs-NL9HMRxud2H25nisVwl%Y9A9GQBiIB z4yhAfcLfvT7Qz>1?KBhO>3$W;b5WOL+k$(BvAM}jc>po5t5!$4niA#1;>>JM z8a0CYY2vIyt7MJ73x(1#Oa1;(U*!B^=}k>I9o0En#huYV1rH<&<*l}PaFA~|bTirk zZ-WZCdtziM6g!2P=Rfz%i!(mgJ1IIF$TYbECZ5X|Uag>78(=A*9NzA7k(~55l^!`h zda-`Et8!WAc+6`eTxnB$|~Bx3CvI7uad0 zWLa~eZK}{^YlF7ylRtL;LivNSWHjyV=QG}a_aV;bZ;hA03OCiknlW$!fcPgwW^51> zw(HEHJmx#SQWmV2m7*3JmeomHaBBaq$q(Vx?|4uyDa_o5FBL<>N{dSz&%Fo@6d;pe z+(yGXnS_M%CvkC!=>g*cA4AAD+d#_}cXyjOQvU6_qAx93k&Os!yQkqOAv@%;)<~Rd z0aZACu-F{xn!!7iYj?lfw_FX!k7T(`C};K5KKUykD%DsF`b|yYBPiiy#c)&M{pRfS z_02*R4}S(rcK3&NmtN1dwpV<-Qxa%<8WH*!8#Ax0C~(en?M4o}O)ESBv8xPP|3L0} z_vgZ^?%R#``-ZEZHnIUdeSA^Ab!PfYl}D1w`x#yCozMIDO~v1FvaxxFrB5c5QeW-z z-$`NKV6#iKmCQqaw@as>2&}~ajMCMvBE+)e7q%GGWU`|_jLOKdXFQ>^%$fMBEn!Of zYtUGJe&)wbp)3x8nH+kLhW?$PA>~X-g=@psb}p^a6zpbigF`{PoVuH|{5CV3n7#15--@$wT4Je;!?_b z#Mv^Q5X!z4jHNWdtdgMA9dk+5GgAHuGi>7vJ4?H!t_hHhRK8r#b1zQ$k3D@jkz!hkWOGpQNb1 zLdzTl+wZGr0MC=I$=M0in8|8(IJU9{1+gvLcqb*m%-2%N3I3{2%vdPvj~}|)c_QFi z_ie^oQJxFah84T&xIm{i|S!Ot@_as0R{L6NP*RBPs5F=~2Zy=Ti2TgOsq6 z*sOzLxh`|5Z-e0(oveqiw$bQwBzxz=X&T}E`!L|Dk`s^7$RQLYY z!mt2bm0&0UWi5l(0Rv7Vb=rG|Hq5cYDA%XBG5i^?zyE$;fs)F~2fiHe^N!Nfc}L~g z67lWvb4kX&W3tJ)*QqkwE#%$SMP9?hthy!<{?pVVNX%E@=-_r4ESk7w^}|!XHzw8T z%T;(7hN=oQ#E2o&b4B4!O# zaDb$JSd{N{@z{VGm|2%PzV-WwHyg)YP@_w(rcmzArjo4(KeRG_14EWa3&%7|nGiur zPxLiRMVnh&$99=e2%y1aZmdOKj7!jV#0KSE82x%{Y&cYrJ$N~VA}k=1DWMfbYB<#; z`$qzqU;omsUh_I_BBogb3S`z5RwcBCPt4{~cAuP@a90Z(&JC8~&nkXWcx*YSNwe

(#qjbAF8)Evg750_f1ExC@M3-{&(R3?5GW)g&)nLINH;<=cWxFmydiFS{<<+kEWZ2N zcwss5MC&YzF2ZwEf8%LGykyidyS6D`R}nbO3vA~TILIUxmA>sTUwTJT>nL%|_}N1J z$Y~6YPHwIgZrFiP4E8?H%#}#@!bYFS+!z-Rg~Vc%?(BqpqBO4JN>KGu`>h9S8QE|D zS;n5VOtR*=K>vGzCN-461zmONQbfb$P%F9_5v1;BA<=_T6pDmu+)Csl(fN4mK#i-_ z%Y(WdK?e@$eb13SbJo|N96r*mMaaJig#;kM$rAFSETMIW; zFXGU;aHK?tQF`sTgKeINK5A9Dz!U@4CCu+DAfhs()tFvBIR9?S#s64(g(t$%imVyq zoYP@cafB}v&-J;=Kuu_G&M84+R>kQ% z1poB-P0JT<$K>GWjq}|Jh-p`Ai!Lguu3ilZ!3V0`#MkoNzvk&&AJ|{$ag3k2Z61JK zn`{yeVl(9d7aH$y4_8m6>bxHwfjhBbV-;~aPLUBhMVJ$9vhS(kP$JBneqGQXUbET8ktqowF7Y#tJutPs}jY@ zw?1e;CFR(Ew&0q;^gss|HFp%_j(QPx%^&xc{~ZRq8mBvoFT`+-D=} zmOKr&J__@M1Z68`or0TfbaWkQIqVM3)Gc;~=2L^8&6>gp#$2`SjGn!=e@JsRwdTmb zK1d|E)6=r^W_R^iW_6_3=NyovvmX0I zpN_-O9AeQ^GRrM5d4e{#wejVt*4%(P4b&4t*aXO#e~E919A%AszJW(76<0@n(nBHk zuwdqhdbkzPMzgVQ^=6WD!Pq|8C=}&J4%ZV(3WGT@`q^zgb!y4k0ozV|nItR3ZniM9 zLp*wM@saj$3gzr#gW>qw##JvR*=*Eo?d-J9EFoasADStdi(lIiW_X$zFabr0Y?;_oSf3SOob!!#J|A68ppX>_^xxLyFKWev4mN8tx0cbIZ`@&>X{;P6KskU z*|ZHfZ3gl+?yVg@BX#ZK(k6gpgA81?K>>Mm|KX``=i0F7LnzC_he z{0e;BcLK_H&*iCKz0=HR@X*81|JgDo=f=Zw^yeJ|-%|D)@4@@3?4F-XOYi!IDWwbX zu=}esjiXIoiHe7CFIm#r-yQ4Hy}h0y@PqgG>muMgz$aX-CPpI!MC#-CIxlg?nX6sp z2cK^HX6#dT9oL*`PW7vsjFeGU%*NFE6!g@x$;dzu=4>fFIoLFjOx8={>8XigMISD% zFhLo_zdoLA=-Mph4)VVjXd@7*512qKyrH5Oofe@fZ$dq1@mbJNawAX3g@%$pV;zvK zH8BgG*rpud+7Ou-HU=I{*ioUJruQ;^t!~_Xr!%$MIpFskU*Yw*K1yzD|hVCaE0(fHTAWsR})l;VUrlrK*J30T%ik9Mj*&GWc+O*fS4 zU}9^V=iUBUMXEzFNLn)V&jW`sN`h4`<6SPLU}%h5Zf1S_irn{bbyf!j%?SFmj(tBl{$9=jy;y4SX;~w;fLNM9}Lp`?VM{IJN%@sQs zIC`v<+#wK+9h6r|Id0TH?P1$D#RF#tO&E3iKLoB-kvhK3d3L|wtplV( z;_KhbqJA*0JKiuK=z!f$;pL(Kcw{#WK*!qvR__Pm$W!quE>t~qDU)})o-5|2w3Ilc zG(V4E4)5~U{_}D^LGN{`X8;9w4zyy=yr=|REv@UX4`DLm^C{2gG9cdWZg0&&MG zyRWmjV!|A++F|l54i)7BaLKZB1lIn_W-Zaeg)_5F>QLwSSyPh z33HW_8d+kke4c3q^m>qSFilR(jfdZhvr|E!WYMQDyH>2-!dTqXs)g5Cnl{P7g2`yCjOx(d#j z&bK9N9AkW1-v4-bh{FK=YKyUPb<%?|IKASzZ%>SMv9firzED(WTIlP^N=uB?y1P|Y zD1eQR7b(yc39X;XNo(6N>!g8!uj7H`o=dOKo!>`P<02Cgpp@K)pgseZl5nt~@fxX3 z>5h0CM$%*s>6 z%#Do8($mw?CI|JktB`IWy3LkC*>r_4?1{%AhoQ3#G}sDR@y>Ksw^yAH8{^9paL2A^ z9p}(QwNEM8K2y4tC^GMd<>`Rsz0PLqVv?VRe#TmqFy;CHM~+xxsNLVXjQ=oPEO+rX z7=yk$!hO!!Ue6vnjlx|`iRn%w-1R2&Q=cEfnf(3tq%F)p5VRF0zjlP$Kkk`F(>wRR z$9;6t+;U>-lHqMGdYrFI*Ud>dWjgp+liw;?p?Dv=!$W4Nj{-jpU*Z2*mzq^YX=Fhb zcpeIH-L4hmg;d+n&yReV%jRqRg}#4YjW&3)R%W6?8vTHD`tI)TE3Vz}s9IV%GTBRC zTQ?L_&O`g#R-|>tF^lX33$N}Wx$_o3xVvtQStv!O?X6P&nH%8eksW7uI*Xd28P~4L z)NZ^xo801mdVWoPS=qgy3pqg!VoF-4gXLsAQtQ|3_RofRZb}ovlam0(T zF{1hb%%QgFPKq`P{5Tx)s`G*TU*9Z3e+$nf`fB<&R+`V~B+2S+3?K^1-G4FQ1S(aW zq+wgbuSamzD!tra9)D+;eE(JRN4_L0K}@A;^JzKRo`PaeGM3)uQ{H+*#%a0#&X)lyN>`wD**X z+Kqk`6FL{1hrBgwnTfEXV||eKPd;}wGY}0 z`|if3jSiK`XJEw0IhgN5oalG|WfAdd0?Ej&H`A;isk*Cx0xsWQ$9(I>(PZbeJS8b* z(6>+#``a1|#H7hXh*6*6p6-;u`l5@nyEZ(0&j4X_II3NcWcO5@S?G3Z=5>rRQZ<&G zK9w0Vuuu3JxPZ(@nSz%L2vD#do`ZJFci5Wxnmnk<}xALXHOUA zi(6KlEip_00SDreXN`pUO&^B~3yCgr`-BmA&FJ7WIWDL|#&71K2gB>()=4+IAv4-ud1<@e-T)??u~p~R zD9K0&LwFM-NZIxB*GF1|TtOkrkP4kO3Yzic#CD^s-|B^~O?$1Wt3zw`ZBgd8Ma14d z8xmA*BBUcn5?(}ZeK&Yj$%NcmL9IJeb9=y-*ZR?v_Wq0RYL>bXJ5e59+HkkijbFmb z+|BbowWhF_+<2JW2P!1Ww;+I)!`O!j5tdx* z>A5`O%Okg*#{sOOoQ7=AMinHC&fmPFP{`R;NL1my&qtrajIA`xaV>Q1ss5c9+YpgT z>yiyP)*o=B-0g7N-+BqLhgV61vg1JfC+UGYWb^R=5qIuT5Ao1Xwb#73pSi3{tyfC< z)0_C^ZV<;J5Eu;`-d$OgPct#}b8#8QFs&p6U5xwDU~6GvbVzs9FgPH&$_WL#1nUbt z^4y>WoP9GgZtf=Psp7^0sd8|}(nXW3R&%oP^9&wM8*ObwAw~C$bsh3Cx0K$%Tkt;9 zwJa(}-X}jp)+wZGGJYgq7S09ZpJ3nM7KebHY%+Oz*dwC&h6oE*i&?&-mV+3ft#79Z z?2YmLZ(1r32aiCdkXZ>f0;RkNbW9;U6D~HKbp5f5&~jEXVNTsZ&m$pLRJY zcx5g5Qk!`V*ilnBAn=iLM<<>uKLpl!q`(SQ-PBa5BHfp@Uxj(nl5zUm6M*X3 zw#OHL#)>`&EAG&+#`EC8`6*_V%BRLagqf0f*#2;!{-sKB14mQjwp4y{6{GB)w_ewL z>K4LlhkNTotj)3+G+6v>(P$(Lky`Di>FhGCMy`CKFw9D=8yerQa%B}08j3gs#;vTq zz5UY3>nw|ccHIr0zdQT8f$%c1Jg$6>E#r4zHQy^_9j%%e8!-O-G*%%i5W5UQVmrQ+hd6G; zc5`I}7QAw*<^?v)NpWdKk+jjViEr0qJq&4ygv+;X;>EvPW7c#T9qgEPTPKSe!BoCJ z?VSkYWGAFSLOv_coEidfqCD^mGLI~$VjR*10p_Z=R5H)Nk_TNQf>+r6S&=S*KmF(a zj^0PqFYtcp4=-Dyy{5)fG4eH)YBkuO(R?>YRno>``71Z-A++SGalmgd_pI+-ab zD`$Zz3lMHvu+q<;{2d;+`GmXFC}>IdLt9Kk5IF>X;`Kb z$Vp`oKHX_YVM&c!*K2rM0v!+rjlC`mkwUYTeTJI=1^4Kxbo}$tYh_cVWYcn z`whjPo2jJp}GVe53V5sjXMOYZV11IV1Rb?+2- zPljI6x{IJ`dTp5MJk~EiOzJfG9V(blnZaLZ`?;nT_llK`IYT+umdFqOiKk3QNU5tv zQh{e7MOAWUb}EqpZ{>dvJ=$gxIa=V5v9s5x4xl$54@u+*KlP^WtCv>N!TYp- z*8&x+DPwOc2wKgyr~%w!dnAh}@Q8q?=a)ib#X~&f9QnG>PDD=c-$R@=kjE0HW_V^r znufWo0Jx6xrDdhIN=;?Z%6R&#-mU9MTZ;FE?~!trdQoYqq%F@5yx-d(K@_Mjm^=|E zTM4)Ir-}Cc=Wp#qc#No!E)z2#C{GF%$Zd=A6n3>ak~Rf`Tak>0CD9<*6s2~8JH!>!AMN8q+bfe!~G-rw@r;5#X7$rH}R%jz7VpF zJfOdI$QUUFFDGE2VSu=(xk9SW`f;Yn2=DP}32P|Rt>*--qIMoXP{kc?@4gl|*@>vk zlEv3&)=#t%_Sm+zsU@Cl?E52|({O~OX z0dT~%*UuQ!EtW3*{)tzEFASajotGbHQF`-`_utc(OB6<$63uti8{YH#DQ6{;mAl-3 zMBhYG^$`HL_tPHw8h`y_N@Hn)Wby+~<&T4l8@sw+x*rhU!X7|x5LO~LD09T=N%y)P zggFMFV1gVAkWF{H!zS;Su&&WwsAc8Fgo_!TO1mR6BgZ_PogB5SWtZpp@?6N=Gho8_ zecNORpiEw>iO@OH>e>1tGskoL?on%gM;#!Fa(>Z3xsne~xt^duh!PiTnjh^_BXslb zvJsIhUwe{hGBh=k7d4j(DQ9Roh<7?hQut(NywN285 za?iyj|KcfQW;7Gah`%UFt4uH>&Mu)x(*#=z>u=YOJ?&z=(F`((KYH#6d-7^R%_QCx*Lo?ATN`RDn6hjeM`KME9J_6CfW4R#8`+Shj`65soV)p zd{QhbuePqPJRTU>SdGas_!V%UxExQ6NIiu!Ui}2OUu!AU1exhj6)TH03KoWIXT%Ra)tQFeXE^ka zFsZ!k<~BwzGviYJHaay`O6-g@Uwy{6H@7n8Xxw2G#o;hYf1rD2tUGOrc3sX3W9S{O zuUFR}y*Ql&`c!#lb|P$5opfX*XKt$4N#o%Q^)%j-c`J)G^L8Ck5-Om^zB2OaMQX}* zfLG^RlbrS9Li5ONUO>K(qP4X(YOG~r!-MUg2L%0o2*Ex0lCVZD8#?tmU7u+56Yh0) zcWA2iJTTOvx}DK2KCU{1+=9cB&36z+-ptlF;P^=L&h2Rm!yn5xRm-gr~ZfpM1t&g4#YGa&;1tt_4{9LOCmevHs-M7LWkDDo&(RC_Yy)O=7%!Fswr@lJXb& z5zkz1Dbf0juK30A*AGz8D(qAEQ{$HWxDFl@I3)5LH@!3CP2KFItW2N^)qokq?rawq zcxK+_mTQXH^?n(*ymCtR=EDbEC*i4 zLF^^x`c6AGJza(zTh}KNuqs^mlzqHlHK3Hm-&pS2O|uy)B_QW+D+|n3nLqmZ9QH2r zsmWenFL&`A$yOom{=#1<4`oGg454Vj;0aZLfo_I)Px}p!5Dv8FR%YL4@!^>|n-wV8 zOcSD9T8NqQ9YvUB2&!2-4uDYSK53X|h&Y_`27B2w277|&OH9beu2Xz--cy3`yl~_i3s5H9{zjv2d2v3yY71?2R_DsU{oj7}VVHGqyS+N))OQ(V8&AGC(;{C{ z1d(2dyZg@2@KdINc4bfG`#H}>G4S5K(J00{W6DNb#QExmliUm4rX=b$lm{M9Q@pB{ zG7E*X7iw#-WQr+pfdlEHB3lqRmH=u5-iJbY46t#arMJ1!gWdE3z7*J@GZcX>y8EeH zW!fL#p7_MGmr2Z@o(*r?uKrA^##TK%!BNZB^^1gK|4rwn^pV4Pxn-C$OKL1*jGT^~ zX6S!lALBUX49hmibaK3*?D*rld5ns&vAso@Nku_KiL}8f87REI43v%6 z$QrH}IxFz~8t>I^;7`3|(^`Pp;?#-rdUmBvjZX42cZ|6VVZEu0&{VC90{B{&0RiR& zN}lI6I*{i-boM4w7#v)S*q{dbMk%%>o7ykLvp zRu`eEv5*6H4ZV(qHfdk!qdaWcM&YSre=iXBa%{EjcraZnd|E?l1&%c%=}#yYevE&d z;Uhdk_!VuyW_(cDXph$OxteF=*RXa>|)6pLi9bRWmH1V@h>n^rUjikp$0c4pxHEbqGL%SMh z^1jm4$#4@hwfRUSsgj_PwXm!j1i&0G9_)G%Lve+)iDqa|5}6UA?Wdj(g1i3d^XFxx z;roA%89ZWhdxg)_a!o&uQ$yi19@V>~nTa@bAdt6(@8iX{_ACfIze?`@l=Jw|A z_?Vg?haNZA(-3e{C?8^wL(QY?eeAX`>6n*-D9yZbnngv<4OwQq4VN)dq8THuDnk&| z=6%YL-Sxa<5YevB@`zz#?Am;{-raqm;msl!d9cM&d|G^E{ev8oU<{j85Tvz=a(vrN zOa2`GZcxjNRa8}VNRg|bA1osU%R{5;Bw9nHq05dW%Nr}D%Jx9UpfvM!u2g5rfxWS= z*L(^FSl!)0WA?hb0lmLbD5m0wdbz*TtYDz}<42b(NR&wVJiD4yv+cW9P9*NYe%TRW zWNYUl&g*J`-hp!MAb>m)CAUjTs?jdisWvXN2s|Po;aQ#eAzv_Nr{W0BVN|>$c)Nx$%VD=2x(KA2> za5e#MCLj+10DfCK>pjZKwCVxLTzeVjc=GMFkgUP>bOP5D*(|meIchrowE(BH1>+(> zao1H5$M-XVY-7_|E>5^RepD9HugYxhc~J5za~J5uS+Ih5gcB*-NKN!u2D_loSMT|$ zZRZ%XE??_^G(`XV-HciY##KB2pN}wk;p2tW#JhKHY(g~0>?YmixTk?z!2CV2y2;piDAWXIO zjKMi$AwHZhlpAWRU!S^6MP437$)i{< zJ11KlDujT_cwJc?wYTLg*BakX1wm}}6DPAg*q;pFg0FbSGW_Ob*c}nU=s@>%o~``4U2Pj1s)}2 zxx-9iBMJ@T5H|dyLgW6;bzSBewx{aL;)w@NV-rM-JtKdAWopch(t=Sy8gb=!&hX=LV0e-NC;Xg#Y8Uog!$>~f)J69Id=$E0)w5pnWUdaX zPYXa8cdKe%u`@#K0htrc%6$+h@lS7+WbRW z=sXp5byYIbjk48Zz8+^vmF)H)`AUc^jmiZ6dTvN>Ts;^62uk%6$#9PNJrC3P(1EU7 zR>H?@^{i>%-m<|(pQ=~Kk=^jY{Iru{<JJ=wW&AooM;u_thDn^3!B^hhaH?h>>fuc8K@% zLq9uFVoAjlrX2fz5u&DA_fF$YOJ?Df_I5!L;T3*43-|+nC$=ACWVG9eRLnNHK0M8h zDR#=0Uf)wCk5^3o%cB4}k%LkeJwNq<85hz`kAsZ@nZNW|v%LGa^nc~V9<$tfo9o1D z8r533@YgvMfmuT6+n%U|_>{m{#jO4`LK72!O=@JM;MUBvmBMb03Mz$aX_ra=5_EdD z-`%SYU{sc54?O&XGz0L^?8A66r+%{5n9MS{YF9I9pkxF^UMscgr|h$o<&Jr=3)(jo zR##Oy<4&)%M*^M)s=N^gQ8`As(|;_^w;k|hLPqVw-)n1Qz1aAanu6BP!N&n+8$k20 zlZR^UTS4uW04dJF?=;4W?4=r&zAPPzpa5XZ7x`lo4bf)Y$~dfsgeQ;=q^*=Z_julu zr_3(z;%6w;FchWVauCVv|KLtW^*&O(Thj-B%NTw6nHO^6Ba9p=YebNpxG#B^;ad87 z5ID()P~?jv(9crz@(+dqTA-N$@PU1=Oi^_zZ&j7+d!r-gnLB8@B-YJXtKU{Hy|{+o zP*Cv>R9o}(OCHg0A{4p*LM|3Zh4D2s^2oiCVyL;Y0Dv5fEGy4@rfzVSVv)26`1`pdYN?XQT+ zq=)d?N_d$EiZ!XDh$y=8fm!g4+bXUSyannI_D>i3(SN{X?t*e`3=ZeXmF z8RD7(A#_e5cVhdoq$m-4hkAQemVGliz&aD}!SRB9PENh=bL~3k%dyX&J(`#s!(4Fd ziTWY;r4eLGAfb;~o?>`F!h!K9M3w>(r0Ii&a?gFqotM^0H=7cz=9jVlB!LbX6_gaH=$ z=EhD|h2IVSgR)!Ge0Jk|ex#b}2R3M@0Y_gzX0lvajLSy1y0S8R=>&|EMmwlRpaDH->tV(nZjFr`Z`F zWu96DuN|0>e&Ejp4m+TOK0lEQ&0$^9gxiF#`)X^i(KcniGq1jG$oS^q$`5x;`4JfH zBK(M{vHpxxj&Yn^NEWoY{5RuKn$hVlQ_ErKxR>$Q1^D8~%oP=VzMlCg7Gf`hC6VFy zlOvBD%c3SWEU{;6Io?`aGOZVV_Mgwj6mEs6V@*Pf^p<#wBh9D9zZ=#s?_#8Z>UAD3A?$6MF0SC`uJ zTSqoa>Tl;?c#5G=gUt7#5C+{mgh=!MAg{b1;9Vca*J;8HVv|hVX>DziYYkS$KN`zLj97~i6i7eS$Y%O-#mm-dRJNET=J?G#%GtKY)V?NDHXM5l0 zxu5&K?(4oTFiYy7lLNAnYGWRjcLb!SkA&g~21@A|G~}{m>b2p7PRP6h=d_oZ^lEYG zy!R1*VsVE&7b%n{%kU*Z4i;UpVh%J(r(QOFrrfMZweLEz9p#6tEFvJsbw$!jOI0NW z!HWWv%p`2aT`d2jC{&&o%Ae0g##moE{C`h!j=7~5tXm|~mIKaAPpVB3@V9AsPPzz4 z9ecijX8K9Lx2ZtXw!dxsWg}JZl_HYJH%KA{rjp3bpg?#DyTMUaw?0#7h(XjzAtz~& zfjF?oo>K|zg3Lv#yQ-{Cr>i>rL3QM4{4QMeE9DsnX?Ljc5o%+AT*|*slDEHdzubJb z>WzSdFK8R}Wl=OzU)fY0y0(W!6X+wAUTW_X5vcM{wAb>Fmrl4~dR+js2*Js}n|9U| z0{9N{6J9bc0T%fxi_cL8a`pkFHB0mO>?PvSKFjR)?e({}Vj_Ug{X#T2SfedDn0M+5 z95Z$tsjPn;Fm^GcQR$30d`&?e>Zqiw-uw6Y$VZo`jBqaN)AbxF84z{(?Bx|_**54P z7Z)4*(9lI~1GBBL`U$KxZ8gv9)NXG-070K5D@MruPcl0^+ zQm>aExVsKpy+ZBO=1QKD_`V?P-=L;0+uaxGD0|^RiTPMP--OkZP*&P7FP0b^eoos2 zR_1S8?CTFK_7TxkUjZWLf>5KCotn~ope zD5evo3#o0d7JZfKNq8K>Bv&5n$6%>&+Gr0tNj020L!UI5&)V3&Rr5f^cq5d=)g!fO z#(93*w>m&JDy8ty<4?W4y+7EU4*w%?T5o`zNOIkhW9>PgMiY!0izm^+&Z%5UJ~T(~ zdL!Ek>1|;ue1owY4Yz&lW>ss)W4K>B1xySDcxZsSOtuoxyf-C|A8yP);D*-!BFB+< z$4X3>;jaV>B&}l&BfMSdFfp*#V<4H{IGRAPjSD+~<-wwJB3OQ7yJo80oomQw!o!9X zsy~x?n+tx5j5(5Epgi{M?eEV7(dT6u=IX~sA^PPik^FuBBwpbpP*|G$G$JC$1q25` zrJN$dnO>=fok2ln{I+QAyc35(kfo^AFZN;FW9sNLsUxb8-S33L0l6+oLX{tKD#uK- z?9p$2VaL>4D84#*BEtz~4OltLK#JVTKJgollq;myb>YO$C+if zkgcEv0~c~aENGSOv{r4aO4wSf4GH$qOykiT=sJ;WfdQlhF_>8?yrhwE&v#za*^eL2 z70oE!hm|AKs|Fh7AKiHqGTHB#5&Z?Ca-a=t1;#W-LBGq6r=pt$VpJ$NT!(FrAAa7J zhq;48Kk-n1T!NE)_HfxgzhzP9i+F}ofkU|Nu4bXT&-EeUC1T&a*P6;`Y;=aenllhh zhmrVMl7q<%et-M8!o-tB8G42vk47{TbvaoV5BCRoov%n@5l2@bMb%F-b z`O;mF-p%_OMXUi%OT}T395v$+KHrF)I+lzeDa;#6I38w1l}DT$F`A`0KkKZ4Z-j|B zpY6BWVEo^Mj-6-H=j!dcbz9PmNAEAj@5{S?S-wiZk2>#*^d63%hR)N}^Z_Lizc zv_P$dZSH2u^mcV?IPsNCzC zJOSc!(AQd;HP|h!+xR7_d$9gGH0K-vY0lznm3f?QogUQs%`%|Rm*c{-J;iDd2d#JA zVmJqVboTm`9`rQdZ>QZ5e4Rk8<{HbJXwVs7v)hALE9&8J`Z_rdB4h5}DmxA@3F!_< z!d-)!lAJ7wOXGBj8msz1`Ahy=&V1TYok;AHGW}(3XUg=4+*ff4dztuRVkk$VmRtJu z$h8G&kh9FaXNhG91fW1 zx+%{cb5wrrbDhx)oi@jD1b@j+efdWc_^7eUy*Rw7eD-Gfr=ik0JpMKrGrGD6P1ksB ztIbRMCbnxPTy9v!IhVT$lKgH6SIJ2N9LNh*B`C*T2lIW?9h}1wX(P48_RC&7r zU2kB-C41ub8Ar#N_s4XbuFe$(1zA1`nDghrtXz`dpFYrU$vmaNSkU*L#&_2s=~L=s!u;jLpgU&qnq2lxfFR|@RWBg+z6>mq9f z@W*A3uw=idi7#kiuFx*V@A9w1-2#rKb~{dV>dSniS3cQt%bBnC(oPwR0;QW{-VHR% zv+~BqE;+twvxsnE z#Ax2c?L#UF8<9U34}Lk9i0DwlXBIx}{6`@u7+ai*0qAtkyyW8wE4}D>A-RiP3t|jPokZFlZ#9bfN>CxF zHr#nNGqYjn(ULfiWum&jubuPouKA+Y&u>%bN>C1ddK>=yu%)G?_M2AA55-~Ouk}m5 z$_Q%b2{+2LhR;2n;d1Eoha1Y^VjsD4&7O0^v%516j(P{q+qXB!>WxF&yd;RIj|IN( z*b1}2&b3=kpidZ|F)@}lAyMm(E(B4JNxQ7K66|_M6SdIpco>d$m*+XewVB#<)45&4 zKYxY_)Pf3niCRNS`(BGb#xQ{;RO^7*tw((Lk83cq+9dTx{DJ1qGc7^_3%YcrO~SKE_U!IGbFzOz@blZYyr-4rpPg@O?&BS+ z9kKc1d_k)Jt)szb^NTuH4p`XfWX+~?tTEb&2(n_lMng(K+wS61|1JEs5uGY#kfjc& zSO03>Te~93La8YI7MsJ^cw2|dUe9^MspW3cX}wQsteS$L0VC%(aeE@d*xq22*wHwd z-r?x;=IC>f9%+jOT`FwIqC~0ht@u8Efg9aN5lAnH3bc%!^DJ*Ir(JtJp>u5J0*iv! z+v~e)SH#-$c9Xa0#*ot{hXG4R3GKcYjxGj~hPStyx)gjh?{b{=$vGw$I$o8WI^(Ed zU)Ci8f+zQ3NO(w7e5nso+Fy>&KaK_NNZ zW2p>UXD_GsIjDMhEI6(w=jXtXaNIhvCZizxsQz3ju- z+vtN?{bOvB`8ModxfhJyERBNaaJhUTb@*I_zei3nBML?b4<#+$Y<+C3Q_Fr@Q1oWK zY}|@*2lWq8-ZpZRfBM>G`Le`awyHLmGmR=fs^!Tca))Ehmb&X{P?i4V%Q4G;f~UPU z;kY#Z{`|-rr^CM2HcbD^);_z7;-h2XOOK2Vr0(&ExRDm{^%9=A zN5u8Cfe$8lcbfFQ(<3hN!{hV#J`Nn;{UF=9;9g3|&z_#X*;Cu~;cNE1QtlVuYr8H;PJj2#6A?U`^Bv{GzL$L|;I$1|zFy!HV_;mr z#AN+?v32qGBmHphs;x|05ow)cQ0IlYgD_q5=-ifaR(iB-ZDl;zM}N(xnr9X4L$$j7 z>~EM!e?i)4Nr{QNEycRfd2t3`ZMR#G8--GMNzcnjZD71tbwUioJeX<6gsz*aL(hGJ zy^u?71G7u-Fd8Uw#_~+AL&mGiXVN`|@-h(Y?bNOyg)F{@OtF!sBmrk>GK%W z`d29AiBIQ-=sy?Zm&X}ZuYjPU;~LC23;?gR`bbs3;}Do<-(fl{#Q~A?xEAxhpM|=^ z)eLHd3x+}3eVFMg>~b{r#bNy#&sclB@rtiiRem0r9_wxj=1A)|-~jmNx}YLOT6%^% z6n6M=pbHo!iwcI^8kC<{2RTBQFI<#BZl6y*!U{dnu5(qSnnQl3*#Wm<|lc4#X2T(PT~AoLDo}@BRnJI2S(_T*GjiM92}*~^O3Qk;Q%s} z={^7}jj=o=%%{wmwPm0`toEMf+hKqY63LYj5w=7JGAAC;^bI{13T5*NU`|BQv^vQ$ z>;3g$h9aZkGdn!rs($mnrj-tufejy6pXK49k5a8dvy8x``M?y z^J%k-iwv_S1?h>S6^?2_aCGDd-aUVGrj$=9&s17(O$%t^9VDvHg0gE`s{Tu1+PrO< zl+Q!~pHh{DW_ss%2b}jJA|iH*?|df7#Om((FdKVqZEYfAI%(rdrRNq{yhH@iVhJJ~ z2gTvaVPtPwQ5f*u*?(?b$gGMKTgxiXvBMU3G#OuL(w5ncXg1?o-GYxgXD$v?u!_|0 zH~Q@kqhY5YD+%lz6faEA!8|j)-h=wZ-~Mev&(=KdVYL2On8J0)HXGj}-coBkY+Ig( z*3lh0=rirw+cAd>#5;iJYB>vGP?o*{&=ExE5w6x4AS1SC5hk!4p$JtHDUx0mB_j1F z>9sAGCN{kR`wVtu><$Y}+GCGdX3dX6AY;O{KH{H0{X$WLN}Vq!Naj%}i9qRIr;9-k zs>1*u;f1Uynda@PRe}@O?dA*(IOk3WiO1j^G7AIbC+TXr2v|2VUY) zz4|ez__am`23*n2XeM4lqx@qQrb&R=U~-WkFtXDj`VL_rmaz6bL+HBRaLgE|{P_=! zxmWHy7=Dh>gym*@Oo~b}M$BB%{t|~`Igx2z*PR6XJ>0$gQtSMy} zU7aw^5&w_L93e5jK&!@3`J9gE6(;@V9AH6VdWJp%9f&+ux%gs1o4)@s*mcA9Sv}nO zP47XToI{iS7}9eh*V}h-#`0h>?)CD_T(g&-*v^q)t{9YG+YQ*TWk>B$d!l(Ok(;&- zvR*4FmPcfBi^*In^YvCcO8iG99D)yDgzc;Rr7y=l)3T$_JTd7ec`;&6J+tX*1eEp@;)!R$DqU(CI-`V{21e zUWD~%>QO@eA!l|p3j!+eXBL1j)lHoy!X}NUBP?NSNk5yz3cN?`E!z{YIT>>|MW!Ym z)=zb?Z}#~Dt>|@=j(03lOB7hjqciSoWbW#z0^D2NfFIg9kKwIHRzhis`o_d7HZ0!c z!LRpdy}JooU&1# zzGZ{1ASpk^mVBUNAdi})$U=f-21C~u<24x@Lj82b0pCgk4<%xbWy#j2 zgjYQ)jnKkUY$2-Vv?64x3l!`res*+xKp?f-?co0X1c6Qi9{>bqH7_Ei1)qRV@kk|x z2jm(1(o+krfzk$T-K<_f%hPf))OLScej8f>Vg)|j3p%H}og)Y_Um@;c#N?#|JC{ps zI|G0AT`{%te^MDBaDWL(=9?A9mceQHNhn3_0P|K(wZ>wEfFOEp+TaG+S}C9Vb6A@v znh<#-F<^@wbSTubKho}`?g*rmIN$9p^;gaJMuKsHvZ!CuC`?hSkKvG>Vau@1@kF| zpRm3(EAygE!vkkVULHHtRXl}kgMx=hn=#3v-G$($33Pu0@-$kp2w-E70c=3G;PHVW z$Z&6z5k>AZ-H+q6WB77R40_yrnQfrM1p%F*Ml>HU)^LknI9&2h2A-9zO`RQOWo7#0 z{WL)`x~+VFZ3YlFu7Mg`<4q^Zu&dJK01*|6`+#HnnDhx+c3Nl_|F;rzNfen#Jx9w4 zh&R+s|E#TUbDMiPR8RdojdNb)uA0HxQ0CVagVLC%;y&66Bp6SU#$_Cj@1sppS(v&3 zSf`#snm=J`;ir#rWnSUV_c8hY-Z`mbq ziP6Eyh}E`5@Nnn9`n=pns0_L5ip`w2nJa>ga_1@=npQPkH=LJmjh-taNbcsAY2h&r zOR?H`5yUUf6@JE5Z`Qz-WkaX(^ja%!j6^IXTE#zvwx*<-Nm>Hw?H z83-l&T6jd>TDSXzz4>`d9SH`R4JO8B}3lMSq)LC^SI$t`E3>!bs0HtDuk03E#rFoi#LC=wJCso@P(^kDbK5SHQ|F<0lF=+l|_3PIj?SDp8! zbziZ{yD7h-`#WbuKGbfj>|UP<6JTuvqSWlL`uDywm=_QwNF6;)Ba+~oM<&5wwZ3Q_ zE^s#sicFgf;%ip0(?(i=ZWpeqj8g(LMO*~|X|#b@q^(u;ml-t$w{*@MGSLV2o0fJ! zG;nE5o;usU96D*&b!Q{P`Au82vvkL^bTK3d@lXFD-F!H{I%$^hmb}}1e0#liJH}@g z1PefHwTB)`v+&`^1Gxu^`!Ogw6H3fd;oD8_-Biocl@MZ_rVQTe=y<20`~I(^ap5ru zRwm~`<;(_+SX~g)MF)wi#v{a-w%RnbptZP)o+^paB)>Q1L0#wtYX}VNVPJQkf1bZG zZ@ENUiUjzxhzDtJ0S$}^Ba;(LeD1GcMl?m`Aho8>3!tBSdmPHcfih)LKdo<07&ku& z(wzs@y`D?Jv9}rCEhQz}=H0*6!qQ%|+5yLhD$K`WwC|yOz;;{3^iMO?5pom%odIxQ zVlclZ#r8BFBJ0=*Y03c7d}15^Hcp>-OUY2UB`lp{N=n*=W;}h`bX05D#(tUt<5?#!re!jwx}2BF>b&fOGpQwkQJC@k{=P63 z64tC-`O{EDtm|P1qM|*ZzJ`Wy( znFAe?G((cul7zkUm5s9UAiNnNCRXX6_c8yZRsUF-JpdhH?w*DI){B{ravsr$K(L(ff)0oE&8u zlMx3Bo8qd@3l3R4mL$)LY0=mIpVB2{uUs=osqoV&vo9M|cMfUgwfsr;F7r|_w`q%V zX181UI7$>;B+Ra8uNN~mFbL~K{uH?Ji?4(TNGha7<6iCNCVDpTT4K^oM_s5xDNjAz znK4-CUsi~@b7@p`z)Z%_Fc#wj3pogI!^o8u(Qys6?BuoDA7>n|PEp928fbibYA$+% z4ySa;AwpPaPMjFEqSr6Fw&VfHOpEc%Sl@A;etC4^8$IRoRq(k9M~{h=K(Fx`UCEwp z+Y~suUkhpS&$-^jBfn}}X75oxI#u=aRcE@iKgNacDMlULq;B(vnh~gsw{Wx$$m^uo znB#ZlfiR8hEl0)cb&%b_ntWZAsQ^<-O55s`AB5Y{YI_Q9X2~K7g};s%vqH=< zwm{0!A;!`IUJ&Wcp;Vb+eOSd((Yy7WO?j4LQ}*=K$ZFO%e^J`s4zufJq7HV*|GM7_ z)Yp%uEYkfE(ruEvJhUNA{Q~$#?4V;D(L>S0W+tYC8PT@#co7_oOXhN7kVk_lR#-U8MlJ>RYBSx$8bd`0PXh(2D3-|Nf zyw{2hW4mBde5<*lZ;c5BvK?raHQqlfzP82$51ot_rgI=MI7T&vNFm92Pm$G6R%<({ zpH9R)>-AA&4F8=)hb#-aY(D?-hH91r-y+?(ye9agB2Vt7njU27Lo7R?6GY$)xwAYu zBSyS223`-tIRBN$&g|5ev$(hmOf2HFg+;Y&>)CyN$WheiQ7GCW1V|ha&iC802EM}Bc3(}Z`}35VY5-EW+DtNKg1c=3g3V4@}Ab5wqZjtwaund{*j z#uh#DZj$hO5zFj2&>bhN8!9M?&~eBB|Mt8a#mQ&-WPT^@Bd?=hgW-Hqm7pxAfStlc z$DQ4wmBVMfx-P=l&8oU}({hR$B&b-R(q=0vWA>s2E&4A>ku#GyQ!`LwstSve%)xbh z&f+>3Jq#r#Bop}@G7D!=#FeIa{TNFHpvCJrR)m}w#EWVwH_a$L!sdGO-pUH z>yu|jb%EWvUHevii=}o}^D;J_{2N7_h+;l|t{g&X{>MPpQ*ciS5CZBJPtEon4_TCo zWO#Ax$PGVTKXxreQRwb9oG~YD$Vy~wl?x#4X=+w>3g!gr1@^>!dr|O#=SE*!lCukRw6Wmou(|&E*RF>Fxr_@5ehz5SlsjElA z&rB{bcN_@_*~CilKPE>)gNOX@3e>}}GB19K&(2${1K^1`5aDm*Th|$Ttn+_$HTK{7 zm4Tr4)h3Ehtf>RG4siRq`W!6M_m4IlnprgwCvnk(kr!`AQD~pyxxd8=Bp|6I*Fv}t z)U$XQ)!bwqgomeO6BHKD+F+Al4DVvH8ux%5@#|WeMa5f@i?zJ6vYrbm&v>Ttj)NMT z7Jvemljq>+K`^wpcb7v{nytfl!o-rW1`K^4{z|!UZ8FnwDu$WW^_rR)I>*PlJ}~KW z6!-kWZnh4iiWFIkj@$ai@%JzwJM~rXB<@J`KDVv2l5>(ZVS5y`W{SAM`@d#bs`8f8 zFc3D60m#bzKv9&#s<&5&yd=0Gt^byne3gsxeEi@p7F%LmDWA%GT4Y{$-O@drRvX14 z4X0xOQ=9HKuWZG?wdqQ{ggh>K+@EPJBzXNi7Fy%-9p)_N7Pl>xQ6T$du8fyRyi4r= zkZa~TyYq%NLd~o{6nXn;b+stHxc$ijdS!gpI08q9t|+7^Hp-%yc{T*lJ}EJHVG6v8 z=0~CUOuzL(ZYxWJbG@s-zdy0CN=^As9pVzNLykr&e#6Cvv)Tohx{9{LktokoNXqAf zE0d|b&>f7b#!+wS(_{7Z!6~wJOCn$S#Qw?RYO%ndJ7e#^6QQf0)Ahbi;+c*X{4U^c zIK?6hi>|z2skTb?qhc!6iIOD+=YimGy$BXsYeOUg$o1z@RaL?k_H%UD71Z^9oS-0n z(w0WI5B`R*bOvK~1AI;Hl6ktnP-1sXp6*V6MQHPg+FGQSpF5}Z9Jwrx_lT+=$PIF( ze6sr6KWtxwp}VO;xs|6#ivvOmZ-T9-kM%8ZQM9CUTA?Eim2S;rl)%KPTJq1b!Y5Bm zKHj@FFaX6-WF2oIogH}Ne6^>I;lU=aCLULmj*Bpz1F~IG+?v*U#ea zh+iBX9M93>h!SeO1k*o`{rk}tVM0I^ObE!H6z2aWGyHb=(m?f-?oo!oT2;B2e4GKX zid(O>upl`}7tL92ygaqT@7U`qBvM6v&gHn^>>Pb^y>3ai-S1SuqT*jz32=d>LypVmt)O#{jf^D12J4iP0m~|j z$uq{BWBJ4Qa*T-H;b&GAlUV3UT6wDd+v;^^4VPZoIk~U2QCJeSss2u482~{!fu$mG zkEe$PrwQWA9FEb`_xtX-E_6R}R)@a!qs^zv=x9s!oecz&(t0nzb+w%7U6T)b4H)1Y z(4@R(DaKARF*RVIvz8*n7XLDpn9V+(7$YW}@NAuK>B}qZE94!H(h(i|^f4-hQ zxA&JDUiZhW8;AVhj`TbS^#Xj(yeV~V1>NYYp0NgdD_!Z9><;)Ii1bwu%H%{ltYei{ z4kTHe1tV_{wrF4kK#exNs2e+y_SSEw@7lLK#lDM;WcN<>@s`HP7fZq=W%(WYQ#$)P zwcorF6V@@hI|D;blc?%S(MeqWu_oE+>ywNfr6G*yd{+RCSeHBp2>JcF>%>I22j4;c zR9(2pQ(heUUk)+c682LL5pGEKeuk@nZjyWBC6?0W@6=JRHM*ZYcUpK_<;$Qz?AwgA%#AC~dWX>6E2KQ-4(hI-RV5pJ^tQdU zMY^*|Jsf$Ri&Q-Z%k%gvDg~evIsFm-H()KryqvxtTE7e1Cp}bdiOIX)pm2rM^7&i= z-0NOMj@~CDuV3_Vnv3g1z2|46d}YcFfL}^Ti2t#yE3Y>K3m_IiaJb!NIdFh8Q=x^G zH9@OM=JmMKiymMN4WA<}ModcKpm8=M&|!SWG1MTbvalY~*T2N`?@4Z+<){Qt%{jf& zd zjE;{8jQ8mX7$xOg#*LiHUlsU0ncYq9R|L$UEL(ByM6!2#drM1;<9LGzb#yqAO6VsO zZPl|ME$QXO9F@&oVYdxh2Ah2g~h8(h?FpvNlQAnRn!KHjF6f3$8Z`P#d2z8iy1ql#JFwG_QxcVxM4j-_Io2!NY^aPM`)r#FpOEsl33?leo3^W}&dH(YfMVUoe4ov> zpDJybO9ekJ*~P^^d;ckR;q_a;-lV8w(fa#zqh6mmLNe zPEehzDP`8Q@Y>Az7w&gszy^9^UXjNZ5fk^6myIIH3t>E=G}<2@u}u*^?^E@N+IxQo zsk**W0VzxOWpc?K26H)u_l2TKuV)6J!Eh$J{>xkSc{@|NcHWq$e2x=l@`l~ncN3b? zYzR}2{q*|`ipBJqH@mVT$1dI3Sy*d@uo&OAi$}R`qb}c4R*<~&rn3r7x5@T;VcTrhug+80oa_&E7==q!&JEp#L=ZmmbbFo?oWL)Ru=d~BBdvJ)mU3c|AH(66@ zWI*t2J(xdCZeH?f?Nkq54*0;{=GB^rrSsW~-B_#Cq}-amMDTKkNOe-^brA)Jsa3Vu zGp#hij=J;a+H-XrgcJ)jK`B&)7Pkb%7*7yoB#{k%);rn%Aij_Jz$g^`?GWrp?`ePy zSg{tdr;%()pzEI6#SH=Rh8G3*6&XKXK4hCU31*Kg|ML) zp*D2VMFVfWGxlFPWbX>8TiEZw>U=UxkY5ugqI|6<#KF$ib*Yex|NMv(nw5KkulDa{ z#;BmppFV#+oo!p4%PFFL0@hcES0#~_F|KzfHv;CTPRc6~=dIqLeC}){X@8G!H^mKl zO5H@T4Q(1KUuo26P!OH`7z*qCBCa=nR0`DWYv)4t%ps_;+pN7spHpCZ`?BrUU?rqKw+S$6qr!!U~-DyU`&~J0?(5j1@nl)b;e7(^gh_}^g@)H9$ zLy_0{!vfc)Qjy1059c2*6;o`fqm^k-MA(;aSxpl@9`h{sER~Puo9j!TJA)EKlNs3g ztG-W9PY-~#FLBmR;s1=xZy*o8_&&Xn>hW@7`079fe~16#1TiSel|fDlkNU5*$D{ZG z5Vwlxq(~W48R-DK!T69FekP!Oaq=j^ZToFxMs%&i$aWuvv&?lqbygjP#*@yVB9O1$ z+*T*6OD`B0#N*IA04Qhy&O;i&BODX@kL~CsovE5YW$vxn3!A|G10AcK`F$&4Ejz8?Pb=U8L#?&@#NX8D0T z4)klA?3G28KN)Ej$3bUmxkcBvcg>^F-;Ya4F4g9sa&wq`5yfhkYRLd|1VQU{=xI%M z%6b~#N_P3tOH%SD-#YbPTd3ffO@hJnO-jCCAOI^ONaEZuIL=%r)OpMA9i~YnFXG8L z70UXHb3i7kRtiA6K}2`5h<-B~9o2E8x7OWLG@~Y$G2ioKzvL1Y=C*#7Na`|^Pa8Pw zM|Own1f=DUHnBTOPNst)Un-7q6SoQPdbO_rMFI_ z(!@iXay%kqtr*r<--DyoBQwT)eYHsIEO;V4LBdj(tDo{z8didLEH=vIav4 z19R0@z$W%v3g=CBKJCzJy0Wr1F~tk9eKmWu(i2QhcXazBMJP^?Gz+^cU|K}wGkhod z3*`p_9doG#?)nlC5O$=wQlj~|_C(+HxtvdnPJOuXwK?!8b)bWTM=GZ4PZA4o8!NR& zx0^NP+u)C6^O57bLNRNbwMO8N2VP!TkP3KZ$h*>jxnZV)^ClJ3#vf0!csLKJ_s@V- ztaWJf_00kO^|JHfr z$vi$e8RS0vazh`TOSShNr%^9fE$c4P9X;NSg4K47NTN_BC|d?hUpk; z)uo;31yt$b428+aHC2?&CZ36WO$R=h6bZn~Dq^d5ZuPjeCN zjoFk}0#o6jz3TmS4z6vx+7iyCpSmgDJN(}8x*(5~nf}-eC>D8&qtgpO2h`Ok`hJ_cG^FVJhnw*|*|1*Cv=CFBhyC=9 zxCoj;o*S=gDSOd5lerHS-sR9n9D=NO##xXw@=-8coRmrm2rpQoCR<3VH~&Y7gSyOU z*VgU|gU(1emM1iSV5ia=+#XhqJv*`YASLh?Iin6}Yx6?QqGH>>Q>3Le{TXZa_vPR3W^NAyN#P}fZ1lhOZ30MuX(g}E8Myyiv4(Zu zN_xJ7;XMx1h}&!BJ%#&@Tu^4Fr;l)$vTCHo&qEaSB2#+O{Kfd?d-G3R>$%D6B01VM zpkq7M=X`~YadEICX~?o7OoK5C^~2)q*6x9{Joi+49yLl}`}Br6sC;)Qcg|j9_!@et zBOM&URO$nXe(gABP%6+wxtDS#GIfQhv3AGf!kr7w-#;~jvb?ZNM}j5quBO-c<>P+e z!+QSA{K2IL((K&E$=$nYXE4PL?RJi$r?KU)C{FXH5Z3(MxjCq|ZgDl@Oo}?a%S$*( zMi}k7Qf{knJLX%6^KIWEu|E+%GG^LRvc0$}q0>^#_#XBjqT_nO$_5i0d2VW&zP4f~ zzrDTEY=$Fm{!$-#Z85guk9!c zCl&$s?Q83@1qAwQp$k?mAZ6}i)$)9%$JYZ?p4cMyM#l-;bm>mXFBFB(EoJXVW^H+# zoGi0Db>RHkpF^+qLA_KYSSWjXJG3VHa(pG|6NmC!z79$Iu<=1|jvP+QrOce+OE5}r zOp-_q9@4f|nzw9W_xrgCFB?N?je><0|6KU#9Mqo{ep&x+k6iKH`*`OM*%sDqmaFP= zMRFw!^pE6&H)vY<-t;Ov11baV5h0v+Eie}ET{?T+k2yK#axq@9^j`;*ekcx#;eNxx zSTdzQaf5XdH5YGqyw__j&6f`<+6AxA<&kdSCe66;Tmw!WKi?WV(_My!@2GPruiQu? zh~yNLWJ~^QVV6`IQ!%^FFt<2P5TUj|CW8W4ugdgB<*YIiNExo2A}<*g<>#oQbjRIa zyzeIsJ}6o9zoXk3ksN{_wnhI@q(u;p`B=XzjIu^sW%|ob59xsCZGpxlN;t)>eQo72 zrVQt0TlD^v(gG+#YtTyCZP#JSG168aRxoupq~fM)%Vak$GFeg=5zpUWBkgFpfmuC_ zO~qMKG({*)b%88?xJzGFYE&M0e@NBPisq$lv!TXeBw{jga|sR}Qkj#(KoPw$;X}a$ zWD5ssbsnwyh0->SH%ea~>q}^U;h_REG;dX~KY(HafJpv}jQUNI#6i<>j^ zxbf0)*p8W1H=D?b1`(LGa~(L+;EG)`Z+fw(<8D{k7#KP9{1|N&I`QTbU7C2pQd|-t z#uABP!B?8|mTlg`-|qqkPkwD%p%$4;_ne54EcxA=bjHVf^faOU(scAr3TyMIU6Nkv zJo=qoIZ8Pl0990HY6Abg8((N6X2);e^J3PI#XLE)7F0<|h>Ue!=WML)(==9Ui?4mT zU?C)er@od_%UIpLM1Q+2k9E>$hC@lTP9pI|S>Hn5A%hUe7dA9Bh~nEBGAPIX2}enX z{%_HgH0!H$Gc%93ZHOg{+h%3oL_I@Yl)Z@=WTzmA$|x9+ltdUD_fiU6FuC3e3zCTd zIsp=<0$=vMSOWoU$Y;;b+j*h88HmvUkRfFUZo`i9yH`A_o15I1|HO>rS{QBxN!B?F zv_vp+jCXH3_J>KsLymQR`CSh827pIw1gf^9xTGW#=2G6dosgBsQQesRi#J#xe}mS( zw}-#cYc6ySn2@Q1h@^_tdHTo@8Re0vkz51YqR zHjZu^KdZW%J)B{ykF3{9YUCXfJnkgJI^5>e7K~n-hw@nnglsF5eLiB(Yg%2tXhM3E zfPt#kDB+lw)9Agr9xkKo9992hhCnQ8fulY#@*d9k!*;Yb^A||@INm`n_p}ILal2WH zmyY|JzLLiI?3BP&$0ucI7e}E>ax_)uv1$Os32KHCacp54C0I2;4jJaGm=Yv%PK_2~ zwQocRnyRcrk>x>V(#})JRk9}=P#VAb%P%^m3n=-Er_ax**2uj5>r}ohaclc>^|!f& z&0XI=1c!%*FWC)jQ0!Cg#x(iBu%U17t`#-!6-ilaoH}&&S}V7MRY#PAJ?sEe*HRQt zO|7Q&2O$^^wN0}3*tH$MzrWsWhNvkdvjr<_CB96UMyFX3Sesc$@nec!HhwgJ>UH9t zZ@`v}(wUU?(|cH{r9{(Ie4XWO{6VAPPDi0!a~pASDi$~OhQ6#qjJGef6@5Z+`CkosJ#??vnh87VSRTCo4p^hZ?^dt0PemoT$u5Vg_g?g{VV|930A{nl;@ClTnsh$7^vkz3V$i?O>*`J90(+4Fs(+ z>#UI_4tq;!^!t7_8XDB((P(~Cq%7ig=eZOXt&yM}jlFP0Dh;+=O2>|QpZ;CXU09?POW*WU;Sdz2jI_GGN5 z8P+r&yt2UhV?}X75W=HbbK#oy%l=hgo5Ek`x^&lPQR%s~)1y{8PdulvS@@PO6c-m4 z1s}Ed?iV&a^o{?9&I+}%J7(|qkc~4@A}BJSzuCH4@2_E8+BL7w-Ov1eG9={NkkKND zw+x`oGDFm)F5RjDaxFMjY7g==*Q8mpvwOOy1f)tm3#%lp;n8NRB;~=c(mJ@irbK7>Su5Xz*6c0pd@8{lt?SIe{NH_7Cfc>pOly0k*vz$V- zcC!-?*s|&lA=`oQAX$PPKWgz7b4)ze! zBS*wjbtww3r{m8b%JJ_?bv%G?*7X)?LI|v{<%ZWl%hy2(G~DM1H>;%f87XPfz(Qea z1VV#t*e?6ds%Eq9x(xG@z1pfJdrKU3IyROBjQ5J`2vGu$O`*DrZ~br|`0PoV9vnRI znDB)*ECDCHbJ_Xi?C64}C4@{&F+5Wd<2-v2f3D2dy1{yZne6VQwCb)Wp6x_CqxhI? zvzh}VM}it}R2?V8g)*3#lBHCyYhBZtR$D&m88Tp)% zJd%nX?~${|uv0T!SzM%CBUAb-q?8(^A)$ICdfeE7;Vaqkrk=m9&-u+d{>)4W8t5H9 zGQYn;kn+E?6m^{4$xv>mKUfV%vRg@F@qJBB=I2kS#>^!5xu|v|GjbnE7;7VI-n8vv z*+QOL1AG5{#<1R7ChuTp2EV`WwB!+^afzmKQ;mt)cb2oTk))Ni8MMw+Uw^u>@s~%U zkkEc+6J`W@<6yl<7LL1kd)~4d%ud!5k&g7TB63g>C~>6iLU zr4v@CfW)Iyf~Mvm#%MGaiWjliXCX<1kCc^BNak7Io0!>-+bq;6Wo!hHd>6V z`4m0kqrCE-{8{+At+ikMQ&M+INQYO{^lUmNU9}RE)9lc3JumT|wfQq#cDDO6Sp-6c7u<-dwo3fJe9M+lg~M=(4Tu)NHrX80HyJWlXk< zurKC32HJ)ROXFvR-I_G>S%~QoVsXZ>awe8JVqB&b#mVB;# z$H~%vWt#e@OLYQ#Rs8*Y*V3}#Y{ra7Ddt%i(<8vO9n$H>=jBH6l6b`Lkz#hLT~j`CQw?-l$Z!CsEU{zG->k ze0{F()=QL?VN?4zCMb(9Z;7p0%VLx63`oTqx_rF9u2gUfpX8;Jm`RqH4lmf(Pa}y5 z0x!zGUc{RAH6sa7jVT;!1axhtzDdv^!ErT(@~SB5_xa^E2EhYuZMnONicMQmXKHsx zQfDSKb}GLy>zI!6il*w3cV-=#T=uJgR80+d<=+_Zx>zlNuYsZM*}|Z^uGL~^}sV8;bpXKhR18nKcfjZ zI@sMjT9U-wt`B0VIZn~;;y6V{Ma5dT6_o7FQ>l@m-e=E&xjF0L@bkvxG#Sw~FHx3y zP;1A?DS#cVO{IukXX*fO%gVS-SbmOK$YFwp8slp&Ts}vN0|krAHTD%zStZu~RZ&f3 zL^;VpNl8hHCTDMh+`8CWD50nT>%C=D(rAxEK`=GlG*4-?FEWQ)o^of}N30?ns+}4- zJZLY#2}=2Hw#yR1mT*&?tlN(2X(Jyx8D!0WYyuKN`)L4OhA+s#m~cKjF)E_y7b!9h z?RLNIFgE=4m<1-Hvw&8w(4Kg3=rKg!n&~&&=N8`Yi-Z88R4bGl<=3jnhydG;GZ&vl z(VxyQ4MX;N;p7vo6LGxNR3ToMi`g^AFs9>lVJ|q}h0&fb}6`Qtoq89PdqPn|i8Dwi9b4*kz z?TOg9absa~%=sc+7?C&Id>pRa+wavTA&QKwvj)9KC7Helk$R?+p@YRwS=Ic=^owX# zOy6co#qED2P)QwEW?jzUS+w?ti|ijj2g6?Hx+|@%)CA2?iU-~L8uJ@yo6wny$4Uty zOnEYd9k_g#&ShMzGv1Lz`gCtP5?kxu)VsSLQ5GMxy|(D6m zc2Jdry0yt@cUEcT8Msy!jjb8kzdEP5Getu8+uJIhhx-33@t7$L#qb+devgq!z+x|| z9bG!%43s^dy;eCWb1$z%BSsxl0Zi z9p1y|c6YJ&x`5^P#temx2_x0YWpi>jSa7~(M0o0t@^sq0F|UQPp@y%3?1oA zbDiMNUdpQ1wH|cx=l!S}l~uOv`C&H^{aM%*!v+#VLieNv*3iCKZrW0-ZD^RDXwCsY zTk8RDSh+H5ikcEO%ATE&Y$nQAKc76`UM4g@#k>OljRkJY#G1w$sN5#IXnj3P9`5tr zvW7-9I015uG};FMW-qo*odD`_H#{IY56#cY_4Orr zuXC3hZj#Eo-^e@v`HL5OLLWYSC~Y5r1yW)yw`GGcYEgpUsMCN>63rptg(B&E6N6dM zWchp=&3+QUdOLQ16tnhU{AgY3cwb~_>$=MJeW-zuwV@_W&vd&!bPk*=<*AN}piO@{ z#4G`%?wym-nrcyU?A%xD@waU3=^zfDI9HAbd>VP-`R?q$W-sbVRV~c8gp^mhS{?h&a25jGK-uhfjYs2uu{A>ugnx!DF9mYTsg?o^enKYwo=y(heQcbB$kj+o)}$J43i>cjSU9n`n;(@G{|h%WAR*lQUi*EkeH8AS6=wg@TMer!mc8wPiORIO6*0mQnj)p7s#ARip z@k}mD=0}!9Ma-f+xB)I5GWl!qIsdLC_s3hCrW-^m0TGxL>08w=w_)LSzyqftsMM#e zgK+3~`O^lzHvu8pGR;Q*ZS3p0r*^kr795UQFhNN(n7oBXRCn&>Blu;>Sky2f9DvoB zALB|Tm}VTtsmn#JdxTTW8z=Ls2VYA{O5*xf2IhMLivv@uq8}lW&m-7f>eH2(BoGGX zj5e!sC`V8W#k+2Bt%%=WNE@9FB4e1PlaABClyS$l=JJK#!8 zlIjh^;jTUyf)ag}F&B=Ynel_SuQucsBJDQTv#2u%DT<1-g?L(acYR>b!kwJBQdwNG zn{;)7439G8(bA~ixYMw7vUx!IayTAp&5HInk7fWE;*oaYT&c6i$2Xo0wpiq zKsdnEg^xor;%bLgYeYO!J`x)$x`IEdM;cjK4|FKcy8E<~eG7~H8DzDF(~nH6!s0|? zm$|ulFz8zF9Z#jkG^R+fL`rUi6%j&f%er3u0 zj0nT8T!mkMpqD+;zVbMnq0gZzcENYA@W=i4 z^Ms6(3!>}UC>5MjX{V4r_}7@j1bZBvxu?dCxC5EcF|UKwu=|0JDn16JOlJZNYY0t^ zh%tsTkTtsa|#O2&41OrXZw{>v1IyPn@;n&4)#R~jCb>)Bs_BDh!hPo8xPhD z#2O2ebA9C^WIR3~zSwIpekdNJI7Lhs1A*0+9>@_x%7U9KQz77hiF3;k2F{$MZ3|g1 z1?$U8J7hRpz#|>7XK{!kBW-jIs%1|eK1#mB)ZJQt@KC6V8{sLSS+?ZbEcTjM8WXI{ z`0iVK8>TUrzgE-zxkS(ILo9?=D5u`Q*vNnfVvR5@0LsF_aoc~~aX#&StIQzMd0oJ! z?9G4M3wz4g7D%3~lu;;QZuu#~&)_={euR@ZgNk@re6_5N*qEfokt0i<`dS0SS-*_6 zqJQ}_m*FK8mWE9BDo+g_{3AHR9oEpuItSa^-G*DuFB}ZvEjXZ5t}>bLA)KsDYy7Ja zFyy^uV*2@UbPxG)tBQ7vKHxh38IC@FR=T_L{1Y_ukZH%X>)7{wErT#3vo(wf>kRNB z5)If^$jhBSchqqRc7@&d1KKK5QLki54>vhe`6ALrlWN^Cr7-vMYGRX-MJP`^(iXSD zO%<}JwYSt+MbW?M8Dk=Q(K;+;5%Yr;Zk%wmH~$zr~rHPIr@N zM2coqBlqIPi^B8BzfaA47^(k6Bl3M4IbHqi=--Bf7Z^Szd*-;7R&|r%*&G+iZ%)p0 zh1WN!QJe@-Rc+zNC9gj6$=>_9CD&1fIdDMJ>2f2x{A?~1d32I-TVRp5aF9HL^3vkJ z+#A(4v+#~~^vL?JHlwugzYxcJW!Y*jQ#;H4{&bS%|R@I<`?ZeJxEO#kE1~MpQ5R4pzUKjVnu}C=lq2kHcX?JVcVDuD-P6RY&;A zH98Wc8aAJsqZt_de{_8bIF)Jt|EZ>$X*WqLibM-0k?bl<2vNvhC`!nlT{V@gMWx8D z)fQQ@w0coaecp`~H5vpUr{HD~Ms#M)Mb2 zp2ozqVn}82!7gB%bc2I2CY}tm4jO+Sn-(Q}>ZnQ6J3Z7VW5|~Y(%XIaa7RgSAd`)S zE$oen9J}^xtHVg0acet**f2{F0pj-RIIKUKYy)DLIh8{<=})$-ZN>3qkpiX}UXqER zt9ZChv8@n^b)a&(abCdANzP+Sh;`GHNYFBdR~ZA)jn4=AvS!ViGZC;>t7^$?o-)5B z5P3E$lC9h(|_nOH5%F*`*Wu?^87g%(i;9mSgWE$ymsb2 zKUN9x>>`s_-(vSf`nH!F%{~esthN06V99%Vae>77;HGSL%$b?iD`n31IZ@?V%S8tY z*t^zTdTWGx>fluj+oB+*Rehl564ugCv^A!#R6XVPT;Ph)jpYHat&^S*#l`#WkNzJm z?J&3A{hfS$4MIp^2y#7adiHD!yy6dc?A*CCDjY5Lr5U~{m%H;?r+CfPiKi5#931wq zv?lDOA;^ocMRwc9$?jva%~M2KNxA^-Po_Z8rQC z2Men&k>p!L&_8@JJhln4Sx63Z>bb+57zx5ky4i!s{IcKfKZqdFrujZQpyCRwna^P>&YL}?`Yo3>?NiUwCFB6NMDUcC zZB+jb){V4Mgu})|N)4sZXadw^k8-}I-MIBiyIBMZJWHxs+Jm>PKGm>k9NLjjaIu=L zX}#AEWrj3c6>;|$!k&AftitYkU`TNF7YNA(hcL>pa^d352me*M#dhaRu-)NRcC}}L zO`W1dC;_5?Yzu`X#KwAG>aJjKc^tJQSHFO3_PqV5xy8Y1zX@%6p+xcA5r4WYlo`Bf zk22@>JwEm3mC?YuSZV9}(IbC5^7YmnmC1D|?IIpJew!zd93x;QKju4 zYkxPH)vjVZ2 zW|NWGIwd$g+0iiBR#-xnrPo(+3luk5sgg(kD?8nSh{7a#iiI*{AzuCM@rlHi=F`V^46838=+ zA{=@5mdYnbr?t1l=`|WyIE{V2mXBS)5Th`H=gC+I67V-)qMavsAtmWxgiz);BxJ-7 zI%C9QbBw+c3vNXhH3P=FCD_W#OKNR0hF>8*_K7$-Dl7vgZ^Elvq7AO_h;B)m-naU7 z#`kaXM|xivuJ+J;u66!AMY2N0q?9K(<<0sw6e^z|(=W!2Kj1h0p{BQ|_%LPQ@ zBmcDk*TGXeK`*YU!g=DrAbLzR$NJ5W&7g#N@%^<2z=8`MAxz9Hlp{urLC+P`ihh8@`g#R=G%h`}4;7Q0QGUKC zS9zQ8^uK`mb3=9`f=%E%%Fvph*7+URx(bby8Us*kAEHR-;s!JT9)W*#a_y)T4$W2QCiciH zzFpRnx(8+2MEd&em%G*8U?^p`+sbY=*)!-;%5DW;>)hxt=@Hn`UI6RKuKvjR{4$R2 z8Z$ZGRM1$bGhA6KhQ6t=R(v0Qv|)$9v9phj@_du-GmxVKWDzqbf`iq@Tr-ms4yyXr z(gz>CY9R*U_Pd5H)?pJmulzcZVmvrjla`aBF!7>0J`*WT4{Y!B!1RZld>^_t1oY+c zbbW9^v0&jOZurid9PyXCd$E)6hM&J#86M*iVw8k`2WK$b_+?X7VbhE;>Tze!Hv;s^ zAM5Fr80wIq2{j##KW8Qx$fJv}Qr_6Xd0X@oUH?>F&8gHd;*41tp^Qq2Q=2nvF>>y2 z@#1aQ9mStbOm~T66p7DrsLckR2I_fy7bIMRRbC4RNN#ylzEj|P$Wgea?>Jv7Y$a*f zV2xNTZP;owC?_t}gLT$5Nzhll@Ec`7pqOAjsn8LSh#6v(L#-lD!69>z+lP2IIbi1) zjCVq$7}PNfD#zR2G+?K_W#34G8n;~A1&3zff zAdFvhM@ef70OZkN;plfo!A86#nz_xyInJ!iJ~f^!yuJViA-)4L=agj%JeUiAhjaP{ zk~uLJs}e>G{zYH+vCDt4zmwL$$HlfN|IDLmDcUr1@T5o)#>8&azOSfT)%r%&u88`r z8-yIs53&hG5c?8bMy|&@_1v~oa2`gw&~(p|)%I=ncZ6Jx@3r=AP%QF@o782`Zx*nk z|Bv{oJYQrAd<^wA8TUREbYffgw&4XZ!eUKZBkfjh>?1)NA4l%7&eR*=CEUWcPn~YY z{qWc{%pi33anqx#@-rX*;b|#V*^hSy!Z;*Znh>iAV8HGBt@qv$1Z`VfkHPcvh10Jq{QV2Yn zimoebwT4{Ckx+$M-##cmaUWMG_z0YM`LMI{ly$5wRfvhVxA_wLZxl~y&7RU9iB(?# z!{rP;eHFI?WaH!oR*8qqFIpFJ1m+>kBFjuuRVUPDBa5K9j#pL(Nutp01+wQ**RMX$ ze7V|3ZQ-qHQ7?K@sbswzN(!acb-WNbHr4v6=7>+G?Wr1z2~`i2R;) z%}J|=yC-hbcaj?n5AHIQ=%!^G<<%@ZJgqCYXq&{!hNty$Pgj?+2Ti-^d#+dcnikO* zbg^)q|B9o9v{4Ze`_T!H(L#?+>s9L4C$?r>SAFl%mJ(*x-XmD0j<7Up{N~xf@n$aG+gT->0S_J=sjtW(nA4cD*~E>R;Hb zX$}_0L*+2NM=}r7Cx$g!UHBqj@Vz!(p8Nb9*P}myvnOb9-nGyT6NucLd+l)VG5-c# z=a{y54w0HSsnJ)Oi#s%L$)FINuEpW9*xipUYqV)dt3(GHYTvCSqd`FDZf6K28)tt$w|Y+QwNWnrZmh_Ui0a zT83QoYw@3NieuE|YlcsW zR+N0ZhzB@t&X~c>Fa(dBhk&V;IsBWbh|pA;!g$M%oWizubkXqlN3l*7+@$~N&DMx& z!VHWZUY?KR8dJ>Mi5DK)FLtd{<>Pc*`CfH`e(pXT@>SzVsN#3bJo$#r_4{Q^(0o98 zS-ALjN^-idnHa^hmOHJ%pXqk3`DpL}xGe7oFgA2nhJIZNf{+ZELz{~JogHRxK{+l1 zBBKy5V#HaogRX^Z8T|HKZgD}wxTBS&37;YhOk4mL#O?LQ#EUSmz>5?XpQtO_dp0=w zT<6yqZD$jTB*PICrOV(3`MwycRKWP=G&PB|gz-r4;o`0RJ?w|S*UD78Cd)1JZ%EV2 zKf=BJEooQGdg$@fyV?rVTRb1io68}kTbVGRS|$g}{L~Yl`?lAwJn-B|V8OgU`Bp7y zdk{z*VXx{_U+j*w?ob!{Ky3I)3@I#^M&H5^cntx?ZjYLk{WFX|S~i7bFk(ODQz+N% zL~KuqB`YXyO1|rse+6&UF<8Sr_GitOX7S4}MeOhl6Ljl(P(sZIxGs0op)6D^N)e&6 zeWA`rg=!{ZTP zFx=*=cys-1{i}8KXhOhyEE@w|tBs+fFsu?Li2%BpcDBhgw<9C`?`Rb$1Q2Z@%J#quOst#@=Om0rpz|8(ei@~g|hdXr{b?HT0}_} zy3)?!>UF7ywUFD~Zd*EEKjB7JQ>R`#puQs$?qod;dhbQyQk~)AG-UM{mjtaLX@r;@ zicPsV#}sNrCp}-`iP=(7eckwl94~3@7v-W)7_@+?M5Sf6`8N!exe<4TI~xES+!0Rg z$~69nBmCGS(=m0>51$T-3OP(zTkWlJM~@;%dfFTP1ehT<7Z|5xC7Nl1O@VOwsk8N8 zvYseGV|K~gYlQT6mcYIq4U&upn{Qlt?&p3+)-{V>!s(qD;C|Q+j59dR?R4bbX!&lvG5=V7K62>BkSd^kqaWG80cY0U?ukPu ziRj?U(>)1$)9tzLJ%^KQD$9Jy;D0Ty_b(Bu+esB9G{BLD=Ur6d<7}QY-fokVl*FfX z8#6Dqq+1s$BK{YTP(T^Jb(=$auUS(?7B-ijv(sc}!ZdNCu1V-!{_PqjuB|?G+xeAe zz`xZ0`2ct}LRtl7N{IOTGiRJ zO{rM1FOwGz5n5+$D3aQ9tkv@0LxiP=x@)rzqTRo(#EV)2?f$`T77IFIV&4~dd42(l z8YQ@p<7hAQPK|hPpy7KRGZ(D%26<4&aAefH3p_db1!{!E6Z`WX1g3uaL~-9cr|8}6 zN44-n8{c5|Y4I|XrjtMpkX>{D=E;4Kq~CY_(bYw!@d0VdS6!%PH~*N~Zn&9Y%_4cG6Hbly(P5j3 zEpy-dvzjF7#zp8e;3<7qG~Hq1khxIqPrW|%%x_~~5Mcva435qgJVwe(OL9S} z5OmB$%D_mYpx{t0O$;|Kl4N2NJqi8b!VN2T4>l*PdV|h#kNuZ}>^*Ri>Qm{^6lNGo z5MGMH%IpQ#&YfOR55M)|&H31gd4UIxyM{?t+D9t@hgyMiq~S$6Zd38LdyuXvO)^Bg zCNXcRPc@i^;Ha9VR&OH^CMlQ#$$D?9vopke#uHjnuuodXMt8hJE_y|@8E^Vl!4SR& z95UD+ulSk`#7h>|VfKfU&P~1}-B4VKA@j-8Ow)U5Q$a+rA0 z^)Q%#T$TVH-T~y=V0L<_Gu6q0OXWc*9$#FjHNrt@CnFpp!Ny?0K>l%jye}D&`!w|u zo9N*u9{DU@$20iBR^pnQ`C+jvd=A%+p?NpdemW>)Z|#BR-J|X~dwlvfgWRd0G(Q3> zgsAULQ)T8l`{4dX5XKfASZWV9*bK#${lK-Wy0Fi#wa6Y;k4o#V;!OXYSJ181N2PM+ zWYnI+XTELjGIDLOWYEy7%Q`qn-0?_AFZLQB#hqigc0F)v2BVLN501ec8yR_u^&|cY zR#ng}oHf-xjnbxjZhJU#;;8>qPY(nD9W}s-Z0n5U$*!tGR7FMQrcz8yOmK4Jc!#V_ z&xXWvn!WImu$TjMQU~KKq@BMW#VmuL_UxFN+Jmb1@8$EEFpbmr9}NS|yib#PTBCp2 zK@iDVA+SjKM49KVKKCGg62lMpcZ)pm1kwT*5aUJdY@=#)PuX4aHAW4um=+yDGF>2}lF z)Gig^0o0o6EV#7SO`K_UbfBDAYuixGOgUGLoSVf93iyR)svd&nt9#B=KGAXv7&p62 z5bhYg3R}p+eB`kF?gjHUC@eZ~@mr=v-j~1*;b2bb4yv+9s*9$EstNrJAf*B^wqHXO z(?h8%MOT6HAO(Ym;5##hQN?$X>e~cEj~M)i2J%!fAJ`bKgr!2SQgecSyyYh_^P^-1 zU0BKZWs}plM8cXPc9lcLwGoJZ4KDyYVEd1eo{hv`iJ0nIVsygqNbRZ$I512UhB$6L z1_qdzcTjG{a+v9??3P8sG+r;AuB?I_4s?`_}EoCM~G(iQnUq5UIsFY=sgUcyL4YgG#WJH{2@ReM;d=!L5ty}-$M?bi8Q7$_#F<)kr4asS(%yKCX z64*%$pmEAN-M^jOw{uZn4)yzhL1*fUp5sqWZI(P*AX#t+KEyI)B1U|%QJDaQZhI(y zootI)Lh@JtVZVVJ9-~f}w?h@Tb{?N6f90pMU}ws*!@_sny0xL&U2Qq`d3Nlc4X~o> zfOq6Gbdg0)!)JSLO3H0dS@xR=@cQxRx>9vl z)1*rScAhiFt>EImeS)+m>4OA#KCu>b2p$~cgnmV4e&hlOH|#cq)~_%SIQ*P3faJre zc0q=YFxLJGzrAj(C^WikkT6;S2^`l3x;!nDD?NM!vrgrXnax~Thyyfn`<_82O1a(K z#&%Cld!r?qi}BIkMa@PSBsSVD9e*FKtG~MtrL%Ea4GUa@%YD_dMlYVhGB6Wkq=c`r zJy#+)b0N64JvWvW9o32N74ACQwCNwD0M0M##2jrHZxgXKxzt$X%hN}jJUA4r)?VM% z(Wm{0^Zu&->&lF35#vO%2ubq<1hY+^pvCi_nYg`^9l-Ti7;O)Rn@k(GQI4D-z7?3n zdci6x=;xb#WmyBPu3rD`(c4JM+NV!#gb^S{j>yQ!D52!Lss)nn?WhtZb0>83`rP#0 z#_w=TtC_fhF#I-r|K%3K`a-2E2K7usw1^x>ZJYSSIjLM%21LmBgw8BDFNhKTI$4=$ zND5}(Z8tQ3K6DuQ&bPkvKrD8NV1!JhFM%V$^Sf6Hz+0vQ_A6Ra+l@9$TB8Sg=4`+5 zy?uqrMtcD`NRgfXm`SLPU+{h5sqfd)%TDxUm+Q0EKtt$K;yO;K&G8jc zX1xW!ruA`@uO~#kvgADoeo_{ym}!#iv$SM25DR0txyQ><^}xl$+={`pCONN@;*L4Q zjJ--hG(2z>d9(RJ(xxMgXSD?flcE*%cRzV@y+C8~W`Zdj<`J8&v=bI1*f)t_0p^nu z5Y51u%2ZiaX3K&t3`DPJr|q?zfx-orJOr#zh=$Q7JN_dX+j(W#VRLHE13mT$2O3~~&ZHt$*gd$|r8*j>%i;dooo^cQvz{*mX=e8N>@aHEB{ z9dg$RlVir&XE`ik3CLmgi`r0q$NGI2&JU(#|5|!T8yjEQPNeSJZ{x9tuD6cZ=`1O6 zBgyihH&z=+IYRvH75mpoNtfW2Ig|Lnd)2myFWwu5NWJ@I&!X{&N;rcJ2z z#hFQhlgJL|SkZ_QAt<@$#uf%ymU}jDd@hWAX4=R`IU+f`C%YeZz;Te{!p@VqOXE}d zrZNedb1~Fr*|w8;*H=ez^~5d!v`_9rJ(9R*3;4iNn7Rv|p(Z7HNK9^Nsn$U7Qq&cA z!!gcj#Wo-HmuZu;ez{iP+vK%wQ}ZRDWeq(bQZoy;Fa29-cb= zlfVO$m1GB3W?V>K?(=i$u9*~FH?h4^*pYHVWbSJhAJ5vy_T+LWm;#YrDABOXzk?>V zQv01Uz-~|VoXA@tYm#u8-&VjNeXU<=B65?^PTo(9i*YzAhD-qD^4z!ATe@rog^LTz z?iFD?{?ZSa`pr}hXzy@0OdE>>cwBK}%QD6}OK%t1X*5q>c)`3S(u)qH&Xnmvx+YDJ zl=HyTYJge1=b8$hnbJI!FuMe=|6S?daEFdzgi2tvWcfiwHl^z>eKDA53kWbQSR}Oz zf+RC7)pZp^E?eEyh2V@>rJYCnywc4B3^;zz8i86Vgb7W3Ie1M5aZOC@C>rgOPb*Fv z3?ffl#*gQ{VXD=a_npnTZa>yzXs-u1p?%?UR92^RdF*~tldyOW8$ax|d^M#$o0JYQ z#vh}Nfjjw>Gde;^}OFfX|vYnH?!t3pV@#>}fxVb-9&LQ$RxnHqc$h%M5M zQV5^33#t0pB!o}NN66xqtL9(orE6y?;fI!QTGx-~fG{->(LIA*D|!|?F*&^&kzPwQ zvhn*k2pvJOJ_Ef2Ob4Tfc2-X@-DBG z$r@4xH976$U-R5`=^lD0NJv1uuv?`Zr!-_)n>05c{kgAjDt-xIW-Q;WA;oPF7FTy? zwpRu72&!jV#g$!W{MP?|go|&LmLz;(*#Jloi5xxEd!jd!w+VY-XF!Aa)0;q%*v@!N zWAr1~$cSR9!hB{XK=%f8uRNlyy)hwcK>j~|WT$p72#BmCZau(8SO{HKd*x!2gPO&X z?r}z&lil^QH|XiL!ShVy+cQFe(z3xEH%-uCLN36nYBavXOz@Wyf}>BLCJ_0QcyPA& zMeV5D|IaYy^>P5#1*y?d4hKE6qUe25h&mp62P$;T0B}6Y+{Cx*oV)mH9wqbeET!#8 zB}jlR)MheS^ymn)Xc8xWp#&or*4Nb3{0OfgN?i?Ntt|9ST#=yg4wRIjWHZ_{1Jnim zH~pk;-A*Y-lb}0iW5jiaRG#f64D7Np&SP&d-@}NDnaI~M*We}d!2>Ii6HyK6?u#vM! z#L!*w7+stG!6*D9w`S7TMawrN_l(b~(~kxFqNu{Ruem+Bu+4+rO1C5-lLsEr?L8e3 zDjgCDqP0p)8&2lCP0B`GBlTCWu3g%7*wHjmep@G+`XT2L*hBYS>DWKoC1n!cNY91`;q1r67YpUV6AT8EU~Jn36U8BT-X z443T4*y6xN6wix@kDyl)msg74{ZC)fvwcCprNMSG7^x}AiWO56CKA}%AZ^=~$wkY4 z<3iFVf3$H~sHKdRO1G14N$}Z*7x0o~gSV=|^kj?pyO}|c7%3+YnL7=k#D3`auxb7u z;f(a1V1^fzWKi}J!37x9>QeI|up?!U9Dvs##-8`^k!-(C&s| z?0nc&A6Krn|z}Mo`CTk;%2`mal1-*dj6 z^6w#}?XHz|?mUPmey;`VTrtw~&K|(8RAxW|@fl+{bt+D51F^oMgF?1e!+kusM)Q~d zPWj03&j%xZG-s?bhudq8{Tst7wBhfod!8bT&B&Unz-u6dAkcoI({a}YA_-q)Ge@Y~`|tI$+2SmZMAHdVLkxA0oNhGO#DKU>V9uWw9E z*>S1hr-sUe6GqQW+**vYG+e37Xu2UaYvHxKOL?%^vBmr z+2gbKg8T?)WJ&FvF>#8e$HJ3$VHe02wu)(M8%E)Nwrcn7jIHu#n=*}6uo*_irjJG~ zGGY)ALxA8NNvYQ8( zxzbVDF2^}|s5Tz9aUb--U}osJUlqS%0z$?OL`yW(S;4LcW)rcc3rNlFj`v73WFM*D zi7_^tFq7s^HTnRiSZ`)CIa2Ab9}Z}Ax69b69sh!*Vvp}R-(KtyHqB2E1zLX6(`ar- zsblDRGKYIJm-EitYK|wz@r(!*NSbUJFRdO7p{`AY_8NzOA1ld?atgAHFtu_yr)mUO zN@7SPJ?A+(cd{|2p1Te?sjd@L8O6B?M&pZZn@DDHrLPkaLM3a*Xb5IyX(Y^as_SOo z#DT$psATlPJ#Xs?Skb;#_ZECo9+ZP}7Zh1wzH5H6Mq$quRyYFKOy4#1m9tp=#|@63 zyE+cO?>>UWze(!@?7v(l+TC1C1qY)GvW%U2r{mCAa&^p@Y;589P$7DgF^D$wNCHuG zr=!y=K9qTbz&u&&CVe=ViQGO+vZd!p=(0rDPA!Cl`}u#|t}ySqI~j5Ay;IKs2;@<^ zu(H)Uw*B;GNkh1K(BEh`CR!gcIA{e+*GtH{9jDxCWp8-nvANPFJPf*Rx&HCIqCY*G z*+Atb5S6mmmQvyF*b$e*^k!w`L7s4s{mXGZ_$cJkY=T}Lqqo0}>~o5G{$_D=ihu@Vcb?%wCh z;N$1yiq@qj{#=%(^hB)&QBN3U~;wnd2|#P2xdf=0#RhH0M#$g+ZjVG%W&&4| zvVBMuY^~9@P|OTkxq<(2;RIkQg7A8rDuUg_b7`0^euJoh{vMLpyMh_gnnQ!bMAOG7 z=!#0Pvbd+{T$S4i&;1Ie|yg0Hg*uhr4rU645)6I7A>JD;zn_KM87FpxK`&^uT#xLqfc9| zN!_=tiElhG1wNXWfN4!0@dzbuDRvemK@Zm^P~AL3NaTfKf=K`(GOz<1 zg@ttV8+CX=s&(oM>FcfvD-{be{qo*OG_h#5sMU#D$cWxTlgP&YBboK&SWvc)y~S6A z4H1~*GhH)qpB+BU7QL~p5fE~yqVZ=!6@BAR^y80-CxLd~*6OBTz!4)D!R0aoFP`C) zZ~d04ou}*;)a`ti@xeKXM$-g(wp5B-ysG?w1`GN!L>=*N^}oqVp}QUcvN9W zdGzS&!HzQJ4kX@x-g{GWMiw}G_NSJg_o79H4W&#%n0&a7jF*iiY-D>+8;sx{iO*@eeP7=RD z!l3#(xfoUE5f9&X?M&i(n+f|ba?H%mk$BK7j9jFCOR1f(e^!56L~|acg#HxZ7nhr3 z)SbsEelr-0gk4Ga$@m1yni<3g(sx*vGOIr$atlV}DjE!bwR4k%Ar8ypP3I-yinbTT zXbC|liNX@2JG-`bg6$kKC%R4ENLob&Ug=GaCV8|`T2Flr*@R}aMkpoA5o}IPNA;f^ z;{dWnhUUbf6>qT6m$7UDZ*n0EYKXa*L&?nZwYm(s)3)Z*^``8g*G}gUpqgm9w7#83 zdBb?l8(*bFwFLmL_Rp4x#{qWhpkxX0FK>Td_u$5fB%6&;k7(4ms5J48yyT=98hNA zGEvi<4^%_+yJQ|xDe`J_o4!x*pK?>2HEfKzR$YM&tt~GX&Q-sTTT}SX=O5@l@DW2_ zAWRQ5ji#GX0(~?b&fL6$Ws~Jlg_)Qz*B_;HH?f!Ths3YhVpAf5k>n%h=LduyT<6IZ z)i8nhCKQ2GFy);PnDup?gXfH%c2~kv7&CY-IWVixO-_B$AG<_mVd07#Yj=f#+h7R| zCQ++AUsA%GxvM3kn+=arOAS(fb26l_E}=+CrAOhmf<$dVT+Dy9S+3FBbDsEX_ta%V z8batf{zIf>QqDk_MyaIxfg2ZJ#y=$+A@~N?w*qA+b7_H@?^3xvi@%Uf>MxRa2;cVs zm@=3m6ism_o{TP+jRhPW9{;{j2p*j;%7<{4C~|<0dG@Z@~%= z1U09QYt7C?=5(5#ujx?8Jtya(sPt2qbwNBbQDpz6AG{{8A!vcUX&`0D-yz4RBtthf z&Eo#~*MD?PLZoKz;0D{f{w`%`^Yq;@vaq#@onWM|`f#(YX%YyaXaOT;Y^u+?$aOT+ z#xvU_;+KsA_4iSSMR??(wWqof?sRyCo0|rN5 zyhQjgr9gD<{EZ~GSrU^6%>f4b0B?QWlSxE1db6|_E=~IWC~Z#sUxtW<4w;w)@*JO@ z9A>7T|8(an9a5gt)S8QHO0D=90^I?J`A_WQcd*LaQ`VNckYcBsKmXedo)Ufn+E#=1 z;@2$Y7+Z=EtIjzxib~SnbHr$JEG1M-+le1n`e@dVvvBus?^R89v^5VB^|u~Zc8+ZG zJw+yOu(KkQH%zKDvuhWxIQK(b-*NkP1K#2>sDg3kn;8!`Jb^j-S?#;)8672_fGYKWm!O)^e5Q_-M+t4IsYptb&DT2VJbg2sC7mx>k;UT9i#xA$5ydFSBOM@BO5)p14j=<0sBr0Y0ag3!N^8n@{; zzLNA4BaeSuNUYg`gekKY{#!7ebin9S_NWZ#0_o$X2_^bR6L&sT3}zyFu8Rv6zk{)5 zRNEJPZyT9iuX?YQtKVNt0Gfz}J>L$h*8NG2wH|wiBP;(r zqN0)*hp@k{CcVv&ZjiZ)+qgoW+qCg3yQP$SbM1?Xt!{>tbIWQXm`3tWO0g5*Cv4aq zp*F67pl<#F!j!%GGg72nTZS1ToqS~1vw;LtIM&7P;*fGR8q%T=0-Z(+PbYClZJsY*Bi_u+?e|~JuvHBt7^44 z4t;kGv_qY;NKx3)**h=1ZEhZ;RZ^dV_C1T1W!-VdDPeB5b1#D4eDv+K3ZP1yLPZ7A zOr-RZlA`x)1h*G=VQVk(8XI-v{pFWwg>XG$62oZI3X{^w)9bs3c=(1gMrDcE%v<<#EV zezdZSXo)d)w=lXo)87q$yQVdNxInV->qolx z4hAAE#e2#cei6gQK3V_bv?3vTw9_D@0`7GxAJYBXN???*9|g%v{j(_DI)Zyw)QnvB z=d)sAGc7Gw_wLmHjztt; zaFvw?_m`;svNCMw+&sMJ+i@QD|5rO6(zPJhnJ}MpBWhYevRsAgjH{7 z^dbrj5ta?`N$>@)W*3=iy@0ivR-4P|Qg7lA0)*rOGRGV3EpDdGbNBxzoQQ6;=+=3V z)pKCLN|vdB@h6H+&BtXr&$qP<6j=|3O&aB`H%|WcVpg!dO}bWdFwrk%`1j4LXP@gU zr;tBNm|EQY3Gwmfi3bM3OaM&~bPe{L?^isUX*p-8MKh$fnO((**x3H3{=u83>>%N5 zC^>LyYq{$9iT^MWMc+<1HAAW06z*3J^ma_^_*I}5-2BQ7pf4sO&7MglPtMZ8DticZ zFN}$*QUm{O8Jc{zO}%ZjVRiqf%fN1H1{OPFZUdH{beZ!)j4ml}cOW$6*B&J8Jrjd+fiI$C9269UcQoQ)?s5ifQIxv{_a@>L zfErEBx>PvtR9AEV&}*JN4YOSai!26+W^1N4Ev2;06;n%kpT@9(8eS83g&;@C`^2O+t=^lXNAWo3NlVTx2a*$`rL<3m3u@F?W{9x zB3;BX#L3(%dpRXpd#-0(_XCTQbl=`t4A z=Cbq;RWV>B*#Cx=>2w(J7wmKCbJ~r_9^&>ulHrLJ5pkx0NA|-ivi#wQc_ZG9*cHL4 z3YRT;C&G3%SsfzR9o!PsjfK_85O&Jmp_5ZVBN(#QHYaH!C0Rqi zqEG$O9^UH`()EH7UobSGbK zi|1>`dtl%HWLB%}S>*>UE%XK=pi!g87=_ZcR(CDj_@xr``ji8pic1;p43D0vh33aa zpxvSq%?TGGzCBLGB-A+%)D-qmhixrMMQqbQYCTFrx)Xl3N~5pC`dqNfp=>pvO|iR< zy?JfJSEgYkj#1@Ug`X=U*%XNy1fm3sheer?+FLXdcg(_R`KHy%Qj^`eGS0&>3}MqY z@qBFQjgt-?DZ>T$y+N^T#gn-~5V#}GYRgUEIM zLph=DUf?IRZTkv8Xr$q8s7oD1;Q|z;aM-hWFG)f`#0&FzNrl2UuM$~e*OTR%3ztZFq_{9e#jR%=eHe z``E-W9u+YCn)7QhoBH+Z#oaEBny-965h_uTY5bGtyZ&db`!$X{uSD0Hp;Xx@dXqT1 z8X6Bqmy6c9&Guru(TjB@WALKZJSst>-}z9-9WDCP57e4B{4e!0TrywXs(4wt{W>_hCkg#1X>@5*z_1%^9zJM}0_ZfW=D853L~p3_ zyy15dA`XWKuof+n>nD11oboK(Ho~ccPzqwQxhagyb4P1HobE;t`h7j?>yySl=BL7o z`-+eU8D@oaK(2coS6JYMx#|s9Oe~@YQNlkR{W0r@Ti-RU;7M;|uftmCpaI;C$*7Qw z@?vsEX=AK~Z_AO-k@l53Us7dmV8CCOJE`E1_2a8Kj(yYC#S$M?pRw-)5kt6)7vj&X z?k2cg5pQ9bc^QKCy~6Z-$Wq>{XQhLHWBB>NvztXPBwx*KReQyrT6d&&Q5Q%C&ke-* z3qekqY~wuDHMvG(QXu=mg$R0_#vt9wwh0CRtxz%o@_>1`z1tgmla!p+={*(ykocbM zM6^UpVm{mHGGEA=J+#fQBXyJwrV>?G+F^4~yb_2(IDv&=A|)8%?hhVMjIzqyvHT0Uj*@mWOa zAckKbv>&CW?nRcA@;eCZqOe$D#@3cKLA9K~z9u`fqU7I<^)n21)ap+|jk|0Eqh=C_ zZbW}w895N`F*%w`bIC5d0M~FORs}ww)zOOd_wwS+{FZdL`ACHe4B2Bn zh$FFOtB>G2TWq|TAMYwSK0O{5;QN&%eWNz8^FXO^Sj#LY0j!X39S;UM1t1qkg zjZc9NAkzaNn1DTF04UW_dTRIiPdO-O(azkoi=^(@&h@drjg4-2N0^j0)8uI$)H(?F z@go*A4h30j6M@;*dWru^UB&bl$-n*E)m&Lzfd%$SMC6p$ztCiVD8FoOZw6pxg1+oB z&os*gz+~r;M(Odje)UiOk%>QSd-k<@-w`q)uwqCDTx6(wAwMsJ*Z*qB+qPl%@q5=H zKAOI=p&0|IZc$d)$=%vT7s2owp=7EobcVrYq46tdvL&()3Ei9SL_XJdwziq$VaaDW z;359iY7ndk*fcDx!()!1Jjwmo@Hxm#??1c#I8Yru*Yq!YzC*tCba3(_3Z!eSuKTd4 z*RIt$zdsB6$C<=2w<&;gnpR;0A$LXW%>i_nKAPaAm-Uu2Wxe_h#9{v1=U9tBlmz-} z9T&%7$HoyUmJb(p6dJ$$$>W&mHH^C~zdlJx#JS8780#0bL4Z#(b>G^#)ZDFZ-{e?+ zNk3Zy3ayT-LtglPhJ>ujr1kq;C&$RpC*%~;S{!~HhY2D^?|Nqf2T=X?*ht8o{DB8k z#aNRzJykUoh|3vKJv+_VS#TaPY$6T=brw#r#!xB_7UU(5-NQ1FO`}p%Q=3v@;3{9M zP}AH*`~s$!4mddECAjIXp6H!Wf+(ipah#gT3L`Jv=DFRiEyY|HH3rMg$>kDHDw>SL z?uZ=^5wZ8>b?H)oKV_Ow7zlmd2)Kig=@%5<R7{@Snk>Yp-*Rj97D6`4K~j^SS)Px6B{{xdEyY93u z?-z{}f#P+0zh&=CoEQH0T>Clj&Fuu|V4Hdu zS(?T7g`SrV7`YX?D%4zEj@iCq_f^!MjE~Th6HWxi0g6<;lGgEa+27ntxfS`p zt#~X}du@NepDdzhzzmmxaG*Pf_6o=$X~>BNZ&)uFZO0E2Z@Q&sPODj!LTTxenP|!p z4%{KwYH4&J!$9YVMaW>O2rn52V2F(@*|K~*PIv9y=;p~~zNBSas>8($Un_=Ynk!$b z2lH9#yzm*|71!o5_D|nKXR}7@I0c)7&{Vb_4f}CS%P5r5bESO!8-X_-4D1HPHXUMo z*+M#8D=#Y}62yy?s3zkK=HVWyAfZ^3NciaowKI^Fsqwx0TW4?maKwuFV0 z&6nqJ=V9l5F9r?0^*H@y(CcI@?j+VKutCUG&itSV2}yfd7%Sa%Q$N?$r#^@7x`{%N zdrGNYo4!WtxcHTK1Sx#Dx_skT(Y}C=Hzuh5g{K1D#slx}F;;4t>UjZuO?MQ>*0si* zKGjHNZ^00}jT?5MHs>? zt@7u_Z;Arg29ZkSIK)-Rdk{*BuZ)*-hikN#$B)9kiI1p{;nXdILL*O~_M>*+Ur&ab zUinfv! z_9yy#p3Qn$7y45>3LE3Vp+0Jn4YWOv6-Mv-Fmp2mT7u8g@%CHY^QhVt`DR#kO2D5+-ZENlc+nx>SDAawXRhf(QxA6Vxey2Dbxe?zD z{?L(C@HoCsV!lE^#F|>_7^CApJJhD1^V{WmmEhyUS;u5qzMr8nBSmi!oNC_@<4O4V z$r@mPJmmEUY=E1-O`WRTo!`bHIX8-8>on1eB&*~F@9#~1NJ3NT#fw~@@9kK(w`(*$X!3SB;{bR7?+LcS=vP0qE!MPg&-1+c z)A6)^^!ppK`-WC{ zj2qfFvS34)g7vKmIr>Z5zIJ(xfy={8jFU1NN8;&+SlpL7V|L<|0E1}z6${))=91Qp zKEg_om3E1)sq zRE+D;;I$G@s%rr;(Su{}1alaS!AT`i0NT8D`bo_-$$+mW#~H{p>v>qpSM3Fo`)AKS z_hl0`X>8!9v89WQE4x-Y_QB`i>fOIi9w4*rtIO8^Nff6fiE4Neo`568j>GSD;1q{g zbfA^-UXaknl@3H797p40h?Ee(0&t%&W~!3+^IOVW){klhlZc+Fz&%CSujxNPZW^pmj(d?hm6L?#rY5wXMZg+b_fcA z;?N66YjP<@Ka}Lf>+wVpS8OC^FvB9wIRB|y%-5{F0QZI}))7}z!laroU5cSV-cT2V z95N9lHbB+J_i*p}B^3VP8K%y6+H-r>6H3RrS@YeryRu^#_j|f;4nM}gc+B(I+e^%E zT^Wu&i*R!~_^LbGk$dxwxAWyyp6uAnygz!WyzDLCp;HReW%q7y)Rpv}4m|w6baYU3 zL7@?!HJ@Bl$bE+nG0LYCYxTIewYLGI%`gK2XJW%{gTD<74Tk}&#sE}}pwi`gQVXuB z^N*U;B|SiX^kekX9bMz?qo?KNJ+YA)>VDg_A~CU2mi6A#ua#vI3Q*_WIZdIQmpO!> z;+faWi*|kDE+RZUHq;Oaw;Y@=#*MBx4s7txRr6NzL#w#uZ(Av=J-z#9v+DEHs+o4Z z6Dv;cxXG)PddR`)nR>mn zvhu-6(5&j#x45F5EW zRJ^BmhiQbzMG^bn4=6%GUo;y2TCIRirg-Y=yv_5^Z=(Nqk{-^Z%$ZuM2M$~U)j4{4 zYQhxAO3+@jAVA;`kuK7SmS6`$<{{dBuuU=OF?=My*2D$>xq5Y+i=a`#Dlu43Ibwf1 zhM}WvZ-dY-%V+i$XDYXzd9&!rku6Q?lnNii{T1QL=sPg?AOPH29>;b_(MRXac zY4;vHc+dspCV89nuQ#iHa`O35y=gDCFPrl=_r{=gW2p9y2M86?>WsslTz!({^GVn$ zkLG-|ag$`Z<;?A(M9LW$MCSbXx-$f05kywU;6RCAy{$;(u;pJunvNryg^ZB`YNhiw z*Pq|jDY`Xi!SD5~H^9v+48p4YB$ci`5m56+zAIQ8IGDd*$Fa)`5_`kF%d+=7FJnqLfMS#%Z$OV|Yy)3@bl3n28c}2%7f2OJ`!@|@6LZrqNOO5r@EP`csL)uc(D&WO&Q+YNvq={p6N_u z0m{*L*NxIvZ~NX(p|EYdjz4E!`y?O8t418Cq)p?>n$kv;LPgJT1O^)o;@LmbR&djE zw{azd^R<=6Q|o5d!^_GX7pE!Ibt&d6Swug|i>|z;z;koaa;xT?4wf^c=1rR^l5K2+ zwmkD%eBt~4Gp#aDjLXW;^#R+dB+D~W>JP=cf9;o+yO3ITlU-9nMw$*a9HGi)!w;ATf555Bt)HUGXHi( zs2r}OazD%{=Qp%$rf{+^ntz_pV6*#)J?Znm6fJhkRSIeg^={03VD;q^>jFh964ae+ z-Jui$^4Sj%#yWQ{vzNAhdT&AM=Y6Z(0~i)uTvgb6p=ol_=88%05RmhN_E|>qV3~H) zP>aYDUDBb}@btYOMG6~q+?DE=yB~U&7(RHzWW{C^H%aWQZ<~Jmbo}RMQ|Zp`MdDDR z1qoV&s;w!#AI_^Dd;B=HgG*D5ALrY-HgJN zRl{VBxPRb@EDh#twDueN5~!N(xL;q4^4r4vOQi?vPR`wx^D^)Wc!KRDKSS(v@j6e{ zzc0WCg3~m$m3i zC8hzfg$;`+kG8C!U(?x(tGw(!JoI|ANA*mb;Ars(rIm1lqBYBopaom<=e~>Ti)W0h z12Vl7ZyNJveZLf+-oAl@lQRa(3IgV%1+AYZ`V<~0C=>-MfL4-RJzw&OtN*k4+y$l-Xw@aDrdwU?i`afNTJw2y z5wCsNp;WIY10#KIH0_DeJ`H3%4nsJv#o5lV=or1c7``g{(w2j8VkOcW~mlo$8sjSCbn(QGR*&E{zfM}GyjHUYy z`=>Vo(0YswTk*=0HI>kCsG(k*5L`l=*<{<4kMXZe#~eo+*Cf@oTPaLj7Xc{eJ2%dw zNdC6vpPkR0YqUF|GQ?hf@ru7sJDuLwPhU4K$U}_5_7PF{*JM;ASQC^RZaae}4&uWL zoT-!oVL8>D2II^MBI)c~G>_tO0!P#A=UeqwDfAY$#vh&RUH$nz%}q(Cifl09Kc40^ z^6gBByX;*R`r8+TJvL$4+dkTh!^xBXRYVGwo?@(jO^nY%cF=W8++_#4}cUPRw z_S@V$v%g=;cCsGrZTwmhbPxbKfXe5BZX6B|4Ah-U2xyJ(&oK9<8hf@?9r>f z*Jrxb!CZ;H#n=^O8m*W8t+5Hxcz)y3O>4Ndj)D4q?1}1w&eh(GSX2jmTP5kI{{aur z_LCanlJm%Er5L=o8i#A#&1}LLuWWvUYQ%?yTk#&7uGRHpPyBm=e{5IgLl_Wq#9rSgXCRQS~&ff-?iF%)vq6bAwnnJiY(*m zNa>~UZ%_uUVnVYSV5!&rzvSE2Y%jd&AKlQaX zQ2w@mkYFGaxBre~&n&caCkMQG;rZV^Ql%^r2g6os+U_>RM@p*szP*)p51;D!Z0KDn zUFiQ!1-Ec>kV=K90dBW4LXofBJ|Se)c8J)IZK2v3M5cFAk7?Lz2q22iD0mUYUCNsB z>a$gPvPHAFDc`U6;6T@Cvtol~GmpxFAN{g;ileQ|QY?ZIwW9{dFL*or0k;o1ME{-p zVVL*wcRssV5$cIv9sBE<64N)Pr?t=4@-%QS7~nRT-O{C`2R-0eAdBL=IAaQ(@fiqz zwiMlbqM__KermL5-hybw-~&mTl!CueSw}<)xrZN8RIEnh_1~TM_bbW#XF89J%6%Da zmJn;+dY5zS$F#;;H}lR*TyO&4b!He$NCW?90aVw2d#{i%EZH??=G!pACmuEm z=VoOwIa0iVfqLvrV}e7$sNJ%i?L(S9fjDfG(?2BN-2N3Wnr%8`PEy892B=+>5al1E z74s>2oXoorBe^w0K2U5Z#830P;+Qe&+5Jqmp+S9>i3tY#(Q9NY##LIjp7maxG;WP# zAZ@EI)Q*K3NvAjlz*44^!z9AA|1BY9-{86v;yL41(5+gk6{rAsvO}1;?cJ;qn{hUs00ov8MVkC}-$QRnV zoKKFViw&bV;T`@h8~mQzYwcVEuVjt|{rX5%pqcwT9o7&;tz=TNXC;{WpqM^ICP;3} z7ifpqKmjQJC#fKz>(YGz2c|f#jGBCxiFY-zF)^=!#=QYqP&?wH06!D}P}q%Z^&)?j z#4q2@g;uhoF`NW|vmRKb0qrfw@9@x|Jo4J1GP?A3r(`Gj;TA6Y@tN#bGTHt8QG$m^ zqW)Ho$SX)w(+OXi@CdJTF}{m=48FXZuvP0p7zOKVK=wL7Ee18KwcpPok=bg8Sx~2rU}C@SkRKE<|KY@Zv|JXa z@!bFtl4df7taTE%+Tme6m?bGi6eJNoj?<>@# z_IEvzYd^RQdtZd#mU4-G_!A_N7U|N%E)h9V4_P*?&uQwlI5ue^l%v=HvM zLAlja_zdUE1+|OWIukEPP+ykVeN*XW|5@C_4BvnPESL(B{-Y$Cd!M!^KL zLX)@tOLoX-hCgJ@6zKuKZh`J6tCBze_L(-IQ;L(d8XMk@O};f`(_O(6DcN=MK*J^) zyjx3;+I4>mGX*yUNt&xXvMbFW)YrYr_xZ7f(aOqY`_1V-?1*NR>o3q|FE&DJoVm#V zwZRXk)vxq-|t23FB#AXs2N6-r(}|$8dIL=EoAJ-l_jP-FP^zJIu!@Wuf-mS>l@r>HYPD`PRrFlc;oumw++uP~Ynl&Z&7qSzw=KHdv8LyVUuO*3Mr#&UA<^|n8*TGqaQ348C3(1S(n>C&f_q-S zayjws+JDZQm&iv5Y{^LFQ0>wLoHc@3UAc#*_nX(qS0JCCO~^(XujA7E)Bh{rzPrt? ztEix`GG=pk;VF4G+B17$prvLElA#jpH+CFch67Om_K_*a(_N?k6!w><5pI0DX@5s* zaRa7)7s48tC-n+&JLN;ZaI4*8_`u$i5 zteQYBps3GMBFNfKzIFQa<+hhtpztD3&+oF=9FQ8zh%428I2&;B%K{Hr1%BWxNCDQ+ z{$7BNK48qUnv9$gLQ{X^+x$vSO3v#81=g!R2LP~wbpQ9yeY;V9-x##W4Wl zEM193PrZOs2J9cQQAd<8?zs-(kd<>hX;s+sPnZn})y}(fStXfY0jaKlkwU6UV6n7M zTzD)R9ufB??v%4P4t}gGCQLY1?pYLlkd~tIX=6hYl4uUW@jqic;0Z2CakJPm1nVsf z_oP_`Hy;5MaOI@=lq#=KdhoWrN`~ZwXq$xj`@?ZO*&XMIe+zjOXMz_#PmFvsQYy<7 z6({I)+_&)DOJvt9khbd4S8m~9OHdxrXdX!f{tji+M`-Cu?oRZocvAZ!?N* zw0I;ovYTX^70c2x!A2ro`|(Nr0g|;RNG#9SWck0bJfx;T8@ZZ%+ujMB(G{*bV0r+* z#zY+vR+s$%snO{#{!j0b-4DY`>zzCFyB|ExgJbM7U0*EX6utG~6VN5~3+qFsL$F{2 zMyitx6zRjhr#E(F@3e%3o?;V>nI(RPpv{c^r;gZ9%lewi#WGL1Tn>?MhQ|Fv?GgSbhZiqi zWF0(m5@`Z}!3gh%EbhHHsPgpbQ&G2XA)GK@*?GKH9h$<5bSeh5Gs%IB0@_~|h^Ycf z4r7A-93XfnIp@Unmgx+As$Vb;+xz~}(&{x!jdfTm%Nd04kpnzA5mY4KV!ur_W`#&vWwjVAl#U-ZNITqWUAn5YqJo)liL|a?i zMM0;;$7ksPf%CEUN2VJYpgpn$l41ua*|hF}j6on^!wEoBfH1a!WrmK~wY<z6bBUgv%21u#f~32maS z@g+&{VIoCHnAcn?qju4?VndcHC)P6;t+wgj!^I6r`&TazLY{A2uh^Na(6q<4zT&*f zsidkp2%u#uYg+?oVhk$nqT}=?n@}kBGGx>9n>`@{jOxhx631?9kfGlVbG|K#sm|ux znPkcTIZ>DTDPfkCms+TUu95pAB!KAEWcV+z!csonFe_bC4uF;+qz6>iT$ahY9g+3u zRLcY}Nr>@(pXpuXY@I)=XP$@thyuQ>+c~^BiSo zg_10CpU)YGQMPaMobSY)=_7R;(AJ#?uxDYks&H=?r*^B3(|CNK)3wPsEF#XJ`m`^U zYd-&q^qM})F`~Zl^ufat2mJyYyms%}`9><7$5>ySo`o?KUj*C6#pj?asc`{^)-jmS zQdYGKsAFLK4kYzr>u5ErIIS4tE5KBdg#3t^c$B4Y92k~I&cIL#LW1{oW`VFZPN#ee z>@1cFtYRbyb5als`jN}{tzcAiQD0#)0^uXTJn#+x_{SbPpmHifT7?dbU2*Ce{o-GGK zUL?Zqv^*g#gxui|Vnkk!z6x8l&NMM0;vS%6_j^?>%nrYNEpkrqZxwm7!-(j##%)y6 z5sBQ|kIewK>cVvRJFp@S5818|u&KcxR`+-rvtahVrH(fqGzR*Vy)w@G!TEuH2&JJy zGY`?&#P1fH2Gfkn0kcC8@a79xlxO3l7Prum_jNwn!%604Z{ygszs&7H0}s?(sXk?wGDi|*YCY|)-K$AE z0gb-r#a@pCFWZ@nW=~1UBJbY+81|^%8hOn7`F862PqAA3-2jdo7XqL-wrYvJ>BYCE z5BvCvF*_h${|cweVx4Rk@Gi-AdQmTVV9fj9KRP7?^^z*CC z3TtM6zgy)|H;TWAl(`uvGjXxB1K0U6=d&*Gj3W97#S+J~!okU@3ZJbarh|AjgRqJ* z1(mN32=j_$y-6$54S^tl(fA0rd~^8=d$x9ARx5q%WB}KqWX2sz`p}3fUz!l||L~Lu zhQGKVk1WCsi)3jaar;{@Og~{-XMP>lYGbJuCLy97cML#8(@gk9t61S@-QAw4rYVPmx3& z`Y+G;ytgnRe{I?D7P|(m_}x1|B)0P-#RY;3dcQ}*P{RYI-KQfYd{>Mz{GeVqu4wqX zV7z?umZw}IzkLiW0GQ=lg}l%U7Oz~S0#cZP#S;UJk>WAS{v94*rTLje02hhjVkE^_ zHAK{v{n7>v|II1rV@aYY|NAIp&dUabpP&4a;;*+gH2O}q)lnPOE{&r#L9d_%R}v37 z$JRd(%#DLjr^i8PTe|||D>D259MKZeQsa|xLY(N{-xTGy_x8&<^*v~3LwDZ6)NQjO@S#34jZ}!LI4&T0PPPsKO zfG@a;O`A+)9-r<3dZ72on?_jNrB!Mok?|1JdP55kC8{1bon@LDhq7t56W4Htnp+wQ zdBFy-U_)edMji^v{&h447|o0wtnVJUqVr%H$s*AG2t1E8haW#}mkNwQY|l{dLWCFc zJ9ol5QWrHJXNF;gU;ticH`Q^UI(4Ha;V>S#BJd?>l~^07*1tdzXI1&zddR8ZPI$u zpPpCx#jDq7MyKI7&2zz@f)9KLLufzLYejH3k)u|ip7}T~un|_bD7Onb^j{lWmkSeg z21=`(Q#>l+H(>z_4*y3UGjqz`zW0r}&0V|s?Zs?z77BGE&IB+Qy@En&{NnaDfCAN6 zgt!Lh)sUJLF$*XpR%i}SoH(HqhXeFyr>I5inox^r%2Wjg(9_N&J~_Dal}M4W3;n!R zft3l6?v6n=Z-0N+*Glb7-nMw@>}pu zl?6J*1&DeBE}NpN>c-A4dGw(AbhbR%K8}Jdm^+_YC0pW?BY<0&aQSs*Rw_Dy=A)8e`<9}0F1 zE)17tg0f-;mv~`&G+=XaDUrN8r_N7jtd0F|xJ9Vwb4Q6gO1_xo5+@g4kM9awUn1S# zNZ&gs+4k*N`R6yBpvQr(%t4!Xf1$9H9|GXw^g7fpgUQhN<$-fd26AmTzQK;q?Ipio zISfe9NZ_1uIYaW`w-hJIgH>7*WP=wo9%TQ&GI{Fq2xA=As+cRm#rIWpk#YtnApHR% zo?ZA(q3oR%J{F0!>P9mWxPb`Ve&zzNEMRBlSW4h1fg2qFY@rCs_hXgxnrXZ7hAusj z@M0DP6V0ntuHwvCk{Ayr&OA7BYMk=4O8Ha5K2sXUBZm{*+dR{#K)Ks0oTp7d8@GZuASJf)2~M>10r zI6NHX{Ch?tyllv&fL-@|nPY^un|(EVxb9vep+2^wWfr{e{$M}UE;bN=%Mqj#RZ2!@PuQftXQ%@{805L=(GWPRGQ?7SDXJ zGcU;$92!q4z=FUmPkp5^Lm?LF3*7$s&7)tSZ9aX#iTS7^$^TO9T&jdF(z-oxj<{F9 zvww`M!&$t!Qw&qkUp1>J{~~?EH-Q@hu%>BWzvPyjNQ2{ zJvNbh3ejWEfexbFZ#)mRPe29BDc3__><2QhaC<9yDOmS*=*>r2AQS*VVWB@7c&>#> zV+4G!;TkVKl=}4dNsJ~73%oWib3`~=c^V4r&d?A`Af-Y3o?- zD@=B%#i{uqbYT0DbW)s@*Qpo&Iq>}E>A$VItu8Hp&`Ip{x^WKfI|ZNO2;~0aQyqs8 zVNIcEox*j(HSK~IphCz*Zka=qIw;W)Cou>z>%D+}F{CPgqI8KU#G)$SEy;f7!ZiKY z_6G58&%N#TXDdGb59{yXj~!6`2++}*dHx?!6C9OLI}pKuAVHOv%fx7Vxu!3&$~D6q zn0i=gV4;0N42j~3X4r>r@Vl%dVH}LD$c&~n=NOSI@1|Pz=28wnIe6se1mcb+d3S+# z;4?$;rg?@Rv;HoUmSS2DnL~#Xz>-9Rwb%q4#1008`9n#AX!QzTZy_`^AkO-kSxICg z8><+Gveis<`u2cs_M=nTYElwu@-A!G#lg5P30;J5dH=8o#e0avBl3UXQR(3{wwzhr zY{lL~r1E?=wd@++%LQ+E9z5(yyUb!(WJhP0F24Y1W+kMavtWU0%ZR1o7iY~u+wgSLZ?T19&5fG?NdwoL&SC8A29 z1fvve=hOgAvKLI7(Z_XI<}@*$7jS0tUhvv(ys))0%!3f*L_&7Dgj(c7K$kq5FVr0R z{gx0PjG!3^-qTO!Z+wwfHbIAK*jQCU50#HlG+saMYzJz-0?GwDDDMD5CM`IQS_N9Hbi!`$ z7IL|%-giUm*0&Aqrg<^f_fSXd!7V}OQN2DuE{hQscA&HKxHGmo3Hn8G7!PG!2iq8n z4zJ5=m!?~tEbC5UR7t_62k%epm*;Rdpk3;X(06Lilf0(-AIK*pO4uD?p?xj3gfQ|b zqW3nBXCq&sEY9s8TR|CB)LI+MMP92=cvGuRb|^q$=Y^y8p+dmLH!mKuZ7=!51YL4S z3LgP5qyPffHd6`>bJDo!O|%qq+qjlc_x$wT*f)Ey=W_vR=HQ|TK>mT*;W~&Y5@D#$ z(2MJV8Lup+3v5Jez$UWxQm{^!c&y}mxrEUqewn)fRlIRPaevm+{#OqGBGtfQa}uRL zr<^8HXZ#oXJ%h5ToCOjV@CeDxcWG8GYvuv#F}EY*Z8}dnNgxF=EEn^FiJAoFD!DSqOOS1zn1vXUhq|H7{!$7ABL zlDba0yw+KIH0@DNlh;+!uXE^K3fe>0UqL60R`NWZ))*?$sB!7FPEa^<@^Ez*$R*j) z0bqjw`>1YCt6HkC`I%Lx9B}r?16K4ENy90w^kxg$$Hq56GjA})!=g=^p*RCFu>yL( z8M5{HzSaVpv$BW(a5P;Sh4GcXr8_m!9&7VkFp;tD8Q&VE=F>p1Z;Wg_vC;zHuj64s zzN?8--`Dc&dS2^Dg(d;~ZK*AS?6j)_T3rIdl|M{B^aB0A9%Ca?)@I|^S(?}V=s zpN%|lIQv3GH`Acm^wF9G#dt5Sow}JU7o0^_f?wxa>O+#=X`&;H570wZCX#(sQEuj(3bt$7+Yb(q9yXH?0Og= z83t5341Ce_{DR#~HLlkHd{_qmt2O*;P~7)5@Z}G|1O$F?Y2h7yK)o>pv;T4+TJ`lbPdgk zAcMXf1;-&pIJW-ioz(=Jt{0=#aiU&sY66aR;jD)Z`8STeNB?D-QxB4?QF&fX6FD(c z=D+T+`0{TP8tN6WAv^Rm``7pEJ>rCRy7=@otOHIdn0*^Cx3d0ldmVnc?!l)Te1|J@ z>l=WG(7EpixBpr}$mfCFNPZ71?3U03Qv^NT^M8;N`zE$6WTN~kZg;9BTWr+|!<<0&gm9nRfSi_^?{=lsg^ny zQt|Ixdi-<=A5Hz{Y1~_GV;1^E|He zPB(LN1HgUe&J5?y{k;P*13zwQ@yk&|cW2B6*#Jh&hyZIhP~fqLWMA0pGT0BYf(anV zk;LB*{h+vWi;()bmi?-agD3}UTUwM$PPe4y*TjNZn>5G4+QPIHz?cbD^s8^@f;+@| z#+kB)3gULucS$Z?1oh|NJoLG@+8H^<_Fc5BS8r_(HD%Rce58WguJ-z36kzK%R4a61 zDhTa`A4PxM1IN--b*Z9z5T+1j{}zvcmgq4lLkSc!KcK+qftSdYqxX|Vq<7l0 zEP2tHOYC+9i2=I2LdpZU_m8rT>$B9Pyw$MT#$M54^YF>DVf*Ipp7fFvfMK#s<;C&+gMBc$8S&@=4)vmszTVn-r{lzK$RfSp=IZoO+&p=3| z;HU@V9DH)SOSKEDL5i4v^U=v84L~dU%$K)w_5nfMfc<3m^V`lq6G|$Mhvv|0%D=i= zAXZ+>Iyn`M*0{Q=0F6BWCkf;S04Eqwc6WO1ZOs{;n5!B02>&uNGEaHXc~pL9L*djv z@_jkz+&K2^43v*rj@bj2p7HCxyR}|jB2%lRheM?QNVbtPbr#TAe(!>MhpiW^8b+!0 zYcUt{y0nb_u@?x-U1>4_8tqKgTzcFJ0UJf@N&EA;7-${d#dP>WA5jAh6~fJ)-O1k` ze|aM)ri1J+u`GOwvXku5mv|1x2<9Q6Y(R@6k3=!RVNb#6pRNKoWIsE?%Gcq#8n<^= zeH1~J1}1;&h}(0W6qhzZU0)0i5-W5u+`D;1H2?U_Vi4BU?ZzR58nynyG`u0I((|~T z(^IYVt<82$)&;(Cg*jalKYtO;<=v?#+5YR}16`e7ZC9%W*d4O6(Xl_5trk=ggXKuj za<4(a2f+MgE|2osop+)ae4($+|Bw6U{Q2S;;xn6WpMBH%YPc8yynf?31%6E2rR|0wdMr2I zgAw0@5dU)^pN_Jnnj0Y*LHoKVZ!g|6c|m`YvLqxh#V%cY4QbXxet^1tC76bUtN`%* zIxsvJw^2yaf&S%oB)#+OXX}nwq!~J-$V=b`xJqSzc(f{4uAK(R>A3IliHUh`7X_6s zitBCf1wi!j{fKMYiwWMmj4P)Rls|H+u4x-RPkNf*csOc5%8vdh0ycpZ0x^VKHe&1n zYOh#I90qN7`=9}h{{hqc2W&HfY=}H!Fxg3F@b>S66S5l`Hu<@P`=JP{Ae8w)&9nD? zXHiZ43c0(=WA-Aj?#z^&fzn4#ndDxU3A0%^2?`D9)yAJ}U@=vsqB=V!2o5-_!9{2+$y7j}{ zOoq2GY+dK$uYE8^OGc(hz5zq`4TM00^hxGcj#e-c8t9YQ+uLzvui-dG@}SlrA#uo5kiDU$K6W9dF$V9P zv!1larx{|7|1lr4`$9qP!}OnTlPm{HXwmc7%g$iv^~qrJ#68&p(?-?a9V$+v;bdR@ z5A`76SN)+~>r?x(3jxN z>cGHbuNTG6IjN%dGoi3K1Yhr1^L#1BZCldK|McPAc>o`u(~ewk3v?@#=hao*4P596 zi|Kgt{zyZ5*<62Hj)JJ$yJrTq0@FTAKt2~q-dbIlNpE9H3HUvFz?pA%6_9@0GpCl4 zO2E{jU1B8C?u<0IbHS=CjAYanX4G_&sq2=A*!w>9AA@1y9ne{yJGNW~%zDNSz=7r3 z6y7i(R41l(o`E-(^_m-4R`Nhni%6-wkv9I;abDiY0G8yGn*}{HUtfvlZj*?mvT?hk zvnrH*F!@{QvbdO-PfDYl_D=+g4b~%HPmES+JU!p$4~eVDzB_$FL^9V1y;cXaB!Ivn z6qw7f)r~!(M(l7;)XOYB_Gc-O8wdXS=m4eYdQ(1yOG)F&1hB2z0X!?I`xtap#)vMW z+v8RkMNi0qLq(SzOF?AjIBfDA;zrrt%O^O2OHf2l_XBb1BSq4B<-cB+$Dd}}P(Si> zS9%Jx8)aIK!lrMofjaM;_i8gr3OsRWqLk0fXJrN36pDFGPwafJQbswOaFS%5*b2Ay zyU0SD&MHwivn-vW&5R2SQ~l*rH21!0F=HS{VXQp(tlVFAJZ%PlQL7@G(1}@WeF9B8 zxY~C5I{e+Rq4&&R7&l*pGk*ETe>xU0Oz?z0*x)AKnpBodYt!c=-h>F$_c@voQQA9W z8jS(Zsus9VR#qko!A`r%L0cQN3PE3lUmJf~m#brx>$rstxcb)9L9zb`)p%*SwJW%2 zBKpg|D;Cziufe$VMO|J#XfXFmrq{7l0rVhA$!F5{l@fe(bt9Q}Hj0Njg=~f1TiSq; zW8f4Qz7GUM`9EA3EfDV${ZRxYU|Rw}sC8Rrt`mNoKG5wid+q~n*Zsn3r5LJ$K{py8 zdM&mafG=?b_NTh~FXPEA0RHL^QSykd??w)-`0Sk!ZDD7jZkP)S9lO7-WK5;F#@l)b&;Csvan z0ygLJqHPiIXC3&DEUp^gRg*$BfzrmkZO4qA9FpVb2092|jJVCucd2-L;+kxkNd6UB zx|8l7nN*(jAdMB?Ly%D&lTomXuR~hVr~IOl@q#tVVNXcY2LyB_fEm~-$_r`gX!kJQ z0!FrYXdG6xDn||!vIy8+;?R3zoPm;5J9d^L-m4_*Xi~fYF@~!t)sJZNQ!3UAAedem zQuG{X@rgfM=B@DM)-oy2g8d)1j<+i{pC{1|AODdUIgmFc!b+hJLNk!=#cyhHFaS6D zyqkvKE(+SMoJ?$Kcn3rA5rh%zf;D!Ae~3qtZxpw}jEXYlYG!_oV? zvCpKQz1`3_HRJ-dyb-|5!2*o(+6Cs3A77hKi=39_>DzA%CW8N|ZFiW*m-~-J^K%9? zLQ;Oc5?7fY*WHr7gA_2iC(-k(I9-Ji{NmkhALV-wpibAP?B-JiOLEU@)BXx>7m&xz ziRxT7|4)a#RWq3b;N6#I) z!go}dC?rk*W6Y(UUy3!$Te@-WVI>pW<*|@48pwX6>#YViQqFUa2F`kfOl}=faDssYm>XGiX z@1NhXbe^!YmOK!&`O|#yaB9;-4;)>)BDj_jP!oRT90xyT&UQM089M#bMbv+oPin@B zGyGU2Xca)?AQptxF4F5Uka|5Yd`^TH!{b>!tW5Gh$>gCnzmT%K{P|9q$HI1`eF;|E zz7oo&x$~>-&lS#knDDbu>pj%z;qxWRk02a~*P}ITXj%Bmqw_`7+ z+Jxg&P^M6TEP_lEEg$c84mV}2)OsC$P&B?C2W_LJ;T=z$)JL#cao zmyU|=5NjUvnKn(~gU+dxW>8a)5}JUMF9FW>NF{wms=j0{L5ibu783pJ_IFauoD@E) z-Mj57r*0e@r>;@zDy~!9wb%eXm%ujbA>j1G!+U<(15SDS`a&V+5S8g+SB4opx*J$z zDO&>!*oNo878&CReNqY7L+gMe`3?W}bGOco zY`u9wb#Lzep$zu-&w$iF+TaUEVb&7`Z2X|8r(yo_rb9$5i&WbI%RIf~> z_m*U&6cPVTdB9ElO9EF$J}(+ZDdfEZB@mmI^2dBcuUGK-yF|k;7!Lcw`UqnrA{L12 z;M3vejSFsIhg~A@CyI+LnL*_Imw1wDz@i@v%zZa6;7ef}K+luoR%nu8L|e}KTs2!= zE6Bc!8mj>(uqgdSI2@XY3Mcn z6*#Brd&G;+856u+o$s?^zHM3K($x@;8P`{P!g&z5GdSAWPZ3^#A3yR|H%%l`=lBtXTFkd9S^8fF9Z^In$ zjA_F-3bAnQ?TrOZzmFAON$TxAsV_6*voNy*+Xbh2i(6BVD=3KtdB8BwjuTA27U-ni zwA!ECEzP6FFsEv8AH>u;n3VY^IA!Q$BLONQWzQeyOWJ`RQh0r-JZ1&2Z>p?ZbWzA9 zCS4YiUnhPa_I~8cM@2=w{9pW1tT+IXo?rwv_)9SLK6z@~7>I`IWKo&re8l;yih7HD zi{JqU1Hpoo@1uHj7=5S*loy)7_Ev&a8t)ghjW0WIduW+l0Ll;-%1b|W5PYBLXT3J< zFjV5KqE8t#3`^bpr_oIh@oxhN20=#9=v6bD=IpyfcBeARJ^%5$NPFS3UAv~SD_}`B zYNn+8qCneIZ3KcBO6$_5Jqzm@6L__{LoAze(@2mbkkkBN^701M1x5n}V5F z%c2zHwwe3ZRvRkhxtap=y#p|kZC(uja5}~Ns!i5(dQ1Ewi_u@i<8FqYCR_8Ct_3O1 z59&G~yVZ!E)Nt5kcET6q(ipVtz8k*gqRddy^$2=Z7OU7a= zE_A?uR%fWZ4%uo*oQWw(i=ixi@vU(WpOe&MPccp%of2f0SVZN?>MpqRj%oM zh2|il3F??y?a;UV^&OGt$8Dcovy(i2BbZ3vm#eIu(J0|*a^vua(8Fn3Mp34yZOl8` z=ZsfaZx2_KdI&!k{!UGnM`H1C-SBfI-*72r?gVq2+qDS{~BYupF(bct=_`KQm-T>xUY3cIk+&0u64(>mXJIBqDb~F zzIVqT(r;XHa*Xw}pU(DVlo9OtkP8-4JvsZAL>hNqnpxbB-&Xjf!-;Ynrej=44=Bk| zOU)~psIyml9E)X661TjG-ohrv+dN~w8b&qr;RJcn`s|jkzlp^|x}}xK;~y@32z^;r zD0M~X^$%gh{~#@CCd$>ZdIL3gwW7c-9fL}E#2CqFnelR@_1@AGZbI=wiz!Q?pA zvw-^J`_1^yXN=`kIS$mzkwPsX_)pE*xP(^#1`z8|Neg0t-oUajc|7^Q1 z$XSkPgZUfCbNM4DnG9+=gB3lJKCGj*#JeeSH(`!O5!oqN>%@m!>cFKOIRaj(Jw&D>LVezW5 z4X4<6F1&dC%K?_9c|!r4j^Sc(rb^Y>MD%C2&O3i(y>&vAjximz;)SZ(x&|Wac`Glq zn{{&=1G*_;-&d?jX``5x-K1P3O9nk|S!(B59a!{1duYT9NNQ+m|J0(FUJ ze13k4%o=zd=G|;H?LwV1Z9b6MG&7dLj4wH~SPO&Ng9drPwxC;EWb$@2mjf%g1UT-} zMKBHvB#kw{38)dN!>1(+t;P^zVG$EK80Ll6+Q97tv9&j}ARPa_XR?3=X?dWxh20ZWQ!#8&y-Y35UX1 zx!N14qhUtQy^l9&EFUg@VfP$AHfZ5t^X&7c?^X4#FtWY9lF7T4Qmr3ColKi#o-@Xb znk3;Q-QKx>IC0fxf17be_&qLZf_2B@1D`Jb@ ze$IH4jOyP^xyD8ptTansc1mu=a9iMMzEXC3*f%) zD!{1NGh9K^T+-q@X>UmLxB#_1;V?k2j9FKTuQlQ@sKwmBC}31#8#Y44*r-Fm*XNW@ zSoSd!hOk8yMkUDt#wEep9#o7QLXF~Mk-E}zt+-H7I?c9d^w7@BINxQr;C$7V{j9sK zwyS}+C2VQryBNQ`YRA`ilTZy8wWVCR`C(_F@LA*&3w_4P#V^I<*He;{9XKhTCR(eE zb#il0mbNS%>%PMwQapqyUm4hZoDW(;y-2@!fib+(kZ`R~=SAgDXLmMo=Sv4z2OqsW zal$<;BBG4*tS7oxt1-}E`%zL`tESq(=gKD|$7~o{KYcQYZ6cnFbtq}8c;ZK;srI&Y z(wTcT+;6ovJ8M|b>9H?OLzCw#rw0M+tb>9c+mk7B7EUfQs+d_}+}pDJGlSI!?PQAD zt&KlAD$(@z_Ktm{Q)_6V@u576lZUsgnI+cAY!NF*zs>E;+dpWkU8tls$JFqwSw3tc z1S54YuWBgS3Xwj;{;u#Ufl;vY8Jz{`VnCuyYB|xhHp7yqf;82KHhSRPy=Wg=vam!V zXfJYIxz0y2j+6BU-=md#$bQ=jQRCX<3f{nfiQdl2PFil=CURI2+&?rQYSVGQ>?uO$l#oVb+MSgE)q}`7b>7bK@BodL$8^0aLTU2*&;Jph(KwMZ`twst z?-eN=yO!|laL(4~|uf`A&)x zVRkreLPh>R+8;t6Qk%1>!l6<_!1`hb@kyC$Pfilaa{Z>=KVgKTJ!oMz9$EC7A!FuJ zWBZhzVBfF1hdEQ6rL3aj&kUhY=B5w*WD%MZXkchxk?5+OOl_ozesi7Y3NHB4?Q@L0 z!IAebsJ-U7eYduI-iBBc^8L8bb4Jzg!qXj=vdTTL!`ir~6WAq9?Bya2nozvK3C={7 znf3bX^{YX7!Q|Eiq4B}sPCV|8xzmG@+n4;*FN@02ln7Ec6`QVlHpxCc4-U8&=T#%* zv0Gh_R%o3EF#%#bD!ZWP(C*9WEb5^|^d>4tI=)5=YLD(OES9cs44KZ=jvL0%#}KPs z8A|0CILzKs+0^Z>=`Bj|XtslGbbiCB#_S$N99!T`1qFA|33Mh)231rMm&cTTd}|K( z{`oQh?T1(%DPQ!TPqo>4~xVs z%;nfrIVmJ#Hly=tO5uK--5!{u7GOP)Sf)4h*o=Kq*FZb2fk2+CFz;eW-{){V2uqg7 zYSfyf{MM|lTJyY62FDEyb=A$r?884UdZo6@YSK_E>bsH}i~<88Xu6^>)~BdAi(DpD zNsWKOR!TL>i=zhM59=u3Sd7UoSR( z1m=CFc<;ojNvfyJEBN2spq7%;06tGRASX!$%*ZNqP4q#nOi zfvs@Z3rUn>!;;o0xx16Ha=KJjt&U|IyMJ{O z51VCCV5Lc8VCpX$?zNEDw@AXnY?FJE!Y(;@zmgb#Rx!Ug_H%@6H z%iULHV!~?~r3MHS#8d?b>IJLJu^s&0@;{|I^lsinef*ScdV8hTIpGXVuNF(V;D^?x z{LM|>;<34GoidTzRszaF>W>s>*;w#3wyB(4Wo6-UiNjZkdpYDjpF){96kTPMbB3B= zRxcO{l%>-1Tr8i-56;zWqNGkzMSg50)ePJPV?IuUee55&%113MR2euve2mb%`To#+E7@LT$Ae6dI0~XOUH66D zC)42(#(5t;s19N{>Mb|09Iot_o8$7&@bI7tr0?PSR`>6|ejLGb6Ta&1w?Dn2YAqi^ zupMDoxli)+DF+4l_dPn?cw4yhGcq~vRX1F43i*u{!CE6`7hQrrocO)|f#2V0=0aj^ z7jiPVt_k|Yc-I>%(y2gzrx9ezSG}KmCdaa@r29M2%MHMcG&$s?WeW(>%e~-Smi*N1 z^ek(v*zhUa5ARB`X7ULZn6F; zIYMlxB=O#YRX8vtuCEe&5{|NB?|(%6o5X#Zn7*fiGxJKOg*roUp(Gzh_0xSFVw(N+;mZ|GCb2JFSy&jU5z| z>4?RAv3jdSh1=5H_Al$tjZ(y8ql30PN6pLQEc zMqZ>6j5}=!LGLyo0$m;>>|p4z{_>*C1=cs4KG52dsB>%lwlzY znZCHY2RNL*?lU_r?hk2N$+ozpuB7r<3_T!a-5F@uWa?{u#0TZ6zFUWi$LG zPZ0DLJ3Bmh+S>Q$e`pw1=RHtymY4L?FtxUO&_r7>Gg0qde6!_44EVWV2u@v+#Rq}5 zHpNQ&dZ+{Yo6pdAJR_sq+mg$s5Cuuj;Oko}n0b&Hms`jZg)lG5x9U;M1=__0%+~D- zV8PYFq-|mFxyhI|=8=g&p$-bqD}|mr&CqAQ7V2DF&uq%-%jo|m8jUYL%Y+V?za8PB z=7J^H>wH0w5U>tP)AGB`>CKeuKw{dvTuFc!>@!Cdqu>94lQn^mXC6*T~>eDE1gr_>l*(fOFoXIT+ z9P>%5GZ(c8+X?!xtLKhFE$_Lc@%4u_h-XHjW$L^xJU07H{ujU~um=Mr}W1;#-guUrlpLJ%%vRlHo#Ihcni|s$7P5Kgp9e`%<>#MJR9O$C z_ZUiwty#T|Jr&s3JFOPPB|}!xX?w-d-Dn=2qRx7)*>D|p7x8EcAskXu*{=1*FuL^HwRDX#Tq&9327&GocLB* zDLNl#ax$f>>zvmCuvJCC48qib$IxHtE3~EyYVn=&KU{pd- zj8xb^5f8ts&2gWY&G|m@8%I<-Z_g8JwU@VZ&}+Cm@Yvhfi39HxI(|j798HdWNAm3h zwH>E_`^tR*l4jp;M!r&4LYAOERsly|6JWi~C2Ms*cwZES_lf#+tXWWWyUTu8ljPB5 zc$D=z00f}LC%<7|(`stH$MdDkU_O`st1D;YFT$IT{~ZWrNtE~WNEL1Wup`x=c7>%C|(eo999_TahLy?6nt z3mM+=uDxwTrrnx^mEzsA6E>>XnZ!kQe_=)SfHP393x8)7C+BA>OiQi3ksI>Y}< zyrA+>oWb4~8H5h@CYe$2QBzRRY>R1wAte*fhm$Cn zhfzu819@=Os0dS(Y`lb?7JEMOEL(Eq8Zw(EjRQvMm zeiaIpYEwmB!ZK5mN9Tm{X;g1IbGOZv=1aVaPV@a0YQ3MaTw1fUwodjfjm*XCi;fIC zbnzv!Cp;gy6&Y0KyiFzeqKkcmmWzxh19u-4G+L-V z+pITt)HawhRN=n-DlSKrE>k00qc@Uqx-ld7;{Vg$n}<``wr|5rL<1qokfKsTWXL=g zDKZtJP-Z3bn0d$@Nhpz-RAkOP4`nQ4l$j;N63aXf;XO`G_j7O0yZxT+eg62qZ~I<< zh_!O9>pF+yJcj+)ck zLuG}NZjeog1ZZ%%y`VncTT7W3Y^1N>XU658FW=CgB$v$Ge2Md&-5r<1r3*t2;e*d) z<2W>zY_r^7-%VG~{Bm8rM3|~vj91>d+u_53<4aR^Nq0Niuw9u&R|MD`%CFcsGw8ad z7i^gqo;l5jZ3fE1;lh2zb&sU%mQFeARn;t&=B7@wi<_;C#%e51;ze-P&&w@u4i?0^SwUbnENlQ`=ovjbt*{*dAck~?>We}s!Sf3IR>wP)< zj)dw5;rAgXZ0D#qU}&$ zoSD?uD~xZd^rxN(d2vkou|KnC3-7^FbROa6H9FOlBCO3-2*COA2)^t4B%;kXoT__l zTN@17I1h`BJJ8;UHohl{>GVCV_L>PvTP zJFrf=Z`Eg#(HSml@2kxUFS|%tV48D$p1jdRzqBLzH$2ZF2L z>@jAmE}MyxXh6oiaf;(=(0e@It`xh{T;c~qiL2{gPt-&gFgyxQvI zZng1YZ1$37y8tD#J15@4AuO)zW$g*Kt(hBA<<$8ZY^DIXQcX3%5KhL>do^*gvi1NR zDsh75qmgeDuUgKf5@CB%9SMVZ!e#kzEeNuED;JIG6O-@FfTEuvzREmcPpA4u)`drM zSNCG8WXt8yZ@QIpCim1Z>P^qHrSk`zO>r9 zp7hw&siR#bP+5LsKglx@Y5HKERK_0!H!(|6zUe;v8CzNP$=)M^@rL!uT{XVSRnR26 zH^~luq1ls)6i(_jcz%V3RKb6v2;#y0|XF@cy+=HY2YH>CL zoY-VzrDEca#`wVZ&E)g)<tv57Z*orVSt2_DPbJMi3 z`P#ur6RAt(u|}F-&83K{cHPzV%sHB9=k3@fk3H_gd3Si!@c7twDZ%HDy{ENuI1cT` zHeb?cuWPQCdB4oNKlI$h^lo2OQ4|P!~}%UXjZW4a?PI`5rN@eCc$@vb@tNVz_}k)tj*t0Qxtgsj_@$18Qqe#583Q&dX3 zDV}x2K7X{QY`w`}f~urLV(@Vr?|3_zsbQrLzGCS(l=6;v?&E{cy1KQAej&IP{5^K6 z4M;bp@De9p+ul)tT5!~m4yT@U`jY3Bcxg$zcB+v{!i95Kl6L`=g;4Y(*rZjw<`q*m zSOoP+rW#+-ayJX1DqrYnDzG4BS z-k;fgT!R{|q!nwuY(|SxDDr`r8c!Sch}KGHJU*_>t$EY-x^{F|$>!;R)I?{Xnk*o8tP1r!%M|l zB=~%z@(n+4>^rT^b`W8UCKh!mYc5HQ`7Wznd^5sE|2@NTnaA|6)v=DXqh9w>lM(>& zB|=PNHY~_gpou!KgtmNG95ArWxBpO!rugg^8 z+7WQvx`*)z`xRRT0w$;CM;J-wW5heIRQabb3B9!;I zUal!!PUG6b?g;&Nk};nX9=mSrblao8Vz@`S>&pX4D!WJ2*|}KeOjVBy`fe-l%Jc|E zqiH&=q4~Ksz;K-WdxpAu+^1y2%cYPuV561X3QLr*lz*^UK|n}+>JzW`U7{La!o$wS zkLymr0M34c67$i@gqgx^0}n0Q)d zpm;HwCihBNWjTLg`4ii`SSof$Llysm7gqYExt1^TfV^`JbwUGQ3@0eixDKy7*3D=l zF|DHc&GoclTMH<$wUbpoC=7mapTnQ~Sl9-o@#*sc$LG2kDL;$9+XYW(=I+gLY(Q{_ zhjCap5`oIs^6@*RkB9-{Io_K5M9R>N^squSR+)3cVw>heRk~2_l4aSSL#nk zL}xdwCrRPt?cp#yQXm(i*gW)BOoC|_^d^S3_=|Y>s*6-IK8Do$sMBG(*x=_W4?QG7 zG_6+nO4K2v)lD}x8bfCa%6I=6BBv1RY1jQ$HzxTShXM68nJrJfMenyr1M`)%ek#xX z`}e1^$yD>ICUUbp0s)0|Lp(gP^up)hU0s75qLCJg<5o_JhkRXsSbZ* zyaLENx5@{Lt2n0jKd^bz!_DRI96Y8yg3AdNx4s`z#FCV_^}DmUZ<3V}DsJw3_62t0 zm)DmW&3f#$oR?13emHF$ihZklOZ?tOlUlyHHj=$l$tu)+aGU>&FL3gtz|n>7J?2Fu z@5%5;lW#_@KkgcHjB3ME@G-ziNzc*N)2yBY#xZ&*eUiPK$kchagPlN(0-+uYE!qv+ zcN5*Zj>od9ghES6#Y&eV~*qP4EVasuc3ZQnc zM2}@LvnjGTZvihZIp{<$PLoT7c@cPu*B+o=X=su;Dvfx!pyP8Km_KIe_yv@W?rEuf zh+_W9r^+<xH{1X4GaT|&hX*0=)-3QLJG?U%R{#k@HrJ*yC|Xr1B9DpI_qpZEOR&{V)0IpB+Kr*Y!nXi3kg`LObS)b2nPw>V+Gv( zKcr9MTv@0~U~6dBhbq`eFUCkRpFEiiFywY3Z>nGPA4pN7jgo}YL~(-L_UeV@+ft;q zM}TE8)Bi&mz|~NdE!#CfmTmeQ-_io;dn7|+6~6WInZ@GoQ9?F|O>MQo8Mrc<-}Lz^ z0ueN*v!%T6BgO1G^qc2PL{Fzi_q-Tzq-@LHw8e00v{m-`0-C;DY8d&4k zA2Tin1i&4Dn?7sSeHQZM^3V#_0@Qqn7!ms2E(7)M6`y^FQz@4lcGF^(eyX(mPz8Z! z@NBWX8f$qrng-yqO8)hrTU1P`c-DQ><*jI_m!_cPJ<6wQsX8+0c;#vQyuo?BgQ

LX)?cEJ3m$ST?Zu>ObttpC_$?1x{=Ux+(HqTC_*7sUkzcAH>N1lMTg z)7wW*!qBtBGEp+L*nE8*|y;KxT8y z0Xo&#o?DsflelI6T$#WA@wnaO^O(FVl!8y5+AjE+dGgKhJ@_LDJBn8;)@*CU z>&DR`Bi6V~5F64)JabU7aMWj*>de${C&DOR_#5r&w!GDEoh!wQKi)EaF>DzIOt95U zW(8X5yiRU_Nk={WaJ;GlrCN4!Bnam9IHkI*&RwIPhsj{)55-gede)!+dVKemwi&Kt zehR7))$qCh`o*;M@&Mnzz68W9B){o7{=CKbhsW~wi!N2`{X0EM*l(5y7L$9t{5$LI zZ#|psA7BZi5o@`w2Yg!Wy^k?oW4G5|IeKJ!(adF#eYBMvtOg;6wBmCb)+WxF!B-{4u z#=!1^iWSH$LF;0h#XlsE{x@@?SK0Ub(|`RSY4Atc=hthPf&2evIgtP1>ihj4#s4t9 z{eEr5ce!*g#u2H@*fU}-9{92}hR?MVKt#^| z1m@zu%2WM)W&XOcZF#O!u)#nsHW+%7|3zu>x8&lFAAT}%Hc=g#BL3S={rK_N-hX#% z|IOmQSTe1RsVhd0^8cnW2A)W~Jv6NRAFN{_cMJJ{Zj33XooVVAK_ALx3N;_<-hHHk zNG^a7vwad)&MEPjnH?3^egCGrgw!_wtbwr~Z_zr@&EMk1)pm2#KvUcKq6d z0r~~;O5P~wYC+_@-uwT@gI4McE2}KfTq~7h6BG`GoW9Ou&=Uw}x(5Z%t#~MoXEs*? zA$UiMdf+m~L;rs~leo@82ScjmL$aRGe>^gXojm++49mYWqW_hp`mZiH=sLYavi}*N z$q?n!s0||~%KLA0ynoilwl?c;`!Hl(sP)zSpKb$&$-3$Rs2%*{%cJi6e|ob0H`e4o zc2EARv-dx}L$ntq&vqABy#yA#ctpqm7z;LF-vi{yOX#9X)_LcpdlzJNTA|r19+5&8 zJ51-{5N`p5e5wH5r3USf95V#+gQz!usT#<})m~kq1dFVhM7>NbLX|GtF8P2z*d` z9D+4LG_cc76C@=Qk@_&SMvVheI~#xxp+^1sGG}4c$7WA5Elhm7_iieLsJ0K!$SO|) z;(&C}$!^Joj`^TGJPc0yQ9yS$iLXzb?y+wY8ZGF*H$--~i+2|4>O~+!KMnxk#o}l< zEby}s*$_=G68MrnIY1}?y=|z+SsO9wr$AT0^}G-AC*+=4ip^9Iro&hcJBdJ}PlJ7* zkOHVbWsWQoTH2GI%^Z=}3w~(k_|A%HKu8BdWTrzJs8i=ns=tfO@47 zp%xBtMQB0zJ^%2;NzT9aZh(9&Ey)-a(ZDe6rC285{%{56!D$c?4%9*j9?c(%E^CH#(s&=sJ0?^!?Nwz-bYg*f14+5j^BVa#yYTi_0r)iqnlhDPB4->;n`6Wf-z=YjceMR< ze_B0fu{;o+EQj9$d78Vs{6Wi4qD}cQ3Vvd;L6~04>@IZijbqEp`6eC+GzT5Rxmy@P zwP0-w2$Y)^@jEDlj&Tr29JOj}k=P8oXWVV@fEBPS=^zH}dIUwYNMaI+xPP165hQWp z1}P~G8mEub+%v#ayU&3NBmD+JUs2~cG;C5o1kSgCDnVEW9?B1^XlXeYcJFB7g*be1 zqDuuhSn^;wGvJpHjSM1l207f1C{;oij#P zeiQS82G$jqtRf2N?FP*EHM~Q2HD1E6xWXbt>FL zibkg1Sm5h~1f(D9gbO!L-4a7Ar;AwXUJOKB5I>(ws5{(4od&SmLTIWA9+}gx@t?ge4rJDVy?nrsuRpiR z6Ej^1yCA!vQRv=G93H9^bJsW9H7&$mcI*+@-{^UY=XOh+{O&fP6MR6Wwk+PveV7u* z?E03^toyUE2v7*y1~k)w>Ta`*{GhTTh^YdJ4tWFqJ$MJH=`Ltl3H@AFT%TblN2LH~{S6Q;+;>Fu4Klt+Dhsl3DamRnY~4T* ze-3&g7Q!9iwDd_6!yJqJaE`lN#O63BXVb^Zy%+ZRU7v>hZ`-swzzrRGtu6*M*WWWY zhSj_V#9-ETr&mCr=?%DYUE;M265z!xjxDRGsPIM`pPv$1rrAwlD8GLxM$EZ&8uF&v zaAcExD?~h3%^P5|iF3_>T63<@9Vl`^R##6*y!mt+xWub5=j_(xgFhzPmjSP}DpF zqeiAZAXE@Jl10}8<%UU*jB9-AN+3+umKf0oIrJUoEFLp{U^IYnIb^Odwk;$A6U8o3 zQ_(vpNMV`&{Qx1Pph;5nKOaAO^c<|*eK5}h^5C#y9Hqe3A_@TaW0e5bxHo<|LEG`v zi*cHUf@oNKNtd1+BQUEyt%AX${geg-I9T~p*4@9g90hsHcgFolXhH@*EJz_) zus!Y|z1y$YcRVF(~#E%P!0dR@4l_diRy7N@pSv-2rE9o*Ym$Ah#R*^c4$Tcwt=&B-3WXY*34J7HrTm{F8D3(06x?eMTe;M#w%kA7#O zSu;tLZT4qgO8~&)30e>1ycRP9@7nQM_vwtjfn=u&Vu*lBNLLmaX6b_kcgqlL*>=Ps zWamWe1sg1U?lLKu+n2b;NMmOb3mJHjwuD42r>UqPPy(iyUgZ4F)KW!3-uJF#i2QNBeXiaDzQ55S9Jsm}; zVCl!MLulN-lSV5j(awrt7t?;U?+U@*d3^Un`;H#jJ1;sz)aTm{eCDmA;aX2%mx?5> z@7!oSuEbR`CoXfwBS^?<(7Sn~s;WvW<0T~2!qdM$%*%@&%bVyX!9*P*v^frGGHBLo zL8sa%>J|e+9W^a2ElE6=tSpM4HkWHj^NkWv)e{gzNMGTs)nL^^ZKF-BBaW^V1YOVF zyMQ@=lwNND*iKBzK7p!VxO^bQBZyBMHB_5}N^VMgM&SSg-N@gnsY-$SMdaq!&F`Zb zjXz3^s{(@y#KXo~jZs6Am?z$_8+S3lY|gR6mSoDQW`Rv$l55&^75J6Zy0Q$@%C4$Z zcOEzt2}K<7?=w;x5c7rm1))e-iQ0!inuOPB0T|d^ait)?4&jfr>hfNrUd5Yv)Kf6` zWYl0%gJ%60R1Wi;i4N~dEv<@*?V>6@%Dvk zuVXxH^mUkG?hWp~d;6FLXf7vA6$}LgnPOG>r-3xG8F|s(20fT2t(@A5)Ltii8ah#w zLh`|2h}Vy7Z>Dbj0Z<7}r8H{FvA%K>wowJ=0?7`^CN$Iy@NM7#9KX`{;>*huT~u@Y7|UynPgFNWTv_KQGyT50A;_tM)GGvFO$=b+XgUqfUYWr$$a%9qj~& z&PAq-W@zSf_AHL5p*<12btK_N4|qU?Ze`coN!_O)z<45mnDj{*Iau88vaMLRwWLju ztXyrt!%_i!QGz98-ew9dl0A3$9{F^TaMD*{)s7KUcRj~#OTeN(75?CZasOe5-WSS4 zKgS%ih3-G<_x+2cY8AH9bAR!V7y!kr%9dILPax3&6VdE)*p;zoB<<7CRg0Rg+oaZn;u+q?6eIwwch#m zg3ctNd(X!Lam*9;tNL*g;6#Rms7_fIwvHmP#x+O>Wcyp#BUQ@m!+N_QPD-G?t-d`E z>9-$y;=G?O&+0%B0rf`Lzv#iQnUDmh-#`Yr$q$g5jFK3gx+sM-=*Mn5P~o}Y2!Tr( z@)>|&$=fzj9a!ojM4@Js1CFt#^>WkjcV)=*LQ-S@FJIIOL`m0s!LClbD&8iY)&v|)`kaPi;Un>s+t?Qhy-ZW!V-h#M1h2^4cu+|JBI>4R*9p7AW%%`JI z_2crBR1%XRNe8Uj3b7rKhX7>Cjm*riA`>X>*u0S{6M&%zk3T`&O&1AfCW@V2KkiO! zlF`^C1kUP$mOnT3w$YIsfH@0_y#i6q+whVCSlD;L%^I6;5?j_{$8u`zBv#lbe`=11 z*SwZFf>F%r?11+YoOiFWKy@C%D};eg5mI@{D18y@$RJJl=|ro( zj(PYH)XCQ%6N$9wpi*ucTZ`g+7zf^)!^QzraGA924H^wb5raGf0W{%QG~ry6*z#M* z7%F|`RC(8{N+dwtSUCpS=2j584R&*fX}y-^sEBeEu{I(JLHeK)U~T8t8MQ2EH7K3e zd>yDlk*82F?zEu!st8R^1-3`DDbyi>;@_b)VWF_znKnGc~rgGalTPN8#1}#XaU|;?%7$< z#r+n5F+foComV|$2y%zbInh8$E*v*%!>~(G{PajJJb;lIZnxI z@={na#@ukO@s6seex5`rI696Dkc{^VW0aTN}-zLJ-DVr(MrJSwB+i zPdAk9qNNpe7fg=lXEtG)j zMy^>8e;j)#i1GoVO?8$}egEQQk2-|Disd&pz)$@Q^qF66{(3^UX9!{?6($HL5|Lz4 zMq6dhG|(oZf?L}tg5U*;&iOXKJp=`=_IZWZx|mN;V2h(m-bOG(vLBpg)~i*1HaL^k zUK3!$2n`HpKdoTbfIzKZ1VwDW#lcCPgf#Bz!8>S39#{p6Mh*2QXLgkYe^ft%eARG~ z8Rq>NxU8`wm5;Ff#2bX&M>RbN-FZ0jUDxehp^ic?Z2JaLpvTsN6Nr=|PEl;<1s`&^ z#6tA03LcE++J+xB`wea`aq*jukOT5qogZEIj~jtrr9EO$iyB*ISA7!)vB`R$?#Y8j z0#BJBayoYXRrRloVQ8Wfg`^<(ubR)b?XRzGKvwJqwcY$NT30XJ_tY^GQE(C0O28SD zK`VG}9EqO;vtAll5z&FsFdHtM#eD}h9Vj{i{PvY-KB45d@D-!MsC+nNZbC}9Zn5Ym z^MT=N!NX`e_E&)4?3!M^X#qTffDn9QH&tEfKOt<|$pwVxA{$Y@Ne$*W)D$4@=!V*R z!7GXOxLJva?9FOljffISnMw?lu4UbTUFPzCiorSD}9c-95T zVXQR>?EZ`!=ptp+-LVO6`>sb2GLX;Zf$U}`UeXC}yPc6Qd&9pc9l{nnuHrjw3Gz}J zVcxZ{aBys_^C($}ihubpUqL?LT5ICiL}MNEO9@ER6Iejj@?j2bSSHnwyrIKJEX%vP zNFl)tsJtu|LFE?hXuOYy(3|ErklF+6nNy6L3kE8d2MISgEDnHBtMC!tN$%M?hT~4R zf`-z-MYv|lV@7&lJ9yV4T}TAq>6&qZPvJ_J7Utfz$E`SF+E=vbZ|_~W3pW%f(KKfT z(S&r?rAPOX0}h8ye#tpF`4hEFhsaS1IJjItt>hzX!U*4|UJWpJC{|$)?~Rn5;xF_# z=@w~c*l_C(A`dSX78vzW&?_p$unf8Jtn!@1wlJKT?knyXo$c(@a0RsMwX4ESTK)B` z1iSW95!F6El!%N*@}4n}j5j%91+CDXz`!UTM};?j&rlL6p^O_Y7kazQ zUeOKeQESarhnBc=*Uo#&@(X12RWft0UN+#Vj|g6e0!i~1dyI7Gr2x;bLEwyx4IXg^ z7*zs@i79_z@U{N5pOE~puz`Y5>A|+$NW6;e=+UF<_|a{D)Ymh?uV_^vid(jKAM6m{ z1IQcwW_22ZXyiff6X>&5k)-Jf^AdpL;zA5(-tGtf)$iqtn+?3BEKV-h$Sa90s-`~* z3Qr#Qw&GRX#TbKa%fX51*jb5inb%j2f zC!e^Z5b#=E znAjI9Xg99uI*P`%W4MI2a3MFK7kEk`3%*vO%@$V1z{VUJf&%@ zhy!O$ZSAAI)@$v`5#6C~6cUkv{7Rk2P(;J)xH3HTnKl6$Vrr8kF*0xc$*~z6{oa z1|-d`sK|QzlE>%;g@tcuMYamGyF-m(Fy^$F+DYx_ts5FeoVhtUy-OW-Fk0y*MZ}xx zy(BxnG>v*<90Hur#nG?IQxpV)#Mu)%g|k}%0ui@wvV~;X`PEn$-MipqjR(IvmMr!X zA(e&NS}(6yS7v#6xvya2o{t&N#mN<>o#8yIep|D(;ZD$dXG}OGU7K}IaoKllv}zvL z$WH%`CDsM>Pupi^EJ^q6J9uje(wcI!2Ye1>OF(FOL7;My<+u7R#)EBWuMehy|Ao-s zfIZg|NZUf!2Ex0B*KPacllpUQe6eb$hlL`>v_AAz#F`9 z`SRtZWhkELJ>SN6nfU&k;V>wEh(E;Pa39woHxfuLzM>-|vIw+jwUE2Ndfma^UPehN zP1BnNM->^h4gSD@L&Z+}Yl1ySis2P1oH`>#nzlb9a z`8Z1^z?%Oce>!=1|Ss;fO3rVCL|;vGk8`&pyxXJTMv3Z z<2>y4RWQmM0Gyyl(UqYXKmS26Z5Nb5DqwA*7h2aJHT;;3sGrc>3V*F)dk$e?VLct4 zIP281GI6kY?06`~S3wST;uN~1W@{5Wxpdz+^K!D;}c4CYZn_`S89i)_U zUv7UmpLvsM`SvGZ;_Mq@B_x)>NgKPTudfdkUyUEbYx$hw=j8-OfZioIfKNc67UJUx zW^{=P?ejnN;Tk$28_WQ(+DO~)fbjH(@^hQwb{A9c2|2)niuPFi=xKW62DQ@1d3kw= z!!ln#dH=jGLaW1ek;j#SzsNAiB&d(%&p=D>rs zwYB(^6!uL=WfQn{7ntK&&?Tq^2V)Sl=tFFJdU}Q+acKogZe2ge-|#15aV7(FxH|4Z z288{IV-`&LF&-tT7*c~k<`huC+45Eq6lf{O`!w!$bk@Xx>5zGWu;e}RwE{mVJ@e}CH^Bl!0J euaU4SEhn&(G4~wH5f4HmAbUYkI{mzk*Z%@0(`@Si literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01j/python/factories/base_factory.py b/example/1d-linear-convection/weno3/julia/01j/python/factories/base_factory.py new file mode 100644 index 000000000..7b8b6105e --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/python/factories/base_factory.py @@ -0,0 +1,49 @@ +# factories/base_factory.py +""" +工厂基类 - 提供统一的组件创建接口 +""" + +from cfd_registry import ComponentRegistry +from typing import Any, Optional + +class BaseFactory: + """工厂基类,提供统一的组件创建方法""" + + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + """ + 统一创建组件的方法 + + Args: + category: 组件类别(如'boundary', 'flux'等) + name: 组件名称(如'periodic', 'rusanov'等) + *args, **kwargs: 传递给构造函数的参数 + + Returns: + 创建的组件实例 + + Raises: + ValueError: 如果组件不存在 + """ + name_lower = name.lower() + + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + # 获取可用组件列表 + available = ComponentRegistry.list_all().get(category, []) + + # 构建友好的错误信息 + if available: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"可用类型:{available}") + else: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"({category}类别下没有注册任何组件)") + + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + """获取指定类别的可用组件列表""" + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/python/flux.py b/example/1d-linear-convection/weno3/julia/01j/python/flux.py new file mode 100644 index 000000000..5ac73aa8d --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/python/flux.py @@ -0,0 +1,74 @@ +# flux.py +""" +通量计算器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册,替代硬编码工厂 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象通量计算基类(统一接口) ---------------------- +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass + +# ---------------------- 2. 具体通量计算子类(使用装饰器注册) ---------------------- + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + +class FluxCalculatorFactory: + """通量计算器工厂""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD对象 + + Returns: + 通量计算器实例 + """ + from factories.base_factory import BaseFactory + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/python/gen_boundary_test_data.py b/example/1d-linear-convection/weno3/julia/01j/python/gen_boundary_test_data.py new file mode 100644 index 000000000..c7fc2a9c5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/python/gen_boundary_test_data.py @@ -0,0 +1,35 @@ +# python/gen_boundary_test_data.py +import numpy as np +from config import CfdConfig +from mesh import Mesh +from domain import Domain + +# 固定测试配置 +config = CfdConfig() +config.with_boundary("dirichlet", left_value=0.5, right_value=1.5) +config.debug = True + +mesh = Mesh() +domain = Domain(config, mesh) + +# 构造 mock CFD 对象(仅含 config + domain) +class MockCfd: + def __init__(self, config, domain): + self.config = config + self.domain = domain + +# 测试用 u:0,1,2,...,N-1 +u_input = np.arange(domain.ntcells, dtype=np.float64) +np.save("u_input.npy", u_input) + +# 测试每种边界 +from boundary import PeriodicBoundary, DirichletBoundary, NeumannBoundary + +for bc_name, bc_class in [("periodic", PeriodicBoundary), ("dirichlet", DirichletBoundary), ("neumann", NeumannBoundary)]: + u = u_input.copy() + cfd_mock = MockCfd(config, domain) + bc = bc_class(cfd_mock) + bc.apply(u) + np.save(f"u_{bc_name}_py.npy", u) + +print("✅ 测试数据已生成:u_*.npy") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/python/gen_ic_test_data.py b/example/1d-linear-convection/weno3/julia/01j/python/gen_ic_test_data.py new file mode 100644 index 000000000..69c2573b8 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/python/gen_ic_test_data.py @@ -0,0 +1,27 @@ +# python/gen_ic_test_data.py +import sys, os +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +import numpy as np +from config import CfdConfig +from mesh import Mesh +from domain import Domain +from solution import Solution + +# 固定 mesh +mesh = Mesh() +config = CfdConfig() + +# 测试三种 IC +for ic_type in ["step", "sin", "gaussian"]: + config.ic_type = ic_type + domain = Domain(config, mesh) + sol = Solution(config, domain) + + u_full = sol.u.copy() # 包含 ghost + u_interior = sol.u[domain.ist:domain.ied].copy() + + np.save(f"u_{ic_type}_full_py.npy", u_full) + np.save(f"u_{ic_type}_interior_py.npy", u_interior) + +print("✅ 初始条件测试数据已生成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/python/initial_condition.py b/example/1d-linear-convection/weno3/julia/01j/python/initial_condition.py new file mode 100644 index 000000000..047415b74 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/python/initial_condition.py @@ -0,0 +1,90 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, ic_type: str, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from factories.base_factory import BaseFactory + return BaseFactory.create_component('initial_condition', ic_type, config) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/python/mesh.py b/example/1d-linear-convection/weno3/julia/01j/python/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/python/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/python/plotter.py b/example/1d-linear-convection/weno3/julia/01j/python/plotter.py new file mode 100644 index 000000000..e1f99ae25 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/python/plotter.py @@ -0,0 +1,109 @@ +# plotter.py + +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/python/reconstructor/__init__.py b/example/1d-linear-convection/weno3/julia/01j/python/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/python/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/python/reconstructor/base.py b/example/1d-linear-convection/weno3/julia/01j/python/reconstructor/base.py new file mode 100644 index 000000000..bbd638503 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/python/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def reconstruct(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/python/reconstructor/eno.py b/example/1d-linear-convection/weno3/julia/01j/python/reconstructor/eno.py new file mode 100644 index 000000000..c2fb385dd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/python/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def reconstruct(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/python/reconstructor/factory.py b/example/1d-linear-convection/weno3/julia/01j/python/reconstructor/factory.py new file mode 100644 index 000000000..efa4a1446 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/python/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from cfd_registry import ComponentRegistry + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from factories.base_factory import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/python/reconstructor/weno3.py b/example/1d-linear-convection/weno3/julia/01j/python/reconstructor/weno3.py new file mode 100644 index 000000000..bf68be509 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/python/reconstructor/weno3.py @@ -0,0 +1,59 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def reconstruct(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/julia/01j/python/registry.py b/example/1d-linear-convection/weno3/julia/01j/python/registry.py new file mode 100644 index 000000000..61be90500 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/python/registry.py @@ -0,0 +1,75 @@ +# registry.py (改进版) +""" +CFD组件注册系统 - 简洁高效版 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表""" + + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True # 控制是否打印注册信息 + + @classmethod + def set_verbose(cls, verbose: bool): + """设置是否打印注册信息""" + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + # 检查是否已注册 + if name in cls._registries[category]: + if cls._registries[category][name] == component_class: + # 相同类重复注册,静默跳过 + return + elif cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取组件类""" + if category not in cls._registries: + available = list(cls._registries.keys()) + raise ValueError(f"❌ 未知类别: {category} (可用类别: {available})") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + @classmethod + def get_count(cls) -> int: + """获取注册组件总数""" + return sum(len(components) for components in cls._registries.values()) + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/python/residual.py b/example/1d-linear-convection/weno3/julia/01j/python/residual.py new file mode 100644 index 000000000..b4d4d7dcc --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/python/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux import FluxCalculatorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + self.reconstructor = self.cfd.reconstructor + + # 初始化通量计算器(工厂创建) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._reconstruct() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _reconstruct(self): + """私有方法:界面值重建""" + self.reconstructor.reconstruct(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/python/run_eno_weno.py b/example/1d-linear-convection/weno3/julia/01j/python/run_eno_weno.py new file mode 100644 index 000000000..ff46f2267 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/python/run_eno_weno.py @@ -0,0 +1,54 @@ +# run_eno_weno.py + +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + #mesh = Mesh(ncells=100, L=2.0) + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行ENO3求解(使用你的链式调用) + print("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + config_eno3.with_reconstruction("eno", 3) # 显式指定3阶(也可省略,ENO默认3阶) + # 可选:覆盖默认值(如dt) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + cfd_eno3.run() # 求解并生成result字典 + + # 可选:快速验证ENO3结果 + # plotter.plot_quick(cfd_eno3.result, title="ENO3 Quick Check") + + # 3. 配置并运行WENO3求解(注意:WENO默认5阶,这里显式指定3阶) + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) # 显式指定3阶(默认是5阶) + # 可选:覆盖默认值 + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + cfd_weno3.run() + + # 4. 可选:保存结果(供离线绘图) + # cfd_eno3.save_result("eno3_result.npz") + # cfd_weno3.save_result("weno3_result.npz") + + # 5. 绘制ENO/WENO对比图 + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" # 可选:保存图片 + ) + +if __name__ == "__main__": + # 主程序入口 + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/python/solution.py b/example/1d-linear-convection/weno3/julia/01j/python/solution.py new file mode 100644 index 000000000..92a46d3f4 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/python/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config.ic_type, config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/python/solver.py b/example/1d-linear-convection/weno3/julia/01j/python/solver.py new file mode 100644 index 000000000..fdb5e46a7 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/python/solver.py @@ -0,0 +1,85 @@ +import numpy as np +import matplotlib.pyplot as plt +import inflect +from abc import ABC, abstractmethod + + +from flux import InviscidFluxCalculator, RusanovFluxCalculator, EngquistOsherFluxCalculator, FluxCalculatorFactory + +from boundary import BoundaryCondition, PeriodicBoundary, DirichletBoundary, NeumannBoundary, BoundaryConditionFactory + +from time_integration import TimeIntegrator, RK1Integrator, RK2Integrator, RK3Integrator, TimeIntegratorFactory + +from mesh import Mesh + +from reconstructor import ReconstructorFactory + +from initial_condition import InitialCondition, StepFunctionIC, SineWaveIC, GaussianPulseIC, InitialConditionFactory + +from domain import Domain +from solution import Solution + +from config import CfdConfig +from residual import ResidualCalculator + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, config, mesh): + self.config = config + self.domain = Domain(config, mesh) + self.solution = Solution(config, self.domain) + self.reconstructor = ReconstructorFactory.create(config, self.domain) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + def exact_solution(self): + """通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界""" + x = self.domain.mesh.xcc + T = self.config.final_time + c = self.config.wave_speed + L = self.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = InitialConditionFactory.create(self.config.ic_type, self.config) + return ic.evaluate_at(x_shifted) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + self.integrator.step(dt) + t += dt + config.dt = dt_old + + # 整理标准化结果 + u_numerical = self.solution.u[self.domain.ist:self.domain.ied].copy() + self.result = { + "x": domain.mesh.xcc, + "numerical": u_numerical, + "analytical": self.exact_solution(), + "config": { + "scheme": self.config.recon_scheme, + "order": self.config.spatial_order, + "rk_order": self.config.rk_order, + "final_time": self.config.final_time + } + } + + return u_numerical diff --git a/example/1d-linear-convection/weno3/julia/01j/python/time_integration.py b/example/1d-linear-convection/weno3/julia/01j/python/time_integration.py new file mode 100644 index 000000000..25ac0b4ca --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/01j/python/time_integration.py @@ -0,0 +1,125 @@ +# time_integration.py + +""" +时间推进器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = cfd.residual_calculator + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(使用装饰器注册) ---------------------- + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class TimeIntegratorFactory: + """时间推进器工厂""" + + @staticmethod + def create(cfd) -> 'TimeIntegrator': + """ + 创建时间推进器实例 + + Args: + cfd: CFD对象 + + Returns: + 时间推进器实例 + """ + from factories.base_factory import BaseFactory + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/01j/python/u_dirichlet_py.npy b/example/1d-linear-convection/weno3/julia/01j/python/u_dirichlet_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..3aa20bd2e395654a6e8c59bbbc9f5eaa955b345e GIT binary patch literal 480 zcmbWzxeftg6o%oW6O}@tQ2Y_`BNMTVeHmMHM59p2#LPq?$c$)2;vU?f%TQY8ZM07F zov8Zw0H1NzP(=`e8>IlQ+pS*SsNB7-1(=_si zx~l&#Cf|Elzsex&D8?~?Nlc-_G-fc1IV_-oMJ!<%D_BJnYgoqyHnD|m>|hsr*hdQo Wp*NHvjWVK9#x%-=MtSnjr}YA`88kxx literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01j/python/u_gaussian_full_py.npy b/example/1d-linear-convection/weno3/julia/01j/python/u_gaussian_full_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..b762f0fe295b52a9a176817021995ce5a561d778 GIT binary patch literal 480 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yCOVor3bhL411<(MU?Ou)P3Oi_+soE}ylQv)*=cg#Ue98-%FeWXxBn~o z-*&0B&Pq*ej`k{D0z3w9EA6|!ORWz4wA}u9Vfdl$E2r%H!VVg&w0>m2nCbZM)%@?^ zbQ@G0rY;|Sof!j>qBKc_8=pP9zb3SzgEt8=WEn__NZ z$*8-a{Z)*4!nvvF3 wqbFAl+ziF%o9|Zhkuvbx>B0B<*(1H@$~rUDs~6~AeE<2_q`W{KwBTd_0INu`7ytkO literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01j/python/u_gaussian_interior_py.npy b/example/1d-linear-convection/weno3/julia/01j/python/u_gaussian_interior_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..68af8954e7a17e69b6b2e6954b78ee542d4e96a0 GIT binary patch literal 448 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#y20EHL3bhL411=Mpb80#_p4wiv{^M1<)6Y(m^Y(fcvsHGc?YsS7$^W)X zt#wvvVso@t=@Q^Ecw1@T^<8Rp;HTyG#|y&`bzeDU-xqe!V5RjV`^8Mhf3N0$2dCSh z;xKjjP<`JzA?Co${oe+$2WBtK9e1T6?wYnbA&=>Lk?qOJ*(aWE{cN*=zyHlG=1iNl zN(soJS7TeOgcr6%N&Go&S^Ug2epV2>rCgn3z1$RY3rj}b z1?{h5%p2Bhd1c#FYWC{z!qS+GX48p_*$(R2wVEWXJEgqJq{jHxHq-S&9M{! literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01j/python/u_input.npy b/example/1d-linear-convection/weno3/julia/01j/python/u_input.npy new file mode 100644 index 0000000000000000000000000000000000000000..ef506fa75cacbbe63dfcc17332ddd6c33de5b888 GIT binary patch literal 480 zcmbWrI}gE70EXdX6O+MUFr0`uNFy$FzqBs35sSejjUF+GN-GwT_z(W5pP?kM2cBT{2ChY{-ZF ze<|^^w?*2qNlaq~vzSAPc`RTNOISezt60N2Hn52%wy=#I>|zi5IKUx}aEun(u}>5e R8pV`GF{4q;X%r6a*dIEnGEe{j literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01j/python/u_neumann_py.npy b/example/1d-linear-convection/weno3/julia/01j/python/u_neumann_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..fa723d1efce906983b6e6710379207d15afbc271 GIT binary patch literal 480 zcmbWrI}gE70EXdX6O+MUFr0`uC5^ZgRkymtMl1%CG}^`>Dy>*V;y?JqN?(i7w>|hs7?4gBy9N-W~I7U11fnvf( PG3BF}@lnkAKgRt9WDhZ? literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/01j/python/u_periodic_py.npy b/example/1d-linear-convection/weno3/julia/01j/python/u_periodic_py.npy new file mode 100644 index 0000000000000000000000000000000000000000..156aff85dfa4c84deaaed404e0dccc37937f8836 GIT binary patch literal 480 zcmbWzyAA+Jw5Jw7YF-Tj}jFJcs^aeGCKh-lzUPGOs<_`6u&g%W>?2t#0c7 zOUUOY-iJGid|Tp}#1y76g9@{l!#rA8L>o(3#tK%kh7Q)TflX{-8#~xV7kk*p0eWBW QC_@@$M5Bypl;8h*0k_X5-%Mo@PtK#WJA?o%og6P}-0bsJ$1U?l6M7O94XfzYY=Kp9oR6e-T9A{tpmy_6tDF-R}f( z-+mW}`}RZKw;$@h{Tm?e+z)l%epjeEd#FAks5zgY=FW!NlLxi;2-F?cP8 literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/02/julia/boundary.jl b/example/1d-linear-convection/weno3/julia/02/julia/boundary.jl new file mode 100644 index 000000000..5b0baaf43 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/julia/boundary.jl @@ -0,0 +1,90 @@ +# julia/boundary.jl +# 目标:与 Python boundary.py 行为完全一致(1:1 同构) +# 前提:ist/ied 在 Julia 中按 1-based 设置(ist = nghosts + 1) + +using NPZ # 仅用于测试,实际模块可不依赖 + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象基类(Julia 风格:用函数接口) ---------------------- +# Julia 无 abstract class,用文档+约定 +# 所有子类型必须实现 apply!(bc, u) + +# ---------------------- PeriodicBoundary ---------------------- +mutable struct PeriodicBoundary + cfd::Any +end + +function apply!(self::PeriodicBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist # 1-based, e.g., 3 + ied = self.cfd.domain.ied # 1-based, e.g., 43 + + # 左 ghost: u[ist - 1 - ig] = u[ied - 1 - ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ied - 1 - ig] + end + # 右 ghost: u[ied + ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ied + ig] = u[ist + ig] + end +end + +# ---------------------- DirichletBoundary ---------------------- +mutable struct DirichletBoundary + cfd::Any +end + +function apply!(self::DirichletBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + left_value = getfield_safe(self.cfd.config, :left_boundary_value, 1.0) + right_value = getfield_safe(self.cfd.config, :right_boundary_value, 2.0) + + # 左边界 + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = left_value + end + # 右边界 + for ig in 0:(nghosts-1) + u[ied + ig] = right_value + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Dirichlet边界: 左值=$left_value, 右值=$right_value") + end +end + +# ---------------------- NeumannBoundary ---------------------- +mutable struct NeumannBoundary + cfd::Any +end + +function apply!(self::NeumannBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + # 左边界零梯度:u[ist - 1 - ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ist + ig] + end + # 右边界零梯度:u[ied + ig] = u[ied - 1 - ig] ← 注意是 ied - 1 - ig + for ig in 0:(nghosts-1) + u[ied + ig] = u[ied - 1 - ig] + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Neumann边界: 零梯度") + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/julia/config.jl b/example/1d-linear-convection/weno3/julia/02/julia/config.jl new file mode 100644 index 000000000..bf48de3a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/julia/config.jl @@ -0,0 +1,68 @@ +# julia/config.jl +""" +CfdConfig:与 Python config.py 完全同构 +""" +mutable struct CfdConfig + ic_type::String + recon_scheme::String + flux_type::String + rk_order::Int + wave_speed::Float64 + final_time::Float64 + dt::Float64 + boundary_type::String + left_boundary_value::Float64 + right_boundary_value::Float64 + spatial_order::Int + + function CfdConfig() + new( + "step", + "eno", + "rusanov", + 1, + 1.0, + 0.625, + 0.025, + "periodic", + 1.0, + 2.0, + 2 + ) + end +end + +""" +专用配置:重建方案(链式调用) +""" +function with_reconstruction(cfg::CfdConfig, scheme::String, order::Union{Int, Nothing}=nothing) + cfg.recon_scheme = lowercase(scheme) + + if order !== nothing + cfg.spatial_order = order + else + if startswith(cfg.recon_scheme, "weno") + cfg.spatial_order = 5 + elseif cfg.recon_scheme == "eno" + cfg.spatial_order = 3 + else + error("不支持的重建格式:$scheme(仅支持 eno/weno)") + end + end + + return cfg # 支持链式调用 +end + +""" +专用配置:边界条件(链式调用) +""" +function with_boundary(cfg::CfdConfig, bc_type::String; left_value=nothing, right_value=nothing) + cfg.boundary_type = bc_type + if left_value !== nothing + cfg.left_boundary_value = left_value + end + if right_value !== nothing + cfg.right_boundary_value = right_value + end + return cfg +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/julia/domain.jl b/example/1d-linear-convection/weno3/julia/02/julia/domain.jl new file mode 100644 index 000000000..a7edc2260 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/julia/domain.jl @@ -0,0 +1,61 @@ +# julia/domain.jl +""" +Domain 结构体(与 domain.py 完全同构) +- 保存 config +- nghosts 计算逻辑 1:1 +- ist/ied 为 0-based(与 Python 一致) +""" + +include("mesh.jl") + +mutable struct Domain + config::Any # 保存 config(与 Python 一致) + mesh::Mesh + nghosts::Int + ist::Int # 0-based + ied::Int # 0-based + ntcells::Int +end + +function Domain(config::Any, mesh::Mesh) + nghosts = _calc_nghosts(config) + ist = nghosts + 1 # ← 1-based 起始索引 + ied = ist + mesh.ncells # ← 1-based 结束索引(不包含) + ntcells = mesh.ncells + 2 * nghosts + Domain(config, mesh, nghosts, ist, ied, ntcells) +end + +function _calc_nghosts(config::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + order = getfield_safe(config, :spatial_order, 2) + + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + if scheme == "eno" + nghosts = order + elseif startswith(scheme, "weno") + nghosts = order ÷ 2 + 1 + else + error("未知重建格式 $(scheme),无法计算ghost层!") + end + + if nghosts <= 0 + error("计算得到的ghost层数量无效:$(nghosts)(阶数$(order),格式$(scheme))") + end + + return nghosts +end + +function is_physical_cell(domain::Domain, idx::Int) + return domain.ist <= idx < domain.ied +end + +function get_physical_indices(domain::Domain) + return domain.ist:(domain.ied - 1) +end + +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/julia/flux.jl b/example/1d-linear-convection/weno3/julia/02/julia/flux.jl new file mode 100644 index 000000000..047598f7a --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/julia/flux.jl @@ -0,0 +1,70 @@ +# julia/flux.jl +""" +通量计算器模块(与 flux.py 完全同构) +- 抽象基类 + 具体实现 +- 字段:cfd, config, mesh, wave_speed +""" + +include("mesh.jl") + +# ---------------------- 抽象基类 ---------------------- +""" +InviscidFluxCalculator 抽象类型 +Julia 无 ABC,用文档约定 +所有子类型必须实现 compute! +""" +abstract type InviscidFluxCalculator end + +# ---------------------- RusanovFluxCalculator ---------------------- +mutable struct RusanovFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function RusanovFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::RusanovFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = calc.wave_speed + c_R = calc.wave_speed + F_L = c_L * u_L + F_R = c_R * u_R + Smax = max(abs(c_L), abs(c_R)) + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + end +end + +# ---------------------- EngquistOsherFluxCalculator ---------------------- +mutable struct EngquistOsherFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function EngquistOsherFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::EngquistOsherFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + c = calc.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/julia/initial_condition.jl b/example/1d-linear-convection/weno3/julia/02/julia/initial_condition.jl new file mode 100644 index 000000000..5e029e8d2 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/julia/initial_condition.jl @@ -0,0 +1,86 @@ +# julia/initial_condition.jl +""" +初始条件模块(Julia 版) +目标:与 Python initial_condition.py 行为完全一致 +""" + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象接口(Julia 无继承,用函数约定) ---------------------- +# 所有初始条件必须实现: +# evaluate_at(ic, x::AbstractVector{Float64}) -> Vector{Float64} +# apply(ic, solution) + +# ---------------------- StepFunctionIC ---------------------- +struct StepFunctionIC + config::Any +end + +function evaluate_at(ic::StepFunctionIC, x::AbstractVector{Float64}) + u0 = ones(Float64, length(x)) + for i in eachindex(x) + if 0.5 <= x[i] <= 1.0 + u0[i] = 2.0 + end + end + return u0 +end + +function apply(ic::StepFunctionIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- SineWaveIC ---------------------- +struct SineWaveIC + config::Any +end + +function evaluate_at(ic::SineWaveIC, x::AbstractVector{Float64}) + L = getfield_safe(ic.config, :domain_length, 2.0) + return sin.(2π * x / L) +end + +function apply(ic::SineWaveIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- GaussianPulseIC ---------------------- +struct GaussianPulseIC + config::Any +end + +function evaluate_at(ic::GaussianPulseIC, x::AbstractVector{Float64}) + center = getfield_safe(ic.config, :pulse_center, 0.5) + width = getfield_safe(ic.config, :pulse_width, 0.1) + return exp.(-((x .- center) ./ width).^2) +end + +function apply(ic::GaussianPulseIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- 公共辅助函数 ---------------------- +""" +将初始场 values 应用到 solution.u 的物理区域(含 ghost 的数组) +""" +function _apply_to_interior(solution, values) + domain = solution.domain + # Python: for i in range(domain.ist, domain.ied) + # Julia: for i in domain.ist:domain.ied-1 (因为 ied 是结束索引,不包含) + for i in domain.ist:(domain.ied - 1) + j = i - domain.ist + 1 # values 是 1-based,i - ist 是 0-based → +1 + solution.u[i] = values[j] + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/julia/mesh.jl b/example/1d-linear-convection/weno3/julia/02/julia/mesh.jl new file mode 100644 index 000000000..1b0dd4bf9 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/julia/mesh.jl @@ -0,0 +1,45 @@ +# julia/mesh.jl +""" +Mesh 结构体(与提供的 mesh.py 完全同构) +- 字段名、初始化顺序、循环逻辑完全一致 +- 不做任何泛化或优化 +""" + +mutable struct Mesh + xmin::Float64 + xmax::Float64 + ncells::Int + nnodes::Int + nx::Int + x::Vector{Float64} + xcc::Vector{Float64} + L::Float64 # 在 init_mesh 中赋值 + dx::Float64 # 在 init_mesh 中赋值 + + function Mesh() + # 与 Python __init__ 完全一致 + xmin = 0.0 + xmax = 2.0 + ncells = 40 + nnodes = ncells + 1 + nx = ncells + x = zeros(Float64, nnodes) + xcc = zeros(Float64, ncells) + + # 调用 init_mesh(模拟 Python) + L = xmax - xmin + dx = L / ncells + + # Generate node coordinates: for i in range(self.nnodes) + for i in 1:nnodes + x[i] = xmin + (i - 1) * dx # Python i → Julia i-1 + end + + # Generate cell center coordinates + for i in 1:ncells + xcc[i] = 0.5 * (x[i] + x[i+1]) + end + + new(xmin, xmax, ncells, nnodes, nx, x, xcc, L, dx) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/julia/plotter.jl b/example/1d-linear-convection/weno3/julia/02/julia/plotter.jl new file mode 100644 index 000000000..a77d8d688 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/julia/plotter.jl @@ -0,0 +1,147 @@ +# julia/plotter.jl +""" +CFDPlotter 的 Julia 实现(通过 PythonCall.jl 调用 Matplotlib) +确保与 Python plotter.py 行为完全一致 +""" + +using PythonCall + +# 初始化 Python 环境(加载 matplotlib, inflect) +const plt = pyimport("matplotlib.pyplot") +const inflect = pyimport("inflect") + +mutable struct CFDPlotter + default_styles::Dict{String, Any} + p::Py +end + +function CFDPlotter() + default_styles = Dict{String, Any}( + "numerical" => Dict( + :color => "blue", + :linestyle => "-", + :marker => "o", + :markerfacecolor => "none" + ), + "analytical" => Dict( + :color => "red", + :linestyle => "--", + :marker => "", + :linewidth => 1.5 + ), + "comparison" => [ + Dict(:color => "black", :linestyle => "-", :marker => "o", :markerfacecolor => "none"), + Dict(:color => "blue", :linestyle => "--", :marker => "s", :markerfacecolor => "none"), + Dict(:color => "green", :linestyle => ":", :marker => "^", :markerfacecolor => "none") + ] + ) + p = inflect.engine() + CFDPlotter(default_styles, p) +end + +""" +轻量即时绘图(快速验证结果) +""" +function plot_quick(plotter::CFDPlotter, cfd_result::Dict; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + rk_order = cfd_result["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = cfd_result["config"]["final_time"] + order = cfd_result["config"]["order"] + scheme = uppercase(cfd_result["config"]["scheme"]) + actual_title = "1D Convection (t=$(final_time))\n$(order)th-order $(scheme) + $(rk_str)-order RK" + end + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"]; + label="Numerical ($(uppercase(cfd_result["config"]["scheme"])))", + plotter.default_styles["numerical"]..., + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + # 通用样式 + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +""" +多格式/多精度对比绘图 +""" +function plot_comparison(plotter::CFDPlotter, result_list::Vector{Dict{String, Any}}; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + schemes = [uppercase(r["config"]["scheme"]) * string(r["config"]["order"]) for r in result_list] + rk_order = result_list[1]["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = result_list[1]["config"]["final_time"] + actual_title = "1D Convection Comparison (t=$(final_time))\n$(join(schemes, ", ")) + $(rk_str)-order RK" + end + + # 绘制多个数值解 + for (i, res) in enumerate(result_list) + style = plotter.default_styles["comparison"][mod1(i, length(plotter.default_styles["comparison"]))] + label = "Numerical ($(uppercase(res["config"]["scheme"]))$(res["config"]["order"]))" + plt.plot( + res["x"], res["numerical"]; + label=label, + style..., + markersize=5, linewidth=0.5 + ) + end + + # 绘制解析解 + plt.plot( + result_list[1]["x"], result_list[1]["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +function _set_common_style(plotter::CFDPlotter, title::String) + plt.title(title, fontsize=12) + plt.xlabel("x", fontsize=10) + plt.ylabel("u", fontsize=10) + plt.legend(fontsize=9) + plt.grid(true, color="gray", linestyle="--", linewidth=0.5, alpha=0.7) + plt.tight_layout() +end + +""" +快捷函数:ENO/WENO对比绘图 +""" +function plot_eno_weno_comparison(eno_result::Dict, weno_result::Dict; save_path=nothing) + plotter = CFDPlotter() + plot_comparison(plotter, [eno_result, weno_result]; save_path=save_path) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/julia/reconstructor/eno.jl b/example/1d-linear-convection/weno3/julia/02/julia/reconstructor/eno.jl new file mode 100644 index 000000000..e78a636f3 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/julia/reconstructor/eno.jl @@ -0,0 +1,107 @@ +# julia/reconstructor/eno.jl +""" +ENO 重构器(与 reconstructor/eno.py 完全同构) +""" + +# ---------------------- ENO 系数初始化 ---------------------- +function _init_eno_coef!(spatial_order::Int, coef::Matrix{Float64}) + if spatial_order == 1 + coef[1, 1] = 1.0 + coef[2, 1] = 1.0 + elseif spatial_order == 2 + coef[1, 1:2] = [3.0/2.0, -1.0/2.0] + coef[2, 1:2] = [1.0/2.0, 1.0/2.0] + coef[3, 1:2] = [-1.0/2.0, 3.0/2.0] + elseif spatial_order == 3 + coef[1, 1:3] = [11.0/6.0, -7.0/6.0, 1.0/3.0] + coef[2, 1:3] = [1.0/3.0, 5.0/6.0, -1.0/6.0] + coef[3, 1:3] = [-1.0/6.0, 5.0/6.0, 1.0/3.0] + coef[4, 1:3] = [1.0/3.0, -7.0/6.0, 11.0/6.0] + elseif spatial_order == 4 + coef[1, 1:4] = [25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0] + coef[2, 1:4] = [1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0] + coef[3, 1:4] = [-1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0] + coef[4, 1:4] = [1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0] + coef[5, 1:4] = [-1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0] + elseif spatial_order == 5 + coef[1, 1:5] = [137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0] + coef[2, 1:5] = [1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0] + coef[3, 1:5] = [-1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0] + coef[4, 1:5] = [1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0] + coef[5, 1:5] = [-1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0] + coef[6, 1:5] = [1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0] + elseif spatial_order == 6 + coef[1, 1:6] = [49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0] + coef[2, 1:6] = [1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0] + coef[3, 1:6] = [-1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0] + coef[4, 1:6] = [1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0] + coef[5, 1:6] = [-1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0] + coef[6, 1:6] = [1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0] + coef[7, 1:6] = [-1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0] + elseif spatial_order == 7 + coef[1, 1:7] = [363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0] + coef[2, 1:7] = [1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0] + coef[3, 1:7] = [-1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0] + coef[4, 1:7] = [1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0] + coef[5, 1:7] = [-1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0] + coef[6, 1:7] = [1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0] + coef[7, 1:7] = [-1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0] + coef[8, 1:7] = [1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0] + else + error("ENO 系数未实现 order=$spatial_order") + end +end + +# ---------------------- ENO 重构器 ---------------------- +mutable struct EnoReconstructor + spatial_order::Int + ntcells::Int + lmc::Vector{Int} + coef::Matrix{Float64} + dd::Matrix{Float64} + + function EnoReconstructor(spatial_order::Int, ntcells::Int) + lmc = zeros(Int, ntcells) + coef = zeros(Float64, spatial_order + 1, spatial_order) + dd = zeros(Float64, spatial_order, ntcells) + _init_eno_coef!(spatial_order, coef) + new(spatial_order, ntcells, lmc, coef, dd) + end +end + +function reconstruct(rec::EnoReconstructor, q::Vector{Float64}, cfd::Any) + # 1. 差商计算 (dd[1,:] = q) + @views rec.dd[1, :] .= q + for m in 2:rec.spatial_order + for j in 1:(rec.ntcells - m + 1) + rec.dd[m, j] = rec.dd[m-1, j+1] - rec.dd[m-1, j] + end + end + + # 2. 选择 smoothest stencil + domain = cfd.domain + for i in (domain.ist - 1):(domain.ied) # Python: range(ist-1, ied+1) → ied+1-1 = ied + rec.lmc[i] = i + for m in 2:rec.spatial_order + if abs(rec.dd[m, rec.lmc[i] - 1]) < abs(rec.dd[m, rec.lmc[i]]) + rec.lmc[i] -= 1 + end + end + end + + # 3. 重构界面值 + solution = cfd.solution + for i in domain.ist:(domain.ied) # Python: range(ist, ied+1) → ied+1-1 = ied + j = i - domain.ist + 1 # Julia 1-based + k1 = rec.lmc[i - 1] + k2 = rec.lmc[i] + r1 = (i - 1) - k1 + 1 + r2 = i - k2 + 1 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in 1:rec.spatial_order + solution.q_face_left[j] += q[k1 + m - 1] * rec.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m - 1] * rec.coef[r2, m] + end + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/julia/reconstructor/weno3.jl b/example/1d-linear-convection/weno3/julia/02/julia/reconstructor/weno3.jl new file mode 100644 index 000000000..2b6fe1abb --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/julia/reconstructor/weno3.jl @@ -0,0 +1,65 @@ +# julia/reconstructor/weno3.jl +""" +WENO3 重构器(与 reconstructor/weno3.py 完全同构) +""" + +mutable struct Weno3Reconstructor + # 无字段,与 Python 一致 +end + +function reconstruct(rec::Weno3Reconstructor, q::Vector{Float64}, cfd::Any) + domain = cfd.domain + solution = cfd.solution + _reconstruct_left_interfaces(domain, q, solution.q_face_left) + _reconstruct_right_interfaces(domain, q, solution.q_face_right) +end + +function _reconstruct_left_interfaces(domain, u, qL) + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in (domain.ist - 1):(domain.ied - 1) + j = i - (domain.ist - 1) + 1 # ← Julia 1-based: j = i - (ist-1) 对应 Python j = i - (ist-1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = _reconstruct_from_left_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_right_interfaces(domain, u, qR) + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in domain.ist:domain.ied + j = i - domain.ist + 1 # ← Julia 1-based: j = i - ist 对应 Python j = i - ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = _reconstruct_from_right_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_from_left_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -0.5*v1 + 1.5*v2 # r=1 stencil + q1 = 0.5*v2 + 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end + +function _reconstruct_from_right_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 0.5*v1 + 0.5*v2 # r=1 stencil + q1 = 1.5*v2 - 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/julia/residual.jl b/example/1d-linear-convection/weno3/julia/02/julia/residual.jl new file mode 100644 index 000000000..e8fd65843 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/julia/residual.jl @@ -0,0 +1,65 @@ +# julia/residual.jl +""" +残差计算器(与 residual.py 完全同构) +- 封装重建→通量→散度完整流程 +- 依赖 cfd 的多个字段 +""" + +include("mesh.jl") + +mutable struct ResidualCalculator + cfd::Any + config::Any + domain::Any + solution::Any + mesh::Mesh + reconstructor::Any + flux_calculator::Any # 通量计算器(外部传入,替代工厂) + + function ResidualCalculator(cfd::Any, flux_calculator::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + mesh = domain.mesh + reconstructor = cfd.reconstructor + + new(cfd, config, domain, solution, mesh, reconstructor, flux_calculator) + end +end + +""" +计算完整残差(对外唯一接口) +""" +function compute!(calc::ResidualCalculator) + _reconstruct(calc) + _compute_inviscid_flux(calc) + _compute_flux_divergence(calc) +end + +""" +私有方法:界面值重建 +""" +function _reconstruct(calc::ResidualCalculator) + reconstruct(calc.reconstructor, calc.solution.u, calc.cfd) +end + +""" +私有方法:计算无粘通量 +""" +function _compute_inviscid_flux(calc::ResidualCalculator) + compute!(calc.flux_calculator, + calc.solution.q_face_left, + calc.solution.q_face_right, + calc.solution.flux) +end + +""" +私有方法:计算通量散度(残差 = -dF/dx) +""" +function _compute_flux_divergence(calc::ResidualCalculator) + solution = calc.solution + mesh = calc.mesh + for i in 1:mesh.ncells + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / mesh.dx + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/julia/run_eno_weno.jl b/example/1d-linear-convection/weno3/julia/02/julia/run_eno_weno.jl new file mode 100644 index 000000000..58b6cfa5f --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/julia/run_eno_weno.jl @@ -0,0 +1,50 @@ +# julia/run_eno_weno.jl +""" +1:1 复刻 run_eno_weno.py 的 Julia 版本 +""" + +include("config.jl") +include("mesh.jl") +include("solver.jl") +include("plotter.jl") + +function performEnoWenoAnalysis() + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO3 求解 + println("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + with_reconstruction(config_eno3, "eno", 3) # 显式指定 3 阶 + config_eno3.dt = 0.0025 # 覆盖默认值 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + run!(cfd_eno3) # 求解并生成 result 字典 + + # 3. 配置并运行 WENO3 求解 + println("Running WENO3 solver...") + config_weno3 = CfdConfig() + with_reconstruction(config_weno3, "weno", 3) # 显式指定 3 阶(WENO 默认 5 阶) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + run!(cfd_weno3) + + # 5. 绘制 ENO/WENO 对比图 + println("Plotting comparison results...") + plot_eno_weno_comparison( + cfd_eno3.result, + cfd_weno3.result; + save_path="eno_weno_comparison.png" + ) + + return cfd_eno3, cfd_weno3 +end + +# 主程序入口 +if abspath(PROGRAM_FILE) == @__FILE__ + performEnoWenoAnalysis() + println("Analysis completed!") +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/julia/solution.jl b/example/1d-linear-convection/weno3/julia/02/julia/solution.jl new file mode 100644 index 000000000..90ca03931 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/julia/solution.jl @@ -0,0 +1,75 @@ +# julia/solution.jl +""" +Solution 结构体(与真实 solution.py 完全同构) +字段顺序、方法、逻辑 1:1 对齐 +""" + +include("mesh.jl") +include("domain.jl") +include("initial_condition.jl") + +mutable struct Solution + domain::Domain + q_face_left::Vector{Float64} + q_face_right::Vector{Float64} + flux::Vector{Float64} + res::Vector{Float64} + u::Vector{Float64} + un::Vector{Float64} + + function Solution(config::Any, domain::Domain) + mesh = domain.mesh + + q_face_left = zeros(Float64, mesh.nnodes) + q_face_right = zeros(Float64, mesh.nnodes) + flux = zeros(Float64, mesh.nnodes) + res = zeros(Float64, mesh.ncells) + u = zeros(Float64, domain.ntcells) + un = zeros(Float64, domain.ntcells) + + sol = new(domain, q_face_left, q_face_right, flux, res, u, un) + initialize_from_config(sol, config) + return sol + end +end + +""" +重置解数组为初始状态 +""" +function reset_solution(sol::Solution) + fill!(sol.u, 0.0) + fill!(sol.un, 0.0) +end + +""" +根据配置初始化场 +注:暂用硬编码替代 InitialConditionFactory +""" +function initialize_from_config(sol::Solution, config::Any) + ic_type = getfield_safe(config, :ic_type, "step") + + # 硬编码创建 IC(替代 InitialConditionFactory) + ic = if ic_type == "step" + StepFunctionIC(config) + elseif ic_type == "sin" + SineWaveIC(config) + elseif ic_type == "gaussian" + GaussianPulseIC(config) + else + error("未知初始条件类型: $ic_type") + end + + apply(ic, sol) # 调用 IC.apply +end + +""" +更新旧场:un = u +""" +function update_old_field(sol::Solution) + sol.un .= sol.u # 等价于 Python 的 un[:] = u[:] +end + +# ---------------------- 辅助函数 ---------------------- +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/julia/solver.jl b/example/1d-linear-convection/weno3/julia/02/julia/solver.jl new file mode 100644 index 000000000..243ccf8fd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/julia/solver.jl @@ -0,0 +1,168 @@ +# julia/solver.jl +""" +CFD 求解器主类(与 solver.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("initial_condition.jl") +include("boundary.jl") +include("flux.jl") +include("residual.jl") +include("time_integration.jl") +include("reconstructor/eno.jl") +include("reconstructor/weno3.jl") + +# ---------------------- Cfd 求解器 ---------------------- +mutable struct Cfd + config::Any + domain::Domain + solution::Solution + reconstructor::Any + residual_calculator::ResidualCalculator + integrator::Any + boundary_condition::Any + result::Dict{String, Any} + + function Cfd(config::Any, mesh::Mesh) + domain = Domain(config, mesh) + solution = Solution(config, domain) + + # 在 Cfd 构造函数中 + # =============== 重建器创建 =============== + recon_scheme = config.recon_scheme + spatial_order = config.spatial_order + + # ✅ 模拟 Python ReconstructorFactory 的逻辑 + if recon_scheme == "weno" + recon_scheme = "weno$(spatial_order)" + end + + # 现在根据 recon_scheme 创建 + reconstructor = if recon_scheme == "eno" + EnoReconstructor(spatial_order, domain.ntcells) + elseif recon_scheme == "weno3" + Weno3Reconstructor() + else + error("不支持的重建格式: $recon_scheme") + end + + # 通量计算器 + flux_calculator = if config.flux_type == "rusanov" + RusanovFluxCalculator((config=config, domain=domain)) + elseif config.flux_type == "engquist-osher" + EngquistOsherFluxCalculator((config=config, domain=domain)) + else + error("不支持的通量类型: $(config.flux_type)") + end + + # 残差计算器 + residual_calculator = ResidualCalculator( + (config=config, domain=domain, solution=solution, reconstructor=reconstructor), + flux_calculator + ) + + # 边界条件 + boundary_condition = if config.boundary_type == "periodic" + PeriodicBoundary((config=config, domain=domain)) + elseif config.boundary_type == "dirichlet" + DirichletBoundary((config=config, domain=domain)) + elseif config.boundary_type == "neumann" + NeumannBoundary((config=config, domain=domain)) + else + error("不支持的边界类型: $(config.boundary_type)") + end + + # 构造用于积分器初始化的上下文对象(NamedTuple,模拟 cfd 接口) + integrator_context = ( + config = config, + domain = domain, + solution = solution, + residual_calculator = residual_calculator, + boundary_condition = boundary_condition + ) + + # 使用注册表工厂创建时间推进器 + integrator = create_integrator(integrator_context) + + #@show typeof(integrator) + + # 注入 cfd 到 residual_calculator 和 integrator + residual_calculator.cfd = (config=config, domain=domain, solution=solution, reconstructor=reconstructor, residual_calculator=residual_calculator, integrator=integrator, boundary_condition=boundary_condition) + integrator.base.cfd = residual_calculator.cfd + + result = Dict{String, Any}() + new(config, domain, solution, reconstructor, residual_calculator, integrator, boundary_condition, result) + end +end + +""" +通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界 +""" +function exact_solution(cfd::Cfd) + x = cfd.domain.mesh.xcc + T = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = @. (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = if cfd.config.ic_type == "step" + StepFunctionIC(cfd.config) + elseif cfd.config.ic_type == "sin" + SineWaveIC(cfd.config) + elseif cfd.config.ic_type == "gaussian" + GaussianPulseIC(cfd.config) + else + error("未知初始条件: $(cfd.config.ic_type)") + end + + return evaluate_at(ic, x_shifted) +end + +""" +主求解循环 +""" +function run!(cfd::Cfd) + # 应用初始边界条件并同步 old field + apply!(cfd.boundary_condition, cfd.solution.u) + update_old_field(cfd.solution) + + t = 0.0 + dt_old = cfd.config.dt + dt = dt_old + + while t < cfd.config.final_time + if t + dt > cfd.config.final_time + dt = cfd.config.final_time - t + end + #@show t, dt, maximum(cfd.solution.u), minimum(cfd.solution.u) + # 执行时间步 + step(cfd.integrator, dt) + t += dt + end + + # 恢复 dt + cfd.config.dt = dt_old + + # 整理结果 + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied-1] # Python: [ist:ied] + analytical = exact_solution(cfd) + + cfd.result = Dict( + "x" => cfd.domain.mesh.xcc, + "numerical" => u_numerical, + "analytical" => analytical, + "config" => Dict( + "scheme" => cfd.config.recon_scheme, + "order" => cfd.config.spatial_order, + "rk_order" => cfd.config.rk_order, + "final_time" => cfd.config.final_time + ) + ) + + return u_numerical +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/julia/time_integration.jl b/example/1d-linear-convection/weno3/julia/02/julia/time_integration.jl new file mode 100644 index 000000000..43b540af7 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/julia/time_integration.jl @@ -0,0 +1,168 @@ +# julia/time_integration.jl +""" +时间推进器模块(与 time_integration.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("residual.jl") +include("boundary.jl") + +# ---------------------- 抽象时间推进器基类 ---------------------- +abstract type TimeIntegrator end + +mutable struct TimeIntegratorBase <: TimeIntegrator + cfd::Any + config::Any + domain::Domain + solution::Solution + residual_calculator::Any # ResidualCalculator +end + +function TimeIntegratorBase(cfd::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + residual_calculator = cfd.residual_calculator + TimeIntegratorBase(cfd, config, domain, solution, residual_calculator) +end + +function compute_residual(integrator::TimeIntegratorBase) + compute!(integrator.residual_calculator) +end + +function apply_boundary(integrator::TimeIntegratorBase) + apply!(integrator.cfd.boundary_condition, integrator.solution.u) +end + +function map_idx(integrator::TimeIntegratorBase, i::Int) + return i - integrator.domain.ist + 1 # ← +1 转为 1-based +end + +# ---------------------- RK1Integrator ---------------------- +mutable struct RK1Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK1Integrator(cfd::Any) + RK1Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK1Integrator, dt::Float64) + compute_residual(integrator.base) + for i in integrator.base.domain.ist:(integrator.base.domain.ied - 1) + j = map_idx(integrator.base, i) + integrator.base.solution.u[i] += dt * integrator.base.solution.res[j] + end + apply_boundary(integrator.base) + update_old_field(integrator.base.solution) +end + +# ---------------------- RK2Integrator ---------------------- +mutable struct RK2Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK2Integrator(cfd::Any) + RK2Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK2Integrator, dt::Float64) + base = integrator.base + # 阶段1:预测步 + compute_residual(base) + u_pred = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u_pred[i] += dt * base.solution.res[j] + end + base.solution.u .= u_pred + apply_boundary(base) + # 阶段2:校正步 + compute_residual(base) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = 0.5 * base.solution.un[i] + 0.5 * base.solution.u[i] + 0.5 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end + +# ---------------------- RK3Integrator ---------------------- +mutable struct RK3Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK3Integrator(cfd::Any) + RK3Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK3Integrator, dt::Float64) + base = integrator.base + # 阶段1 + compute_residual(base) + u1 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u1[i] += dt * base.solution.res[j] + end + base.solution.u .= u1 + apply_boundary(base) + # 阶段2 + compute_residual(base) + u2 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u2[i] = 0.75 * base.solution.un[i] + 0.25 * base.solution.u[i] + 0.25 * dt * base.solution.res[j] + end + base.solution.u .= u2 + apply_boundary(base) + # 阶段3 + compute_residual(base) + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = c1 * base.solution.un[i] + c2 * base.solution.u[i] + c3 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end + +# ---------------------- 注册表 + 工厂(方案2)---------------------- + +const INTEGRATOR_REGISTRY = Dict{String, Function}() + +function register_integrator(name::String, ctor::Function) + if haskey(INTEGRATOR_REGISTRY, name) + @warn "积分器 '$name' 已注册,将被覆盖" + end + INTEGRATOR_REGISTRY[name] = ctor +end + +function create_integrator(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + config = cfd.config + if !hasproperty(config, :rk_order) + error("cfd.config 缺少 rk_order 字段") + end + rk_order = config.rk_order + if !(rk_order isa Integer) || rk_order < 1 + error("rk_order 必须为正整数,当前值: $rk_order (类型: $(typeof(rk_order)))") + end + + name = "rk$rk_order" + if !haskey(INTEGRATOR_REGISTRY, name) + available = sort(collect(keys(INTEGRATOR_REGISTRY))) + error("未注册的时间积分器: '$name'。可用选项: $available") + end + + return INTEGRATOR_REGISTRY[name](cfd) +end + +# 注册内置积分器 +register_integrator("rk1", cfd -> RK1Integrator(cfd)) +register_integrator("rk2", cfd -> RK2Integrator(cfd)) +register_integrator("rk3", cfd -> RK3Integrator(cfd)) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/python/boundary.py b/example/1d-linear-convection/weno3/julia/02/python/boundary.py new file mode 100644 index 000000000..6054f92de --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/python/boundary.py @@ -0,0 +1,103 @@ +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from factories.base_factory import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/python/cfd_registry.py b/example/1d-linear-convection/weno3/julia/02/python/cfd_registry.py new file mode 100644 index 000000000..c12eb106c --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/python/cfd_registry.py @@ -0,0 +1,62 @@ +# cfd_registry.py +""" +CFD注册系统统一导入模块 +所有其他模块从这里导入注册系统 +""" + +import sys +import os + +# 添加当前目录到路径,确保能找到registry.py +current_dir = os.path.dirname(os.path.abspath(__file__)) +if current_dir not in sys.path: + sys.path.insert(0, current_dir) + +try: + # 尝试导入注册系统 + from registry import ComponentRegistry, register_component + REGISTRY_AVAILABLE = True +except ImportError: + # 如果找不到,提供简单的空实现 + print("⚠️ 警告: 未找到 registry.py,使用最小化回退实现") + + class ComponentRegistry: + """最小化的注册系统回退实现""" + _registries = {} + + @classmethod + def register(cls, category, name, component_class): + if category not in cls._registries: + cls._registries[category] = {} + cls._registries[category][name] = component_class + + @classmethod + def get(cls, category, name): + if category not in cls._registries: + raise ValueError(f"未知类别: {category}") + if name not in cls._registries[category]: + raise ValueError(f"未找到: {category}.{name}") + return cls._registries[category][name] + + @classmethod + def create(cls, category, name, *args, **kwargs): + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) + for cat, comps in cls._registries.items()} + + def register_component(category, name=None): + """简化的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + REGISTRY_AVAILABLE = False + +# 导出统一的接口 +__all__ = ['ComponentRegistry', 'register_component', 'REGISTRY_AVAILABLE'] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/python/config.py b/example/1d-linear-convection/weno3/julia/02/python/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/python/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/python/domain.py b/example/1d-linear-convection/weno3/julia/02/python/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/python/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/python/factories/base_factory.py b/example/1d-linear-convection/weno3/julia/02/python/factories/base_factory.py new file mode 100644 index 000000000..7b8b6105e --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/python/factories/base_factory.py @@ -0,0 +1,49 @@ +# factories/base_factory.py +""" +工厂基类 - 提供统一的组件创建接口 +""" + +from cfd_registry import ComponentRegistry +from typing import Any, Optional + +class BaseFactory: + """工厂基类,提供统一的组件创建方法""" + + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + """ + 统一创建组件的方法 + + Args: + category: 组件类别(如'boundary', 'flux'等) + name: 组件名称(如'periodic', 'rusanov'等) + *args, **kwargs: 传递给构造函数的参数 + + Returns: + 创建的组件实例 + + Raises: + ValueError: 如果组件不存在 + """ + name_lower = name.lower() + + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + # 获取可用组件列表 + available = ComponentRegistry.list_all().get(category, []) + + # 构建友好的错误信息 + if available: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"可用类型:{available}") + else: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"({category}类别下没有注册任何组件)") + + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + """获取指定类别的可用组件列表""" + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/python/flux.py b/example/1d-linear-convection/weno3/julia/02/python/flux.py new file mode 100644 index 000000000..5ac73aa8d --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/python/flux.py @@ -0,0 +1,74 @@ +# flux.py +""" +通量计算器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册,替代硬编码工厂 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象通量计算基类(统一接口) ---------------------- +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass + +# ---------------------- 2. 具体通量计算子类(使用装饰器注册) ---------------------- + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + +class FluxCalculatorFactory: + """通量计算器工厂""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD对象 + + Returns: + 通量计算器实例 + """ + from factories.base_factory import BaseFactory + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/python/gen_boundary_test_data.py b/example/1d-linear-convection/weno3/julia/02/python/gen_boundary_test_data.py new file mode 100644 index 000000000..c7fc2a9c5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/python/gen_boundary_test_data.py @@ -0,0 +1,35 @@ +# python/gen_boundary_test_data.py +import numpy as np +from config import CfdConfig +from mesh import Mesh +from domain import Domain + +# 固定测试配置 +config = CfdConfig() +config.with_boundary("dirichlet", left_value=0.5, right_value=1.5) +config.debug = True + +mesh = Mesh() +domain = Domain(config, mesh) + +# 构造 mock CFD 对象(仅含 config + domain) +class MockCfd: + def __init__(self, config, domain): + self.config = config + self.domain = domain + +# 测试用 u:0,1,2,...,N-1 +u_input = np.arange(domain.ntcells, dtype=np.float64) +np.save("u_input.npy", u_input) + +# 测试每种边界 +from boundary import PeriodicBoundary, DirichletBoundary, NeumannBoundary + +for bc_name, bc_class in [("periodic", PeriodicBoundary), ("dirichlet", DirichletBoundary), ("neumann", NeumannBoundary)]: + u = u_input.copy() + cfd_mock = MockCfd(config, domain) + bc = bc_class(cfd_mock) + bc.apply(u) + np.save(f"u_{bc_name}_py.npy", u) + +print("✅ 测试数据已生成:u_*.npy") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/python/gen_ic_test_data.py b/example/1d-linear-convection/weno3/julia/02/python/gen_ic_test_data.py new file mode 100644 index 000000000..69c2573b8 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/python/gen_ic_test_data.py @@ -0,0 +1,27 @@ +# python/gen_ic_test_data.py +import sys, os +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +import numpy as np +from config import CfdConfig +from mesh import Mesh +from domain import Domain +from solution import Solution + +# 固定 mesh +mesh = Mesh() +config = CfdConfig() + +# 测试三种 IC +for ic_type in ["step", "sin", "gaussian"]: + config.ic_type = ic_type + domain = Domain(config, mesh) + sol = Solution(config, domain) + + u_full = sol.u.copy() # 包含 ghost + u_interior = sol.u[domain.ist:domain.ied].copy() + + np.save(f"u_{ic_type}_full_py.npy", u_full) + np.save(f"u_{ic_type}_interior_py.npy", u_interior) + +print("✅ 初始条件测试数据已生成") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/python/initial_condition.py b/example/1d-linear-convection/weno3/julia/02/python/initial_condition.py new file mode 100644 index 000000000..047415b74 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/python/initial_condition.py @@ -0,0 +1,90 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, ic_type: str, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from factories.base_factory import BaseFactory + return BaseFactory.create_component('initial_condition', ic_type, config) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/python/mesh.py b/example/1d-linear-convection/weno3/julia/02/python/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/python/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/python/plotter.py b/example/1d-linear-convection/weno3/julia/02/python/plotter.py new file mode 100644 index 000000000..e1f99ae25 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/python/plotter.py @@ -0,0 +1,109 @@ +# plotter.py + +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/python/reconstructor/__init__.py b/example/1d-linear-convection/weno3/julia/02/python/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/python/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/python/reconstructor/base.py b/example/1d-linear-convection/weno3/julia/02/python/reconstructor/base.py new file mode 100644 index 000000000..bbd638503 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/python/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def reconstruct(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/python/reconstructor/eno.py b/example/1d-linear-convection/weno3/julia/02/python/reconstructor/eno.py new file mode 100644 index 000000000..c2fb385dd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/python/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def reconstruct(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/python/reconstructor/factory.py b/example/1d-linear-convection/weno3/julia/02/python/reconstructor/factory.py new file mode 100644 index 000000000..efa4a1446 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/python/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from cfd_registry import ComponentRegistry + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from factories.base_factory import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/python/reconstructor/weno3.py b/example/1d-linear-convection/weno3/julia/02/python/reconstructor/weno3.py new file mode 100644 index 000000000..bf68be509 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/python/reconstructor/weno3.py @@ -0,0 +1,59 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def reconstruct(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/julia/02/python/registry.py b/example/1d-linear-convection/weno3/julia/02/python/registry.py new file mode 100644 index 000000000..61be90500 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/python/registry.py @@ -0,0 +1,75 @@ +# registry.py (改进版) +""" +CFD组件注册系统 - 简洁高效版 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表""" + + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True # 控制是否打印注册信息 + + @classmethod + def set_verbose(cls, verbose: bool): + """设置是否打印注册信息""" + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + # 检查是否已注册 + if name in cls._registries[category]: + if cls._registries[category][name] == component_class: + # 相同类重复注册,静默跳过 + return + elif cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取组件类""" + if category not in cls._registries: + available = list(cls._registries.keys()) + raise ValueError(f"❌ 未知类别: {category} (可用类别: {available})") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + @classmethod + def get_count(cls) -> int: + """获取注册组件总数""" + return sum(len(components) for components in cls._registries.values()) + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/python/residual.py b/example/1d-linear-convection/weno3/julia/02/python/residual.py new file mode 100644 index 000000000..b4d4d7dcc --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/python/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux import FluxCalculatorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + self.reconstructor = self.cfd.reconstructor + + # 初始化通量计算器(工厂创建) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._reconstruct() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _reconstruct(self): + """私有方法:界面值重建""" + self.reconstructor.reconstruct(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/python/run_eno_weno.py b/example/1d-linear-convection/weno3/julia/02/python/run_eno_weno.py new file mode 100644 index 000000000..ff46f2267 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/python/run_eno_weno.py @@ -0,0 +1,54 @@ +# run_eno_weno.py + +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + #mesh = Mesh(ncells=100, L=2.0) + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行ENO3求解(使用你的链式调用) + print("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + config_eno3.with_reconstruction("eno", 3) # 显式指定3阶(也可省略,ENO默认3阶) + # 可选:覆盖默认值(如dt) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + cfd_eno3.run() # 求解并生成result字典 + + # 可选:快速验证ENO3结果 + # plotter.plot_quick(cfd_eno3.result, title="ENO3 Quick Check") + + # 3. 配置并运行WENO3求解(注意:WENO默认5阶,这里显式指定3阶) + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) # 显式指定3阶(默认是5阶) + # 可选:覆盖默认值 + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + cfd_weno3.run() + + # 4. 可选:保存结果(供离线绘图) + # cfd_eno3.save_result("eno3_result.npz") + # cfd_weno3.save_result("weno3_result.npz") + + # 5. 绘制ENO/WENO对比图 + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" # 可选:保存图片 + ) + +if __name__ == "__main__": + # 主程序入口 + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/python/solution.py b/example/1d-linear-convection/weno3/julia/02/python/solution.py new file mode 100644 index 000000000..92a46d3f4 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/python/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config.ic_type, config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02/python/solver.py b/example/1d-linear-convection/weno3/julia/02/python/solver.py new file mode 100644 index 000000000..fdb5e46a7 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/python/solver.py @@ -0,0 +1,85 @@ +import numpy as np +import matplotlib.pyplot as plt +import inflect +from abc import ABC, abstractmethod + + +from flux import InviscidFluxCalculator, RusanovFluxCalculator, EngquistOsherFluxCalculator, FluxCalculatorFactory + +from boundary import BoundaryCondition, PeriodicBoundary, DirichletBoundary, NeumannBoundary, BoundaryConditionFactory + +from time_integration import TimeIntegrator, RK1Integrator, RK2Integrator, RK3Integrator, TimeIntegratorFactory + +from mesh import Mesh + +from reconstructor import ReconstructorFactory + +from initial_condition import InitialCondition, StepFunctionIC, SineWaveIC, GaussianPulseIC, InitialConditionFactory + +from domain import Domain +from solution import Solution + +from config import CfdConfig +from residual import ResidualCalculator + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, config, mesh): + self.config = config + self.domain = Domain(config, mesh) + self.solution = Solution(config, self.domain) + self.reconstructor = ReconstructorFactory.create(config, self.domain) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + def exact_solution(self): + """通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界""" + x = self.domain.mesh.xcc + T = self.config.final_time + c = self.config.wave_speed + L = self.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = InitialConditionFactory.create(self.config.ic_type, self.config) + return ic.evaluate_at(x_shifted) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + self.integrator.step(dt) + t += dt + config.dt = dt_old + + # 整理标准化结果 + u_numerical = self.solution.u[self.domain.ist:self.domain.ied].copy() + self.result = { + "x": domain.mesh.xcc, + "numerical": u_numerical, + "analytical": self.exact_solution(), + "config": { + "scheme": self.config.recon_scheme, + "order": self.config.spatial_order, + "rk_order": self.config.rk_order, + "final_time": self.config.final_time + } + } + + return u_numerical diff --git a/example/1d-linear-convection/weno3/julia/02/python/time_integration.py b/example/1d-linear-convection/weno3/julia/02/python/time_integration.py new file mode 100644 index 000000000..25ac0b4ca --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02/python/time_integration.py @@ -0,0 +1,125 @@ +# time_integration.py + +""" +时间推进器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = cfd.residual_calculator + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(使用装饰器注册) ---------------------- + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class TimeIntegratorFactory: + """时间推进器工厂""" + + @staticmethod + def create(cfd) -> 'TimeIntegrator': + """ + 创建时间推进器实例 + + Args: + cfd: CFD对象 + + Returns: + 时间推进器实例 + """ + from factories.base_factory import BaseFactory + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/julia/boundary.jl b/example/1d-linear-convection/weno3/julia/02a/julia/boundary.jl new file mode 100644 index 000000000..5b0baaf43 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/julia/boundary.jl @@ -0,0 +1,90 @@ +# julia/boundary.jl +# 目标:与 Python boundary.py 行为完全一致(1:1 同构) +# 前提:ist/ied 在 Julia 中按 1-based 设置(ist = nghosts + 1) + +using NPZ # 仅用于测试,实际模块可不依赖 + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象基类(Julia 风格:用函数接口) ---------------------- +# Julia 无 abstract class,用文档+约定 +# 所有子类型必须实现 apply!(bc, u) + +# ---------------------- PeriodicBoundary ---------------------- +mutable struct PeriodicBoundary + cfd::Any +end + +function apply!(self::PeriodicBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist # 1-based, e.g., 3 + ied = self.cfd.domain.ied # 1-based, e.g., 43 + + # 左 ghost: u[ist - 1 - ig] = u[ied - 1 - ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ied - 1 - ig] + end + # 右 ghost: u[ied + ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ied + ig] = u[ist + ig] + end +end + +# ---------------------- DirichletBoundary ---------------------- +mutable struct DirichletBoundary + cfd::Any +end + +function apply!(self::DirichletBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + left_value = getfield_safe(self.cfd.config, :left_boundary_value, 1.0) + right_value = getfield_safe(self.cfd.config, :right_boundary_value, 2.0) + + # 左边界 + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = left_value + end + # 右边界 + for ig in 0:(nghosts-1) + u[ied + ig] = right_value + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Dirichlet边界: 左值=$left_value, 右值=$right_value") + end +end + +# ---------------------- NeumannBoundary ---------------------- +mutable struct NeumannBoundary + cfd::Any +end + +function apply!(self::NeumannBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + # 左边界零梯度:u[ist - 1 - ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ist + ig] + end + # 右边界零梯度:u[ied + ig] = u[ied - 1 - ig] ← 注意是 ied - 1 - ig + for ig in 0:(nghosts-1) + u[ied + ig] = u[ied - 1 - ig] + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Neumann边界: 零梯度") + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/julia/config.jl b/example/1d-linear-convection/weno3/julia/02a/julia/config.jl new file mode 100644 index 000000000..bf48de3a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/julia/config.jl @@ -0,0 +1,68 @@ +# julia/config.jl +""" +CfdConfig:与 Python config.py 完全同构 +""" +mutable struct CfdConfig + ic_type::String + recon_scheme::String + flux_type::String + rk_order::Int + wave_speed::Float64 + final_time::Float64 + dt::Float64 + boundary_type::String + left_boundary_value::Float64 + right_boundary_value::Float64 + spatial_order::Int + + function CfdConfig() + new( + "step", + "eno", + "rusanov", + 1, + 1.0, + 0.625, + 0.025, + "periodic", + 1.0, + 2.0, + 2 + ) + end +end + +""" +专用配置:重建方案(链式调用) +""" +function with_reconstruction(cfg::CfdConfig, scheme::String, order::Union{Int, Nothing}=nothing) + cfg.recon_scheme = lowercase(scheme) + + if order !== nothing + cfg.spatial_order = order + else + if startswith(cfg.recon_scheme, "weno") + cfg.spatial_order = 5 + elseif cfg.recon_scheme == "eno" + cfg.spatial_order = 3 + else + error("不支持的重建格式:$scheme(仅支持 eno/weno)") + end + end + + return cfg # 支持链式调用 +end + +""" +专用配置:边界条件(链式调用) +""" +function with_boundary(cfg::CfdConfig, bc_type::String; left_value=nothing, right_value=nothing) + cfg.boundary_type = bc_type + if left_value !== nothing + cfg.left_boundary_value = left_value + end + if right_value !== nothing + cfg.right_boundary_value = right_value + end + return cfg +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/julia/domain.jl b/example/1d-linear-convection/weno3/julia/02a/julia/domain.jl new file mode 100644 index 000000000..a7edc2260 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/julia/domain.jl @@ -0,0 +1,61 @@ +# julia/domain.jl +""" +Domain 结构体(与 domain.py 完全同构) +- 保存 config +- nghosts 计算逻辑 1:1 +- ist/ied 为 0-based(与 Python 一致) +""" + +include("mesh.jl") + +mutable struct Domain + config::Any # 保存 config(与 Python 一致) + mesh::Mesh + nghosts::Int + ist::Int # 0-based + ied::Int # 0-based + ntcells::Int +end + +function Domain(config::Any, mesh::Mesh) + nghosts = _calc_nghosts(config) + ist = nghosts + 1 # ← 1-based 起始索引 + ied = ist + mesh.ncells # ← 1-based 结束索引(不包含) + ntcells = mesh.ncells + 2 * nghosts + Domain(config, mesh, nghosts, ist, ied, ntcells) +end + +function _calc_nghosts(config::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + order = getfield_safe(config, :spatial_order, 2) + + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + if scheme == "eno" + nghosts = order + elseif startswith(scheme, "weno") + nghosts = order ÷ 2 + 1 + else + error("未知重建格式 $(scheme),无法计算ghost层!") + end + + if nghosts <= 0 + error("计算得到的ghost层数量无效:$(nghosts)(阶数$(order),格式$(scheme))") + end + + return nghosts +end + +function is_physical_cell(domain::Domain, idx::Int) + return domain.ist <= idx < domain.ied +end + +function get_physical_indices(domain::Domain) + return domain.ist:(domain.ied - 1) +end + +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/julia/flux.jl b/example/1d-linear-convection/weno3/julia/02a/julia/flux.jl new file mode 100644 index 000000000..1252a037a --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/julia/flux.jl @@ -0,0 +1,116 @@ +# julia/flux.jl +""" +通量计算器模块(与 flux.py 完全同构) +- 抽象基类 + 具体实现 +- 字段:cfd, config, mesh, wave_speed +""" + +include("mesh.jl") + +# ---------------------- 抽象基类 ---------------------- +""" +InviscidFluxCalculator 抽象类型 +Julia 无 ABC,用文档约定 +所有子类型必须实现 compute! +""" +abstract type InviscidFluxCalculator end + +# ---------------------- RusanovFluxCalculator ---------------------- +mutable struct RusanovFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function RusanovFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::RusanovFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = calc.wave_speed + c_R = calc.wave_speed + F_L = c_L * u_L + F_R = c_R * u_R + Smax = max(abs(c_L), abs(c_R)) + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + end +end + +# ---------------------- EngquistOsherFluxCalculator ---------------------- +mutable struct EngquistOsherFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function EngquistOsherFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::EngquistOsherFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + c = calc.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + end +end + +# ---------------------- 通量计算器注册表 + 工厂(方案2)---------------------- + +""" +全局通量注册表:flux 名称 → 构造函数 +""" +const FLUX_REGISTRY = Dict{String, Function}() + +""" +注册通量计算器 +""" +function register_flux(name::String, ctor::Function) + if haskey(FLUX_REGISTRY, name) + @warn "通量计算器 '$name' 已注册,将被覆盖" + end + FLUX_REGISTRY[name] = ctor +end + +""" +工厂函数:根据 cfd.config.flux_type 创建通量计算器 +""" +function create_flux_calculator(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + config = cfd.config + if !hasproperty(config, :flux_type) + error("cfd.config 缺少 flux_type 字段") + end + + flux_type = config.flux_type + if !(flux_type isa AbstractString) + error("flux_type 必须为字符串,当前值: $flux_type") + end + + if !haskey(FLUX_REGISTRY, flux_type) + available = sort(collect(keys(FLUX_REGISTRY))) + error("未注册的通量计算器: '$flux_type'。可用选项: $available") + end + + return FLUX_REGISTRY[flux_type](cfd) +end + +# ---------------------- 注册内置通量计算器 ---------------------- +register_flux("rusanov", cfd -> RusanovFluxCalculator(cfd)) +register_flux("engquist-osher", cfd -> EngquistOsherFluxCalculator(cfd)) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/julia/initial_condition.jl b/example/1d-linear-convection/weno3/julia/02a/julia/initial_condition.jl new file mode 100644 index 000000000..5e029e8d2 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/julia/initial_condition.jl @@ -0,0 +1,86 @@ +# julia/initial_condition.jl +""" +初始条件模块(Julia 版) +目标:与 Python initial_condition.py 行为完全一致 +""" + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象接口(Julia 无继承,用函数约定) ---------------------- +# 所有初始条件必须实现: +# evaluate_at(ic, x::AbstractVector{Float64}) -> Vector{Float64} +# apply(ic, solution) + +# ---------------------- StepFunctionIC ---------------------- +struct StepFunctionIC + config::Any +end + +function evaluate_at(ic::StepFunctionIC, x::AbstractVector{Float64}) + u0 = ones(Float64, length(x)) + for i in eachindex(x) + if 0.5 <= x[i] <= 1.0 + u0[i] = 2.0 + end + end + return u0 +end + +function apply(ic::StepFunctionIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- SineWaveIC ---------------------- +struct SineWaveIC + config::Any +end + +function evaluate_at(ic::SineWaveIC, x::AbstractVector{Float64}) + L = getfield_safe(ic.config, :domain_length, 2.0) + return sin.(2π * x / L) +end + +function apply(ic::SineWaveIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- GaussianPulseIC ---------------------- +struct GaussianPulseIC + config::Any +end + +function evaluate_at(ic::GaussianPulseIC, x::AbstractVector{Float64}) + center = getfield_safe(ic.config, :pulse_center, 0.5) + width = getfield_safe(ic.config, :pulse_width, 0.1) + return exp.(-((x .- center) ./ width).^2) +end + +function apply(ic::GaussianPulseIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- 公共辅助函数 ---------------------- +""" +将初始场 values 应用到 solution.u 的物理区域(含 ghost 的数组) +""" +function _apply_to_interior(solution, values) + domain = solution.domain + # Python: for i in range(domain.ist, domain.ied) + # Julia: for i in domain.ist:domain.ied-1 (因为 ied 是结束索引,不包含) + for i in domain.ist:(domain.ied - 1) + j = i - domain.ist + 1 # values 是 1-based,i - ist 是 0-based → +1 + solution.u[i] = values[j] + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/julia/mesh.jl b/example/1d-linear-convection/weno3/julia/02a/julia/mesh.jl new file mode 100644 index 000000000..1b0dd4bf9 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/julia/mesh.jl @@ -0,0 +1,45 @@ +# julia/mesh.jl +""" +Mesh 结构体(与提供的 mesh.py 完全同构) +- 字段名、初始化顺序、循环逻辑完全一致 +- 不做任何泛化或优化 +""" + +mutable struct Mesh + xmin::Float64 + xmax::Float64 + ncells::Int + nnodes::Int + nx::Int + x::Vector{Float64} + xcc::Vector{Float64} + L::Float64 # 在 init_mesh 中赋值 + dx::Float64 # 在 init_mesh 中赋值 + + function Mesh() + # 与 Python __init__ 完全一致 + xmin = 0.0 + xmax = 2.0 + ncells = 40 + nnodes = ncells + 1 + nx = ncells + x = zeros(Float64, nnodes) + xcc = zeros(Float64, ncells) + + # 调用 init_mesh(模拟 Python) + L = xmax - xmin + dx = L / ncells + + # Generate node coordinates: for i in range(self.nnodes) + for i in 1:nnodes + x[i] = xmin + (i - 1) * dx # Python i → Julia i-1 + end + + # Generate cell center coordinates + for i in 1:ncells + xcc[i] = 0.5 * (x[i] + x[i+1]) + end + + new(xmin, xmax, ncells, nnodes, nx, x, xcc, L, dx) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/julia/plotter.jl b/example/1d-linear-convection/weno3/julia/02a/julia/plotter.jl new file mode 100644 index 000000000..a77d8d688 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/julia/plotter.jl @@ -0,0 +1,147 @@ +# julia/plotter.jl +""" +CFDPlotter 的 Julia 实现(通过 PythonCall.jl 调用 Matplotlib) +确保与 Python plotter.py 行为完全一致 +""" + +using PythonCall + +# 初始化 Python 环境(加载 matplotlib, inflect) +const plt = pyimport("matplotlib.pyplot") +const inflect = pyimport("inflect") + +mutable struct CFDPlotter + default_styles::Dict{String, Any} + p::Py +end + +function CFDPlotter() + default_styles = Dict{String, Any}( + "numerical" => Dict( + :color => "blue", + :linestyle => "-", + :marker => "o", + :markerfacecolor => "none" + ), + "analytical" => Dict( + :color => "red", + :linestyle => "--", + :marker => "", + :linewidth => 1.5 + ), + "comparison" => [ + Dict(:color => "black", :linestyle => "-", :marker => "o", :markerfacecolor => "none"), + Dict(:color => "blue", :linestyle => "--", :marker => "s", :markerfacecolor => "none"), + Dict(:color => "green", :linestyle => ":", :marker => "^", :markerfacecolor => "none") + ] + ) + p = inflect.engine() + CFDPlotter(default_styles, p) +end + +""" +轻量即时绘图(快速验证结果) +""" +function plot_quick(plotter::CFDPlotter, cfd_result::Dict; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + rk_order = cfd_result["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = cfd_result["config"]["final_time"] + order = cfd_result["config"]["order"] + scheme = uppercase(cfd_result["config"]["scheme"]) + actual_title = "1D Convection (t=$(final_time))\n$(order)th-order $(scheme) + $(rk_str)-order RK" + end + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"]; + label="Numerical ($(uppercase(cfd_result["config"]["scheme"])))", + plotter.default_styles["numerical"]..., + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + # 通用样式 + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +""" +多格式/多精度对比绘图 +""" +function plot_comparison(plotter::CFDPlotter, result_list::Vector{Dict{String, Any}}; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + schemes = [uppercase(r["config"]["scheme"]) * string(r["config"]["order"]) for r in result_list] + rk_order = result_list[1]["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = result_list[1]["config"]["final_time"] + actual_title = "1D Convection Comparison (t=$(final_time))\n$(join(schemes, ", ")) + $(rk_str)-order RK" + end + + # 绘制多个数值解 + for (i, res) in enumerate(result_list) + style = plotter.default_styles["comparison"][mod1(i, length(plotter.default_styles["comparison"]))] + label = "Numerical ($(uppercase(res["config"]["scheme"]))$(res["config"]["order"]))" + plt.plot( + res["x"], res["numerical"]; + label=label, + style..., + markersize=5, linewidth=0.5 + ) + end + + # 绘制解析解 + plt.plot( + result_list[1]["x"], result_list[1]["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +function _set_common_style(plotter::CFDPlotter, title::String) + plt.title(title, fontsize=12) + plt.xlabel("x", fontsize=10) + plt.ylabel("u", fontsize=10) + plt.legend(fontsize=9) + plt.grid(true, color="gray", linestyle="--", linewidth=0.5, alpha=0.7) + plt.tight_layout() +end + +""" +快捷函数:ENO/WENO对比绘图 +""" +function plot_eno_weno_comparison(eno_result::Dict, weno_result::Dict; save_path=nothing) + plotter = CFDPlotter() + plot_comparison(plotter, [eno_result, weno_result]; save_path=save_path) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/julia/reconstructor/eno.jl b/example/1d-linear-convection/weno3/julia/02a/julia/reconstructor/eno.jl new file mode 100644 index 000000000..e78a636f3 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/julia/reconstructor/eno.jl @@ -0,0 +1,107 @@ +# julia/reconstructor/eno.jl +""" +ENO 重构器(与 reconstructor/eno.py 完全同构) +""" + +# ---------------------- ENO 系数初始化 ---------------------- +function _init_eno_coef!(spatial_order::Int, coef::Matrix{Float64}) + if spatial_order == 1 + coef[1, 1] = 1.0 + coef[2, 1] = 1.0 + elseif spatial_order == 2 + coef[1, 1:2] = [3.0/2.0, -1.0/2.0] + coef[2, 1:2] = [1.0/2.0, 1.0/2.0] + coef[3, 1:2] = [-1.0/2.0, 3.0/2.0] + elseif spatial_order == 3 + coef[1, 1:3] = [11.0/6.0, -7.0/6.0, 1.0/3.0] + coef[2, 1:3] = [1.0/3.0, 5.0/6.0, -1.0/6.0] + coef[3, 1:3] = [-1.0/6.0, 5.0/6.0, 1.0/3.0] + coef[4, 1:3] = [1.0/3.0, -7.0/6.0, 11.0/6.0] + elseif spatial_order == 4 + coef[1, 1:4] = [25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0] + coef[2, 1:4] = [1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0] + coef[3, 1:4] = [-1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0] + coef[4, 1:4] = [1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0] + coef[5, 1:4] = [-1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0] + elseif spatial_order == 5 + coef[1, 1:5] = [137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0] + coef[2, 1:5] = [1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0] + coef[3, 1:5] = [-1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0] + coef[4, 1:5] = [1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0] + coef[5, 1:5] = [-1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0] + coef[6, 1:5] = [1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0] + elseif spatial_order == 6 + coef[1, 1:6] = [49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0] + coef[2, 1:6] = [1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0] + coef[3, 1:6] = [-1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0] + coef[4, 1:6] = [1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0] + coef[5, 1:6] = [-1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0] + coef[6, 1:6] = [1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0] + coef[7, 1:6] = [-1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0] + elseif spatial_order == 7 + coef[1, 1:7] = [363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0] + coef[2, 1:7] = [1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0] + coef[3, 1:7] = [-1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0] + coef[4, 1:7] = [1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0] + coef[5, 1:7] = [-1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0] + coef[6, 1:7] = [1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0] + coef[7, 1:7] = [-1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0] + coef[8, 1:7] = [1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0] + else + error("ENO 系数未实现 order=$spatial_order") + end +end + +# ---------------------- ENO 重构器 ---------------------- +mutable struct EnoReconstructor + spatial_order::Int + ntcells::Int + lmc::Vector{Int} + coef::Matrix{Float64} + dd::Matrix{Float64} + + function EnoReconstructor(spatial_order::Int, ntcells::Int) + lmc = zeros(Int, ntcells) + coef = zeros(Float64, spatial_order + 1, spatial_order) + dd = zeros(Float64, spatial_order, ntcells) + _init_eno_coef!(spatial_order, coef) + new(spatial_order, ntcells, lmc, coef, dd) + end +end + +function reconstruct(rec::EnoReconstructor, q::Vector{Float64}, cfd::Any) + # 1. 差商计算 (dd[1,:] = q) + @views rec.dd[1, :] .= q + for m in 2:rec.spatial_order + for j in 1:(rec.ntcells - m + 1) + rec.dd[m, j] = rec.dd[m-1, j+1] - rec.dd[m-1, j] + end + end + + # 2. 选择 smoothest stencil + domain = cfd.domain + for i in (domain.ist - 1):(domain.ied) # Python: range(ist-1, ied+1) → ied+1-1 = ied + rec.lmc[i] = i + for m in 2:rec.spatial_order + if abs(rec.dd[m, rec.lmc[i] - 1]) < abs(rec.dd[m, rec.lmc[i]]) + rec.lmc[i] -= 1 + end + end + end + + # 3. 重构界面值 + solution = cfd.solution + for i in domain.ist:(domain.ied) # Python: range(ist, ied+1) → ied+1-1 = ied + j = i - domain.ist + 1 # Julia 1-based + k1 = rec.lmc[i - 1] + k2 = rec.lmc[i] + r1 = (i - 1) - k1 + 1 + r2 = i - k2 + 1 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in 1:rec.spatial_order + solution.q_face_left[j] += q[k1 + m - 1] * rec.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m - 1] * rec.coef[r2, m] + end + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/julia/reconstructor/weno3.jl b/example/1d-linear-convection/weno3/julia/02a/julia/reconstructor/weno3.jl new file mode 100644 index 000000000..2b6fe1abb --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/julia/reconstructor/weno3.jl @@ -0,0 +1,65 @@ +# julia/reconstructor/weno3.jl +""" +WENO3 重构器(与 reconstructor/weno3.py 完全同构) +""" + +mutable struct Weno3Reconstructor + # 无字段,与 Python 一致 +end + +function reconstruct(rec::Weno3Reconstructor, q::Vector{Float64}, cfd::Any) + domain = cfd.domain + solution = cfd.solution + _reconstruct_left_interfaces(domain, q, solution.q_face_left) + _reconstruct_right_interfaces(domain, q, solution.q_face_right) +end + +function _reconstruct_left_interfaces(domain, u, qL) + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in (domain.ist - 1):(domain.ied - 1) + j = i - (domain.ist - 1) + 1 # ← Julia 1-based: j = i - (ist-1) 对应 Python j = i - (ist-1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = _reconstruct_from_left_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_right_interfaces(domain, u, qR) + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in domain.ist:domain.ied + j = i - domain.ist + 1 # ← Julia 1-based: j = i - ist 对应 Python j = i - ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = _reconstruct_from_right_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_from_left_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -0.5*v1 + 1.5*v2 # r=1 stencil + q1 = 0.5*v2 + 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end + +function _reconstruct_from_right_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 0.5*v1 + 0.5*v2 # r=1 stencil + q1 = 1.5*v2 - 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/julia/residual.jl b/example/1d-linear-convection/weno3/julia/02a/julia/residual.jl new file mode 100644 index 000000000..e8fd65843 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/julia/residual.jl @@ -0,0 +1,65 @@ +# julia/residual.jl +""" +残差计算器(与 residual.py 完全同构) +- 封装重建→通量→散度完整流程 +- 依赖 cfd 的多个字段 +""" + +include("mesh.jl") + +mutable struct ResidualCalculator + cfd::Any + config::Any + domain::Any + solution::Any + mesh::Mesh + reconstructor::Any + flux_calculator::Any # 通量计算器(外部传入,替代工厂) + + function ResidualCalculator(cfd::Any, flux_calculator::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + mesh = domain.mesh + reconstructor = cfd.reconstructor + + new(cfd, config, domain, solution, mesh, reconstructor, flux_calculator) + end +end + +""" +计算完整残差(对外唯一接口) +""" +function compute!(calc::ResidualCalculator) + _reconstruct(calc) + _compute_inviscid_flux(calc) + _compute_flux_divergence(calc) +end + +""" +私有方法:界面值重建 +""" +function _reconstruct(calc::ResidualCalculator) + reconstruct(calc.reconstructor, calc.solution.u, calc.cfd) +end + +""" +私有方法:计算无粘通量 +""" +function _compute_inviscid_flux(calc::ResidualCalculator) + compute!(calc.flux_calculator, + calc.solution.q_face_left, + calc.solution.q_face_right, + calc.solution.flux) +end + +""" +私有方法:计算通量散度(残差 = -dF/dx) +""" +function _compute_flux_divergence(calc::ResidualCalculator) + solution = calc.solution + mesh = calc.mesh + for i in 1:mesh.ncells + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / mesh.dx + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/julia/run_eno_weno.jl b/example/1d-linear-convection/weno3/julia/02a/julia/run_eno_weno.jl new file mode 100644 index 000000000..58b6cfa5f --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/julia/run_eno_weno.jl @@ -0,0 +1,50 @@ +# julia/run_eno_weno.jl +""" +1:1 复刻 run_eno_weno.py 的 Julia 版本 +""" + +include("config.jl") +include("mesh.jl") +include("solver.jl") +include("plotter.jl") + +function performEnoWenoAnalysis() + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO3 求解 + println("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + with_reconstruction(config_eno3, "eno", 3) # 显式指定 3 阶 + config_eno3.dt = 0.0025 # 覆盖默认值 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + run!(cfd_eno3) # 求解并生成 result 字典 + + # 3. 配置并运行 WENO3 求解 + println("Running WENO3 solver...") + config_weno3 = CfdConfig() + with_reconstruction(config_weno3, "weno", 3) # 显式指定 3 阶(WENO 默认 5 阶) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + run!(cfd_weno3) + + # 5. 绘制 ENO/WENO 对比图 + println("Plotting comparison results...") + plot_eno_weno_comparison( + cfd_eno3.result, + cfd_weno3.result; + save_path="eno_weno_comparison.png" + ) + + return cfd_eno3, cfd_weno3 +end + +# 主程序入口 +if abspath(PROGRAM_FILE) == @__FILE__ + performEnoWenoAnalysis() + println("Analysis completed!") +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/julia/solution.jl b/example/1d-linear-convection/weno3/julia/02a/julia/solution.jl new file mode 100644 index 000000000..90ca03931 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/julia/solution.jl @@ -0,0 +1,75 @@ +# julia/solution.jl +""" +Solution 结构体(与真实 solution.py 完全同构) +字段顺序、方法、逻辑 1:1 对齐 +""" + +include("mesh.jl") +include("domain.jl") +include("initial_condition.jl") + +mutable struct Solution + domain::Domain + q_face_left::Vector{Float64} + q_face_right::Vector{Float64} + flux::Vector{Float64} + res::Vector{Float64} + u::Vector{Float64} + un::Vector{Float64} + + function Solution(config::Any, domain::Domain) + mesh = domain.mesh + + q_face_left = zeros(Float64, mesh.nnodes) + q_face_right = zeros(Float64, mesh.nnodes) + flux = zeros(Float64, mesh.nnodes) + res = zeros(Float64, mesh.ncells) + u = zeros(Float64, domain.ntcells) + un = zeros(Float64, domain.ntcells) + + sol = new(domain, q_face_left, q_face_right, flux, res, u, un) + initialize_from_config(sol, config) + return sol + end +end + +""" +重置解数组为初始状态 +""" +function reset_solution(sol::Solution) + fill!(sol.u, 0.0) + fill!(sol.un, 0.0) +end + +""" +根据配置初始化场 +注:暂用硬编码替代 InitialConditionFactory +""" +function initialize_from_config(sol::Solution, config::Any) + ic_type = getfield_safe(config, :ic_type, "step") + + # 硬编码创建 IC(替代 InitialConditionFactory) + ic = if ic_type == "step" + StepFunctionIC(config) + elseif ic_type == "sin" + SineWaveIC(config) + elseif ic_type == "gaussian" + GaussianPulseIC(config) + else + error("未知初始条件类型: $ic_type") + end + + apply(ic, sol) # 调用 IC.apply +end + +""" +更新旧场:un = u +""" +function update_old_field(sol::Solution) + sol.un .= sol.u # 等价于 Python 的 un[:] = u[:] +end + +# ---------------------- 辅助函数 ---------------------- +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/julia/solver.jl b/example/1d-linear-convection/weno3/julia/02a/julia/solver.jl new file mode 100644 index 000000000..d09d5c5fe --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/julia/solver.jl @@ -0,0 +1,169 @@ +# julia/solver.jl +""" +CFD 求解器主类(与 solver.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("initial_condition.jl") +include("boundary.jl") +include("flux.jl") +include("residual.jl") +include("time_integration.jl") +include("reconstructor/eno.jl") +include("reconstructor/weno3.jl") + +# ---------------------- Cfd 求解器 ---------------------- +mutable struct Cfd + config::Any + domain::Domain + solution::Solution + reconstructor::Any + residual_calculator::ResidualCalculator + integrator::Any + boundary_condition::Any + result::Dict{String, Any} + + function Cfd(config::Any, mesh::Mesh) + domain = Domain(config, mesh) + solution = Solution(config, domain) + + # 在 Cfd 构造函数中 + # =============== 重建器创建 =============== + recon_scheme = config.recon_scheme + spatial_order = config.spatial_order + + # ✅ 模拟 Python ReconstructorFactory 的逻辑 + if recon_scheme == "weno" + recon_scheme = "weno$(spatial_order)" + end + + # 现在根据 recon_scheme 创建 + reconstructor = if recon_scheme == "eno" + EnoReconstructor(spatial_order, domain.ntcells) + elseif recon_scheme == "weno3" + Weno3Reconstructor() + else + error("不支持的重建格式: $recon_scheme") + end + + # 构造用于通量初始化的上下文对象(NamedTuple) + flux_context = ( + config = config, + domain = domain + ) + + # 使用工厂创建通量计算器 + flux_calculator = create_flux_calculator(flux_context) + + # 残差计算器 + residual_calculator = ResidualCalculator( + (config=config, domain=domain, solution=solution, reconstructor=reconstructor), + flux_calculator + ) + + # 边界条件 + boundary_condition = if config.boundary_type == "periodic" + PeriodicBoundary((config=config, domain=domain)) + elseif config.boundary_type == "dirichlet" + DirichletBoundary((config=config, domain=domain)) + elseif config.boundary_type == "neumann" + NeumannBoundary((config=config, domain=domain)) + else + error("不支持的边界类型: $(config.boundary_type)") + end + + + # 构造用于积分器初始化的上下文对象(NamedTuple,模拟 cfd 接口) + integrator_context = ( + config = config, + domain = domain, + solution = solution, + residual_calculator = residual_calculator, + boundary_condition = boundary_condition + ) + + # 使用注册表工厂创建时间推进器 + integrator = create_integrator(integrator_context) + + #@show typeof(integrator) + + # 注入 cfd 到 residual_calculator 和 integrator + residual_calculator.cfd = (config=config, domain=domain, solution=solution, reconstructor=reconstructor, residual_calculator=residual_calculator, integrator=integrator, boundary_condition=boundary_condition) + integrator.base.cfd = residual_calculator.cfd + + result = Dict{String, Any}() + new(config, domain, solution, reconstructor, residual_calculator, integrator, boundary_condition, result) + end +end + +""" +通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界 +""" +function exact_solution(cfd::Cfd) + x = cfd.domain.mesh.xcc + T = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = @. (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = if cfd.config.ic_type == "step" + StepFunctionIC(cfd.config) + elseif cfd.config.ic_type == "sin" + SineWaveIC(cfd.config) + elseif cfd.config.ic_type == "gaussian" + GaussianPulseIC(cfd.config) + else + error("未知初始条件: $(cfd.config.ic_type)") + end + + return evaluate_at(ic, x_shifted) +end + +""" +主求解循环 +""" +function run!(cfd::Cfd) + # 应用初始边界条件并同步 old field + apply!(cfd.boundary_condition, cfd.solution.u) + update_old_field(cfd.solution) + + t = 0.0 + dt_old = cfd.config.dt + dt = dt_old + + while t < cfd.config.final_time + if t + dt > cfd.config.final_time + dt = cfd.config.final_time - t + end + #@show t, dt, maximum(cfd.solution.u), minimum(cfd.solution.u) + # 执行时间步 + step(cfd.integrator, dt) + t += dt + end + + # 恢复 dt + cfd.config.dt = dt_old + + # 整理结果 + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied-1] # Python: [ist:ied] + analytical = exact_solution(cfd) + + cfd.result = Dict( + "x" => cfd.domain.mesh.xcc, + "numerical" => u_numerical, + "analytical" => analytical, + "config" => Dict( + "scheme" => cfd.config.recon_scheme, + "order" => cfd.config.spatial_order, + "rk_order" => cfd.config.rk_order, + "final_time" => cfd.config.final_time + ) + ) + + return u_numerical +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/julia/time_integration.jl b/example/1d-linear-convection/weno3/julia/02a/julia/time_integration.jl new file mode 100644 index 000000000..43b540af7 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/julia/time_integration.jl @@ -0,0 +1,168 @@ +# julia/time_integration.jl +""" +时间推进器模块(与 time_integration.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("residual.jl") +include("boundary.jl") + +# ---------------------- 抽象时间推进器基类 ---------------------- +abstract type TimeIntegrator end + +mutable struct TimeIntegratorBase <: TimeIntegrator + cfd::Any + config::Any + domain::Domain + solution::Solution + residual_calculator::Any # ResidualCalculator +end + +function TimeIntegratorBase(cfd::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + residual_calculator = cfd.residual_calculator + TimeIntegratorBase(cfd, config, domain, solution, residual_calculator) +end + +function compute_residual(integrator::TimeIntegratorBase) + compute!(integrator.residual_calculator) +end + +function apply_boundary(integrator::TimeIntegratorBase) + apply!(integrator.cfd.boundary_condition, integrator.solution.u) +end + +function map_idx(integrator::TimeIntegratorBase, i::Int) + return i - integrator.domain.ist + 1 # ← +1 转为 1-based +end + +# ---------------------- RK1Integrator ---------------------- +mutable struct RK1Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK1Integrator(cfd::Any) + RK1Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK1Integrator, dt::Float64) + compute_residual(integrator.base) + for i in integrator.base.domain.ist:(integrator.base.domain.ied - 1) + j = map_idx(integrator.base, i) + integrator.base.solution.u[i] += dt * integrator.base.solution.res[j] + end + apply_boundary(integrator.base) + update_old_field(integrator.base.solution) +end + +# ---------------------- RK2Integrator ---------------------- +mutable struct RK2Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK2Integrator(cfd::Any) + RK2Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK2Integrator, dt::Float64) + base = integrator.base + # 阶段1:预测步 + compute_residual(base) + u_pred = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u_pred[i] += dt * base.solution.res[j] + end + base.solution.u .= u_pred + apply_boundary(base) + # 阶段2:校正步 + compute_residual(base) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = 0.5 * base.solution.un[i] + 0.5 * base.solution.u[i] + 0.5 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end + +# ---------------------- RK3Integrator ---------------------- +mutable struct RK3Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK3Integrator(cfd::Any) + RK3Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK3Integrator, dt::Float64) + base = integrator.base + # 阶段1 + compute_residual(base) + u1 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u1[i] += dt * base.solution.res[j] + end + base.solution.u .= u1 + apply_boundary(base) + # 阶段2 + compute_residual(base) + u2 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u2[i] = 0.75 * base.solution.un[i] + 0.25 * base.solution.u[i] + 0.25 * dt * base.solution.res[j] + end + base.solution.u .= u2 + apply_boundary(base) + # 阶段3 + compute_residual(base) + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = c1 * base.solution.un[i] + c2 * base.solution.u[i] + c3 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end + +# ---------------------- 注册表 + 工厂(方案2)---------------------- + +const INTEGRATOR_REGISTRY = Dict{String, Function}() + +function register_integrator(name::String, ctor::Function) + if haskey(INTEGRATOR_REGISTRY, name) + @warn "积分器 '$name' 已注册,将被覆盖" + end + INTEGRATOR_REGISTRY[name] = ctor +end + +function create_integrator(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + config = cfd.config + if !hasproperty(config, :rk_order) + error("cfd.config 缺少 rk_order 字段") + end + rk_order = config.rk_order + if !(rk_order isa Integer) || rk_order < 1 + error("rk_order 必须为正整数,当前值: $rk_order (类型: $(typeof(rk_order)))") + end + + name = "rk$rk_order" + if !haskey(INTEGRATOR_REGISTRY, name) + available = sort(collect(keys(INTEGRATOR_REGISTRY))) + error("未注册的时间积分器: '$name'。可用选项: $available") + end + + return INTEGRATOR_REGISTRY[name](cfd) +end + +# 注册内置积分器 +register_integrator("rk1", cfd -> RK1Integrator(cfd)) +register_integrator("rk2", cfd -> RK2Integrator(cfd)) +register_integrator("rk3", cfd -> RK3Integrator(cfd)) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/python/boundary.py b/example/1d-linear-convection/weno3/julia/02a/python/boundary.py new file mode 100644 index 000000000..6054f92de --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/python/boundary.py @@ -0,0 +1,103 @@ +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from factories.base_factory import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/python/cfd_registry.py b/example/1d-linear-convection/weno3/julia/02a/python/cfd_registry.py new file mode 100644 index 000000000..c12eb106c --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/python/cfd_registry.py @@ -0,0 +1,62 @@ +# cfd_registry.py +""" +CFD注册系统统一导入模块 +所有其他模块从这里导入注册系统 +""" + +import sys +import os + +# 添加当前目录到路径,确保能找到registry.py +current_dir = os.path.dirname(os.path.abspath(__file__)) +if current_dir not in sys.path: + sys.path.insert(0, current_dir) + +try: + # 尝试导入注册系统 + from registry import ComponentRegistry, register_component + REGISTRY_AVAILABLE = True +except ImportError: + # 如果找不到,提供简单的空实现 + print("⚠️ 警告: 未找到 registry.py,使用最小化回退实现") + + class ComponentRegistry: + """最小化的注册系统回退实现""" + _registries = {} + + @classmethod + def register(cls, category, name, component_class): + if category not in cls._registries: + cls._registries[category] = {} + cls._registries[category][name] = component_class + + @classmethod + def get(cls, category, name): + if category not in cls._registries: + raise ValueError(f"未知类别: {category}") + if name not in cls._registries[category]: + raise ValueError(f"未找到: {category}.{name}") + return cls._registries[category][name] + + @classmethod + def create(cls, category, name, *args, **kwargs): + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) + for cat, comps in cls._registries.items()} + + def register_component(category, name=None): + """简化的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + REGISTRY_AVAILABLE = False + +# 导出统一的接口 +__all__ = ['ComponentRegistry', 'register_component', 'REGISTRY_AVAILABLE'] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/python/config.py b/example/1d-linear-convection/weno3/julia/02a/python/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/python/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/python/domain.py b/example/1d-linear-convection/weno3/julia/02a/python/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/python/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/python/factories/base_factory.py b/example/1d-linear-convection/weno3/julia/02a/python/factories/base_factory.py new file mode 100644 index 000000000..7b8b6105e --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/python/factories/base_factory.py @@ -0,0 +1,49 @@ +# factories/base_factory.py +""" +工厂基类 - 提供统一的组件创建接口 +""" + +from cfd_registry import ComponentRegistry +from typing import Any, Optional + +class BaseFactory: + """工厂基类,提供统一的组件创建方法""" + + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + """ + 统一创建组件的方法 + + Args: + category: 组件类别(如'boundary', 'flux'等) + name: 组件名称(如'periodic', 'rusanov'等) + *args, **kwargs: 传递给构造函数的参数 + + Returns: + 创建的组件实例 + + Raises: + ValueError: 如果组件不存在 + """ + name_lower = name.lower() + + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + # 获取可用组件列表 + available = ComponentRegistry.list_all().get(category, []) + + # 构建友好的错误信息 + if available: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"可用类型:{available}") + else: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"({category}类别下没有注册任何组件)") + + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + """获取指定类别的可用组件列表""" + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/python/flux.py b/example/1d-linear-convection/weno3/julia/02a/python/flux.py new file mode 100644 index 000000000..5ac73aa8d --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/python/flux.py @@ -0,0 +1,74 @@ +# flux.py +""" +通量计算器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册,替代硬编码工厂 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象通量计算基类(统一接口) ---------------------- +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass + +# ---------------------- 2. 具体通量计算子类(使用装饰器注册) ---------------------- + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + +class FluxCalculatorFactory: + """通量计算器工厂""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD对象 + + Returns: + 通量计算器实例 + """ + from factories.base_factory import BaseFactory + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/python/initial_condition.py b/example/1d-linear-convection/weno3/julia/02a/python/initial_condition.py new file mode 100644 index 000000000..047415b74 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/python/initial_condition.py @@ -0,0 +1,90 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, ic_type: str, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from factories.base_factory import BaseFactory + return BaseFactory.create_component('initial_condition', ic_type, config) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/python/mesh.py b/example/1d-linear-convection/weno3/julia/02a/python/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/python/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/python/plotter.py b/example/1d-linear-convection/weno3/julia/02a/python/plotter.py new file mode 100644 index 000000000..e1f99ae25 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/python/plotter.py @@ -0,0 +1,109 @@ +# plotter.py + +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/python/reconstructor/__init__.py b/example/1d-linear-convection/weno3/julia/02a/python/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/python/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/python/reconstructor/base.py b/example/1d-linear-convection/weno3/julia/02a/python/reconstructor/base.py new file mode 100644 index 000000000..bbd638503 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/python/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def reconstruct(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/python/reconstructor/eno.py b/example/1d-linear-convection/weno3/julia/02a/python/reconstructor/eno.py new file mode 100644 index 000000000..c2fb385dd --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/python/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def reconstruct(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/python/reconstructor/factory.py b/example/1d-linear-convection/weno3/julia/02a/python/reconstructor/factory.py new file mode 100644 index 000000000..efa4a1446 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/python/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from cfd_registry import ComponentRegistry + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from factories.base_factory import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/python/reconstructor/weno3.py b/example/1d-linear-convection/weno3/julia/02a/python/reconstructor/weno3.py new file mode 100644 index 000000000..bf68be509 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/python/reconstructor/weno3.py @@ -0,0 +1,59 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def reconstruct(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/julia/02a/python/registry.py b/example/1d-linear-convection/weno3/julia/02a/python/registry.py new file mode 100644 index 000000000..61be90500 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/python/registry.py @@ -0,0 +1,75 @@ +# registry.py (改进版) +""" +CFD组件注册系统 - 简洁高效版 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表""" + + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True # 控制是否打印注册信息 + + @classmethod + def set_verbose(cls, verbose: bool): + """设置是否打印注册信息""" + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + # 检查是否已注册 + if name in cls._registries[category]: + if cls._registries[category][name] == component_class: + # 相同类重复注册,静默跳过 + return + elif cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取组件类""" + if category not in cls._registries: + available = list(cls._registries.keys()) + raise ValueError(f"❌ 未知类别: {category} (可用类别: {available})") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + @classmethod + def get_count(cls) -> int: + """获取注册组件总数""" + return sum(len(components) for components in cls._registries.values()) + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/python/residual.py b/example/1d-linear-convection/weno3/julia/02a/python/residual.py new file mode 100644 index 000000000..b4d4d7dcc --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/python/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux import FluxCalculatorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + self.reconstructor = self.cfd.reconstructor + + # 初始化通量计算器(工厂创建) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._reconstruct() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _reconstruct(self): + """私有方法:界面值重建""" + self.reconstructor.reconstruct(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/python/run_eno_weno.py b/example/1d-linear-convection/weno3/julia/02a/python/run_eno_weno.py new file mode 100644 index 000000000..ff46f2267 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/python/run_eno_weno.py @@ -0,0 +1,54 @@ +# run_eno_weno.py + +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + #mesh = Mesh(ncells=100, L=2.0) + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行ENO3求解(使用你的链式调用) + print("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + config_eno3.with_reconstruction("eno", 3) # 显式指定3阶(也可省略,ENO默认3阶) + # 可选:覆盖默认值(如dt) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + cfd_eno3.run() # 求解并生成result字典 + + # 可选:快速验证ENO3结果 + # plotter.plot_quick(cfd_eno3.result, title="ENO3 Quick Check") + + # 3. 配置并运行WENO3求解(注意:WENO默认5阶,这里显式指定3阶) + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) # 显式指定3阶(默认是5阶) + # 可选:覆盖默认值 + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + cfd_weno3.run() + + # 4. 可选:保存结果(供离线绘图) + # cfd_eno3.save_result("eno3_result.npz") + # cfd_weno3.save_result("weno3_result.npz") + + # 5. 绘制ENO/WENO对比图 + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" # 可选:保存图片 + ) + +if __name__ == "__main__": + # 主程序入口 + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/python/solution.py b/example/1d-linear-convection/weno3/julia/02a/python/solution.py new file mode 100644 index 000000000..92a46d3f4 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/python/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config.ic_type, config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02a/python/solver.py b/example/1d-linear-convection/weno3/julia/02a/python/solver.py new file mode 100644 index 000000000..fdb5e46a7 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/python/solver.py @@ -0,0 +1,85 @@ +import numpy as np +import matplotlib.pyplot as plt +import inflect +from abc import ABC, abstractmethod + + +from flux import InviscidFluxCalculator, RusanovFluxCalculator, EngquistOsherFluxCalculator, FluxCalculatorFactory + +from boundary import BoundaryCondition, PeriodicBoundary, DirichletBoundary, NeumannBoundary, BoundaryConditionFactory + +from time_integration import TimeIntegrator, RK1Integrator, RK2Integrator, RK3Integrator, TimeIntegratorFactory + +from mesh import Mesh + +from reconstructor import ReconstructorFactory + +from initial_condition import InitialCondition, StepFunctionIC, SineWaveIC, GaussianPulseIC, InitialConditionFactory + +from domain import Domain +from solution import Solution + +from config import CfdConfig +from residual import ResidualCalculator + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, config, mesh): + self.config = config + self.domain = Domain(config, mesh) + self.solution = Solution(config, self.domain) + self.reconstructor = ReconstructorFactory.create(config, self.domain) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + def exact_solution(self): + """通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界""" + x = self.domain.mesh.xcc + T = self.config.final_time + c = self.config.wave_speed + L = self.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = InitialConditionFactory.create(self.config.ic_type, self.config) + return ic.evaluate_at(x_shifted) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + self.integrator.step(dt) + t += dt + config.dt = dt_old + + # 整理标准化结果 + u_numerical = self.solution.u[self.domain.ist:self.domain.ied].copy() + self.result = { + "x": domain.mesh.xcc, + "numerical": u_numerical, + "analytical": self.exact_solution(), + "config": { + "scheme": self.config.recon_scheme, + "order": self.config.spatial_order, + "rk_order": self.config.rk_order, + "final_time": self.config.final_time + } + } + + return u_numerical diff --git a/example/1d-linear-convection/weno3/julia/02a/python/time_integration.py b/example/1d-linear-convection/weno3/julia/02a/python/time_integration.py new file mode 100644 index 000000000..25ac0b4ca --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02a/python/time_integration.py @@ -0,0 +1,125 @@ +# time_integration.py + +""" +时间推进器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = cfd.residual_calculator + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(使用装饰器注册) ---------------------- + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class TimeIntegratorFactory: + """时间推进器工厂""" + + @staticmethod + def create(cfd) -> 'TimeIntegrator': + """ + 创建时间推进器实例 + + Args: + cfd: CFD对象 + + Returns: + 时间推进器实例 + """ + from factories.base_factory import BaseFactory + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02b/boundary.jl b/example/1d-linear-convection/weno3/julia/02b/boundary.jl new file mode 100644 index 000000000..5b0baaf43 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02b/boundary.jl @@ -0,0 +1,90 @@ +# julia/boundary.jl +# 目标:与 Python boundary.py 行为完全一致(1:1 同构) +# 前提:ist/ied 在 Julia 中按 1-based 设置(ist = nghosts + 1) + +using NPZ # 仅用于测试,实际模块可不依赖 + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象基类(Julia 风格:用函数接口) ---------------------- +# Julia 无 abstract class,用文档+约定 +# 所有子类型必须实现 apply!(bc, u) + +# ---------------------- PeriodicBoundary ---------------------- +mutable struct PeriodicBoundary + cfd::Any +end + +function apply!(self::PeriodicBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist # 1-based, e.g., 3 + ied = self.cfd.domain.ied # 1-based, e.g., 43 + + # 左 ghost: u[ist - 1 - ig] = u[ied - 1 - ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ied - 1 - ig] + end + # 右 ghost: u[ied + ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ied + ig] = u[ist + ig] + end +end + +# ---------------------- DirichletBoundary ---------------------- +mutable struct DirichletBoundary + cfd::Any +end + +function apply!(self::DirichletBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + left_value = getfield_safe(self.cfd.config, :left_boundary_value, 1.0) + right_value = getfield_safe(self.cfd.config, :right_boundary_value, 2.0) + + # 左边界 + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = left_value + end + # 右边界 + for ig in 0:(nghosts-1) + u[ied + ig] = right_value + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Dirichlet边界: 左值=$left_value, 右值=$right_value") + end +end + +# ---------------------- NeumannBoundary ---------------------- +mutable struct NeumannBoundary + cfd::Any +end + +function apply!(self::NeumannBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + # 左边界零梯度:u[ist - 1 - ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ist + ig] + end + # 右边界零梯度:u[ied + ig] = u[ied - 1 - ig] ← 注意是 ied - 1 - ig + for ig in 0:(nghosts-1) + u[ied + ig] = u[ied - 1 - ig] + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Neumann边界: 零梯度") + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02b/config.jl b/example/1d-linear-convection/weno3/julia/02b/config.jl new file mode 100644 index 000000000..bf48de3a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02b/config.jl @@ -0,0 +1,68 @@ +# julia/config.jl +""" +CfdConfig:与 Python config.py 完全同构 +""" +mutable struct CfdConfig + ic_type::String + recon_scheme::String + flux_type::String + rk_order::Int + wave_speed::Float64 + final_time::Float64 + dt::Float64 + boundary_type::String + left_boundary_value::Float64 + right_boundary_value::Float64 + spatial_order::Int + + function CfdConfig() + new( + "step", + "eno", + "rusanov", + 1, + 1.0, + 0.625, + 0.025, + "periodic", + 1.0, + 2.0, + 2 + ) + end +end + +""" +专用配置:重建方案(链式调用) +""" +function with_reconstruction(cfg::CfdConfig, scheme::String, order::Union{Int, Nothing}=nothing) + cfg.recon_scheme = lowercase(scheme) + + if order !== nothing + cfg.spatial_order = order + else + if startswith(cfg.recon_scheme, "weno") + cfg.spatial_order = 5 + elseif cfg.recon_scheme == "eno" + cfg.spatial_order = 3 + else + error("不支持的重建格式:$scheme(仅支持 eno/weno)") + end + end + + return cfg # 支持链式调用 +end + +""" +专用配置:边界条件(链式调用) +""" +function with_boundary(cfg::CfdConfig, bc_type::String; left_value=nothing, right_value=nothing) + cfg.boundary_type = bc_type + if left_value !== nothing + cfg.left_boundary_value = left_value + end + if right_value !== nothing + cfg.right_boundary_value = right_value + end + return cfg +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02b/domain.jl b/example/1d-linear-convection/weno3/julia/02b/domain.jl new file mode 100644 index 000000000..a7edc2260 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02b/domain.jl @@ -0,0 +1,61 @@ +# julia/domain.jl +""" +Domain 结构体(与 domain.py 完全同构) +- 保存 config +- nghosts 计算逻辑 1:1 +- ist/ied 为 0-based(与 Python 一致) +""" + +include("mesh.jl") + +mutable struct Domain + config::Any # 保存 config(与 Python 一致) + mesh::Mesh + nghosts::Int + ist::Int # 0-based + ied::Int # 0-based + ntcells::Int +end + +function Domain(config::Any, mesh::Mesh) + nghosts = _calc_nghosts(config) + ist = nghosts + 1 # ← 1-based 起始索引 + ied = ist + mesh.ncells # ← 1-based 结束索引(不包含) + ntcells = mesh.ncells + 2 * nghosts + Domain(config, mesh, nghosts, ist, ied, ntcells) +end + +function _calc_nghosts(config::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + order = getfield_safe(config, :spatial_order, 2) + + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + if scheme == "eno" + nghosts = order + elseif startswith(scheme, "weno") + nghosts = order ÷ 2 + 1 + else + error("未知重建格式 $(scheme),无法计算ghost层!") + end + + if nghosts <= 0 + error("计算得到的ghost层数量无效:$(nghosts)(阶数$(order),格式$(scheme))") + end + + return nghosts +end + +function is_physical_cell(domain::Domain, idx::Int) + return domain.ist <= idx < domain.ied +end + +function get_physical_indices(domain::Domain) + return domain.ist:(domain.ied - 1) +end + +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02b/flux.jl b/example/1d-linear-convection/weno3/julia/02b/flux.jl new file mode 100644 index 000000000..cff235824 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02b/flux.jl @@ -0,0 +1,112 @@ +# julia/flux.jl +""" +通量计算器模块(与 flux.py 完全同构) +- 抽象基类 + 具体实现 +- 字段:cfd, config, mesh, wave_speed +""" + +include("mesh.jl") + +# ---------------------- 抽象基类 ---------------------- +""" +InviscidFluxCalculator 抽象类型 +Julia 无 ABC,用文档约定 +所有子类型必须实现 compute! +""" +abstract type InviscidFluxCalculator end + +# ---------------------- RusanovFluxCalculator ---------------------- +mutable struct RusanovFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function RusanovFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::RusanovFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = calc.wave_speed + c_R = calc.wave_speed + F_L = c_L * u_L + F_R = c_R * u_R + Smax = max(abs(c_L), abs(c_R)) + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + end +end + +# ---------------------- EngquistOsherFluxCalculator ---------------------- +mutable struct EngquistOsherFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function EngquistOsherFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::EngquistOsherFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + c = calc.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + end +end + +# ---------------------- FluxCalculatorFactory ---------------------- +module FluxCalculatorFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "通量计算器 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + config = cfd.config + if !hasproperty(config, :flux_type) + error("cfd.config 缺少 flux_type 字段") + end + + flux_type = config.flux_type + if !(flux_type isa AbstractString) + error("flux_type 必须为字符串,当前值: $flux_type") + end + + if !haskey(_REGISTRY, flux_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的通量计算器: '$flux_type'。可用选项: $available") + end + + return _REGISTRY[flux_type](cfd) +end + +# ✅ 修正:使用 Main. 前缀 +register("rusanov", cfd -> Main.RusanovFluxCalculator(cfd)) +register("engquist-osher", cfd -> Main.EngquistOsherFluxCalculator(cfd)) + +end # module FluxCalculatorFactory + +export FluxCalculatorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02b/initial_condition.jl b/example/1d-linear-convection/weno3/julia/02b/initial_condition.jl new file mode 100644 index 000000000..5e029e8d2 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02b/initial_condition.jl @@ -0,0 +1,86 @@ +# julia/initial_condition.jl +""" +初始条件模块(Julia 版) +目标:与 Python initial_condition.py 行为完全一致 +""" + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象接口(Julia 无继承,用函数约定) ---------------------- +# 所有初始条件必须实现: +# evaluate_at(ic, x::AbstractVector{Float64}) -> Vector{Float64} +# apply(ic, solution) + +# ---------------------- StepFunctionIC ---------------------- +struct StepFunctionIC + config::Any +end + +function evaluate_at(ic::StepFunctionIC, x::AbstractVector{Float64}) + u0 = ones(Float64, length(x)) + for i in eachindex(x) + if 0.5 <= x[i] <= 1.0 + u0[i] = 2.0 + end + end + return u0 +end + +function apply(ic::StepFunctionIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- SineWaveIC ---------------------- +struct SineWaveIC + config::Any +end + +function evaluate_at(ic::SineWaveIC, x::AbstractVector{Float64}) + L = getfield_safe(ic.config, :domain_length, 2.0) + return sin.(2π * x / L) +end + +function apply(ic::SineWaveIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- GaussianPulseIC ---------------------- +struct GaussianPulseIC + config::Any +end + +function evaluate_at(ic::GaussianPulseIC, x::AbstractVector{Float64}) + center = getfield_safe(ic.config, :pulse_center, 0.5) + width = getfield_safe(ic.config, :pulse_width, 0.1) + return exp.(-((x .- center) ./ width).^2) +end + +function apply(ic::GaussianPulseIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- 公共辅助函数 ---------------------- +""" +将初始场 values 应用到 solution.u 的物理区域(含 ghost 的数组) +""" +function _apply_to_interior(solution, values) + domain = solution.domain + # Python: for i in range(domain.ist, domain.ied) + # Julia: for i in domain.ist:domain.ied-1 (因为 ied 是结束索引,不包含) + for i in domain.ist:(domain.ied - 1) + j = i - domain.ist + 1 # values 是 1-based,i - ist 是 0-based → +1 + solution.u[i] = values[j] + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02b/mesh.jl b/example/1d-linear-convection/weno3/julia/02b/mesh.jl new file mode 100644 index 000000000..1b0dd4bf9 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02b/mesh.jl @@ -0,0 +1,45 @@ +# julia/mesh.jl +""" +Mesh 结构体(与提供的 mesh.py 完全同构) +- 字段名、初始化顺序、循环逻辑完全一致 +- 不做任何泛化或优化 +""" + +mutable struct Mesh + xmin::Float64 + xmax::Float64 + ncells::Int + nnodes::Int + nx::Int + x::Vector{Float64} + xcc::Vector{Float64} + L::Float64 # 在 init_mesh 中赋值 + dx::Float64 # 在 init_mesh 中赋值 + + function Mesh() + # 与 Python __init__ 完全一致 + xmin = 0.0 + xmax = 2.0 + ncells = 40 + nnodes = ncells + 1 + nx = ncells + x = zeros(Float64, nnodes) + xcc = zeros(Float64, ncells) + + # 调用 init_mesh(模拟 Python) + L = xmax - xmin + dx = L / ncells + + # Generate node coordinates: for i in range(self.nnodes) + for i in 1:nnodes + x[i] = xmin + (i - 1) * dx # Python i → Julia i-1 + end + + # Generate cell center coordinates + for i in 1:ncells + xcc[i] = 0.5 * (x[i] + x[i+1]) + end + + new(xmin, xmax, ncells, nnodes, nx, x, xcc, L, dx) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02b/plotter.jl b/example/1d-linear-convection/weno3/julia/02b/plotter.jl new file mode 100644 index 000000000..a77d8d688 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02b/plotter.jl @@ -0,0 +1,147 @@ +# julia/plotter.jl +""" +CFDPlotter 的 Julia 实现(通过 PythonCall.jl 调用 Matplotlib) +确保与 Python plotter.py 行为完全一致 +""" + +using PythonCall + +# 初始化 Python 环境(加载 matplotlib, inflect) +const plt = pyimport("matplotlib.pyplot") +const inflect = pyimport("inflect") + +mutable struct CFDPlotter + default_styles::Dict{String, Any} + p::Py +end + +function CFDPlotter() + default_styles = Dict{String, Any}( + "numerical" => Dict( + :color => "blue", + :linestyle => "-", + :marker => "o", + :markerfacecolor => "none" + ), + "analytical" => Dict( + :color => "red", + :linestyle => "--", + :marker => "", + :linewidth => 1.5 + ), + "comparison" => [ + Dict(:color => "black", :linestyle => "-", :marker => "o", :markerfacecolor => "none"), + Dict(:color => "blue", :linestyle => "--", :marker => "s", :markerfacecolor => "none"), + Dict(:color => "green", :linestyle => ":", :marker => "^", :markerfacecolor => "none") + ] + ) + p = inflect.engine() + CFDPlotter(default_styles, p) +end + +""" +轻量即时绘图(快速验证结果) +""" +function plot_quick(plotter::CFDPlotter, cfd_result::Dict; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + rk_order = cfd_result["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = cfd_result["config"]["final_time"] + order = cfd_result["config"]["order"] + scheme = uppercase(cfd_result["config"]["scheme"]) + actual_title = "1D Convection (t=$(final_time))\n$(order)th-order $(scheme) + $(rk_str)-order RK" + end + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"]; + label="Numerical ($(uppercase(cfd_result["config"]["scheme"])))", + plotter.default_styles["numerical"]..., + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + # 通用样式 + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +""" +多格式/多精度对比绘图 +""" +function plot_comparison(plotter::CFDPlotter, result_list::Vector{Dict{String, Any}}; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + schemes = [uppercase(r["config"]["scheme"]) * string(r["config"]["order"]) for r in result_list] + rk_order = result_list[1]["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = result_list[1]["config"]["final_time"] + actual_title = "1D Convection Comparison (t=$(final_time))\n$(join(schemes, ", ")) + $(rk_str)-order RK" + end + + # 绘制多个数值解 + for (i, res) in enumerate(result_list) + style = plotter.default_styles["comparison"][mod1(i, length(plotter.default_styles["comparison"]))] + label = "Numerical ($(uppercase(res["config"]["scheme"]))$(res["config"]["order"]))" + plt.plot( + res["x"], res["numerical"]; + label=label, + style..., + markersize=5, linewidth=0.5 + ) + end + + # 绘制解析解 + plt.plot( + result_list[1]["x"], result_list[1]["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +function _set_common_style(plotter::CFDPlotter, title::String) + plt.title(title, fontsize=12) + plt.xlabel("x", fontsize=10) + plt.ylabel("u", fontsize=10) + plt.legend(fontsize=9) + plt.grid(true, color="gray", linestyle="--", linewidth=0.5, alpha=0.7) + plt.tight_layout() +end + +""" +快捷函数:ENO/WENO对比绘图 +""" +function plot_eno_weno_comparison(eno_result::Dict, weno_result::Dict; save_path=nothing) + plotter = CFDPlotter() + plot_comparison(plotter, [eno_result, weno_result]; save_path=save_path) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02b/reconstructor/eno.jl b/example/1d-linear-convection/weno3/julia/02b/reconstructor/eno.jl new file mode 100644 index 000000000..e78a636f3 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02b/reconstructor/eno.jl @@ -0,0 +1,107 @@ +# julia/reconstructor/eno.jl +""" +ENO 重构器(与 reconstructor/eno.py 完全同构) +""" + +# ---------------------- ENO 系数初始化 ---------------------- +function _init_eno_coef!(spatial_order::Int, coef::Matrix{Float64}) + if spatial_order == 1 + coef[1, 1] = 1.0 + coef[2, 1] = 1.0 + elseif spatial_order == 2 + coef[1, 1:2] = [3.0/2.0, -1.0/2.0] + coef[2, 1:2] = [1.0/2.0, 1.0/2.0] + coef[3, 1:2] = [-1.0/2.0, 3.0/2.0] + elseif spatial_order == 3 + coef[1, 1:3] = [11.0/6.0, -7.0/6.0, 1.0/3.0] + coef[2, 1:3] = [1.0/3.0, 5.0/6.0, -1.0/6.0] + coef[3, 1:3] = [-1.0/6.0, 5.0/6.0, 1.0/3.0] + coef[4, 1:3] = [1.0/3.0, -7.0/6.0, 11.0/6.0] + elseif spatial_order == 4 + coef[1, 1:4] = [25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0] + coef[2, 1:4] = [1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0] + coef[3, 1:4] = [-1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0] + coef[4, 1:4] = [1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0] + coef[5, 1:4] = [-1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0] + elseif spatial_order == 5 + coef[1, 1:5] = [137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0] + coef[2, 1:5] = [1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0] + coef[3, 1:5] = [-1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0] + coef[4, 1:5] = [1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0] + coef[5, 1:5] = [-1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0] + coef[6, 1:5] = [1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0] + elseif spatial_order == 6 + coef[1, 1:6] = [49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0] + coef[2, 1:6] = [1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0] + coef[3, 1:6] = [-1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0] + coef[4, 1:6] = [1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0] + coef[5, 1:6] = [-1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0] + coef[6, 1:6] = [1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0] + coef[7, 1:6] = [-1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0] + elseif spatial_order == 7 + coef[1, 1:7] = [363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0] + coef[2, 1:7] = [1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0] + coef[3, 1:7] = [-1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0] + coef[4, 1:7] = [1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0] + coef[5, 1:7] = [-1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0] + coef[6, 1:7] = [1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0] + coef[7, 1:7] = [-1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0] + coef[8, 1:7] = [1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0] + else + error("ENO 系数未实现 order=$spatial_order") + end +end + +# ---------------------- ENO 重构器 ---------------------- +mutable struct EnoReconstructor + spatial_order::Int + ntcells::Int + lmc::Vector{Int} + coef::Matrix{Float64} + dd::Matrix{Float64} + + function EnoReconstructor(spatial_order::Int, ntcells::Int) + lmc = zeros(Int, ntcells) + coef = zeros(Float64, spatial_order + 1, spatial_order) + dd = zeros(Float64, spatial_order, ntcells) + _init_eno_coef!(spatial_order, coef) + new(spatial_order, ntcells, lmc, coef, dd) + end +end + +function reconstruct(rec::EnoReconstructor, q::Vector{Float64}, cfd::Any) + # 1. 差商计算 (dd[1,:] = q) + @views rec.dd[1, :] .= q + for m in 2:rec.spatial_order + for j in 1:(rec.ntcells - m + 1) + rec.dd[m, j] = rec.dd[m-1, j+1] - rec.dd[m-1, j] + end + end + + # 2. 选择 smoothest stencil + domain = cfd.domain + for i in (domain.ist - 1):(domain.ied) # Python: range(ist-1, ied+1) → ied+1-1 = ied + rec.lmc[i] = i + for m in 2:rec.spatial_order + if abs(rec.dd[m, rec.lmc[i] - 1]) < abs(rec.dd[m, rec.lmc[i]]) + rec.lmc[i] -= 1 + end + end + end + + # 3. 重构界面值 + solution = cfd.solution + for i in domain.ist:(domain.ied) # Python: range(ist, ied+1) → ied+1-1 = ied + j = i - domain.ist + 1 # Julia 1-based + k1 = rec.lmc[i - 1] + k2 = rec.lmc[i] + r1 = (i - 1) - k1 + 1 + r2 = i - k2 + 1 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in 1:rec.spatial_order + solution.q_face_left[j] += q[k1 + m - 1] * rec.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m - 1] * rec.coef[r2, m] + end + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02b/reconstructor/weno3.jl b/example/1d-linear-convection/weno3/julia/02b/reconstructor/weno3.jl new file mode 100644 index 000000000..2b6fe1abb --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02b/reconstructor/weno3.jl @@ -0,0 +1,65 @@ +# julia/reconstructor/weno3.jl +""" +WENO3 重构器(与 reconstructor/weno3.py 完全同构) +""" + +mutable struct Weno3Reconstructor + # 无字段,与 Python 一致 +end + +function reconstruct(rec::Weno3Reconstructor, q::Vector{Float64}, cfd::Any) + domain = cfd.domain + solution = cfd.solution + _reconstruct_left_interfaces(domain, q, solution.q_face_left) + _reconstruct_right_interfaces(domain, q, solution.q_face_right) +end + +function _reconstruct_left_interfaces(domain, u, qL) + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in (domain.ist - 1):(domain.ied - 1) + j = i - (domain.ist - 1) + 1 # ← Julia 1-based: j = i - (ist-1) 对应 Python j = i - (ist-1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = _reconstruct_from_left_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_right_interfaces(domain, u, qR) + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in domain.ist:domain.ied + j = i - domain.ist + 1 # ← Julia 1-based: j = i - ist 对应 Python j = i - ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = _reconstruct_from_right_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_from_left_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -0.5*v1 + 1.5*v2 # r=1 stencil + q1 = 0.5*v2 + 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end + +function _reconstruct_from_right_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 0.5*v1 + 0.5*v2 # r=1 stencil + q1 = 1.5*v2 - 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02b/residual.jl b/example/1d-linear-convection/weno3/julia/02b/residual.jl new file mode 100644 index 000000000..e8fd65843 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02b/residual.jl @@ -0,0 +1,65 @@ +# julia/residual.jl +""" +残差计算器(与 residual.py 完全同构) +- 封装重建→通量→散度完整流程 +- 依赖 cfd 的多个字段 +""" + +include("mesh.jl") + +mutable struct ResidualCalculator + cfd::Any + config::Any + domain::Any + solution::Any + mesh::Mesh + reconstructor::Any + flux_calculator::Any # 通量计算器(外部传入,替代工厂) + + function ResidualCalculator(cfd::Any, flux_calculator::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + mesh = domain.mesh + reconstructor = cfd.reconstructor + + new(cfd, config, domain, solution, mesh, reconstructor, flux_calculator) + end +end + +""" +计算完整残差(对外唯一接口) +""" +function compute!(calc::ResidualCalculator) + _reconstruct(calc) + _compute_inviscid_flux(calc) + _compute_flux_divergence(calc) +end + +""" +私有方法:界面值重建 +""" +function _reconstruct(calc::ResidualCalculator) + reconstruct(calc.reconstructor, calc.solution.u, calc.cfd) +end + +""" +私有方法:计算无粘通量 +""" +function _compute_inviscid_flux(calc::ResidualCalculator) + compute!(calc.flux_calculator, + calc.solution.q_face_left, + calc.solution.q_face_right, + calc.solution.flux) +end + +""" +私有方法:计算通量散度(残差 = -dF/dx) +""" +function _compute_flux_divergence(calc::ResidualCalculator) + solution = calc.solution + mesh = calc.mesh + for i in 1:mesh.ncells + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / mesh.dx + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02b/run_eno_weno.jl b/example/1d-linear-convection/weno3/julia/02b/run_eno_weno.jl new file mode 100644 index 000000000..58b6cfa5f --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02b/run_eno_weno.jl @@ -0,0 +1,50 @@ +# julia/run_eno_weno.jl +""" +1:1 复刻 run_eno_weno.py 的 Julia 版本 +""" + +include("config.jl") +include("mesh.jl") +include("solver.jl") +include("plotter.jl") + +function performEnoWenoAnalysis() + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO3 求解 + println("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + with_reconstruction(config_eno3, "eno", 3) # 显式指定 3 阶 + config_eno3.dt = 0.0025 # 覆盖默认值 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + run!(cfd_eno3) # 求解并生成 result 字典 + + # 3. 配置并运行 WENO3 求解 + println("Running WENO3 solver...") + config_weno3 = CfdConfig() + with_reconstruction(config_weno3, "weno", 3) # 显式指定 3 阶(WENO 默认 5 阶) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + run!(cfd_weno3) + + # 5. 绘制 ENO/WENO 对比图 + println("Plotting comparison results...") + plot_eno_weno_comparison( + cfd_eno3.result, + cfd_weno3.result; + save_path="eno_weno_comparison.png" + ) + + return cfd_eno3, cfd_weno3 +end + +# 主程序入口 +if abspath(PROGRAM_FILE) == @__FILE__ + performEnoWenoAnalysis() + println("Analysis completed!") +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02b/solution.jl b/example/1d-linear-convection/weno3/julia/02b/solution.jl new file mode 100644 index 000000000..90ca03931 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02b/solution.jl @@ -0,0 +1,75 @@ +# julia/solution.jl +""" +Solution 结构体(与真实 solution.py 完全同构) +字段顺序、方法、逻辑 1:1 对齐 +""" + +include("mesh.jl") +include("domain.jl") +include("initial_condition.jl") + +mutable struct Solution + domain::Domain + q_face_left::Vector{Float64} + q_face_right::Vector{Float64} + flux::Vector{Float64} + res::Vector{Float64} + u::Vector{Float64} + un::Vector{Float64} + + function Solution(config::Any, domain::Domain) + mesh = domain.mesh + + q_face_left = zeros(Float64, mesh.nnodes) + q_face_right = zeros(Float64, mesh.nnodes) + flux = zeros(Float64, mesh.nnodes) + res = zeros(Float64, mesh.ncells) + u = zeros(Float64, domain.ntcells) + un = zeros(Float64, domain.ntcells) + + sol = new(domain, q_face_left, q_face_right, flux, res, u, un) + initialize_from_config(sol, config) + return sol + end +end + +""" +重置解数组为初始状态 +""" +function reset_solution(sol::Solution) + fill!(sol.u, 0.0) + fill!(sol.un, 0.0) +end + +""" +根据配置初始化场 +注:暂用硬编码替代 InitialConditionFactory +""" +function initialize_from_config(sol::Solution, config::Any) + ic_type = getfield_safe(config, :ic_type, "step") + + # 硬编码创建 IC(替代 InitialConditionFactory) + ic = if ic_type == "step" + StepFunctionIC(config) + elseif ic_type == "sin" + SineWaveIC(config) + elseif ic_type == "gaussian" + GaussianPulseIC(config) + else + error("未知初始条件类型: $ic_type") + end + + apply(ic, sol) # 调用 IC.apply +end + +""" +更新旧场:un = u +""" +function update_old_field(sol::Solution) + sol.un .= sol.u # 等价于 Python 的 un[:] = u[:] +end + +# ---------------------- 辅助函数 ---------------------- +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02b/solver.jl b/example/1d-linear-convection/weno3/julia/02b/solver.jl new file mode 100644 index 000000000..8165dcc27 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02b/solver.jl @@ -0,0 +1,172 @@ +# julia/solver.jl +""" +CFD 求解器主类(与 solver.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("initial_condition.jl") +include("boundary.jl") +include("flux.jl") +include("residual.jl") +include("time_integration.jl") +include("reconstructor/eno.jl") +include("reconstructor/weno3.jl") + +# 导入工厂模块(必须在顶层!) +using .FluxCalculatorFactory + +# ---------------------- Cfd 求解器 ---------------------- +mutable struct Cfd + config::Any + domain::Domain + solution::Solution + reconstructor::Any + residual_calculator::ResidualCalculator + integrator::Any + boundary_condition::Any + result::Dict{String, Any} + + function Cfd(config::Any, mesh::Mesh) + domain = Domain(config, mesh) + solution = Solution(config, domain) + + # 在 Cfd 构造函数中 + # =============== 重建器创建 =============== + recon_scheme = config.recon_scheme + spatial_order = config.spatial_order + + # ✅ 模拟 Python ReconstructorFactory 的逻辑 + if recon_scheme == "weno" + recon_scheme = "weno$(spatial_order)" + end + + # 现在根据 recon_scheme 创建 + reconstructor = if recon_scheme == "eno" + EnoReconstructor(spatial_order, domain.ntcells) + elseif recon_scheme == "weno3" + Weno3Reconstructor() + else + error("不支持的重建格式: $recon_scheme") + end + + # 构造 cfd 上下文(NamedTuple) + cfd_context = ( + config = config, + domain = domain + ) + + # 创建通量计算器 + flux_calculator = FluxCalculatorFactory.create(cfd_context) + + # 残差计算器 + residual_calculator = ResidualCalculator( + (config=config, domain=domain, solution=solution, reconstructor=reconstructor), + flux_calculator + ) + + # 边界条件 + boundary_condition = if config.boundary_type == "periodic" + PeriodicBoundary((config=config, domain=domain)) + elseif config.boundary_type == "dirichlet" + DirichletBoundary((config=config, domain=domain)) + elseif config.boundary_type == "neumann" + NeumannBoundary((config=config, domain=domain)) + else + error("不支持的边界类型: $(config.boundary_type)") + end + + + # 构造用于积分器初始化的上下文对象(NamedTuple,模拟 cfd 接口) + integrator_context = ( + config = config, + domain = domain, + solution = solution, + residual_calculator = residual_calculator, + boundary_condition = boundary_condition + ) + + # 使用注册表工厂创建时间推进器 + integrator = create_integrator(integrator_context) + + #@show typeof(integrator) + + # 注入 cfd 到 residual_calculator 和 integrator + residual_calculator.cfd = (config=config, domain=domain, solution=solution, reconstructor=reconstructor, residual_calculator=residual_calculator, integrator=integrator, boundary_condition=boundary_condition) + integrator.base.cfd = residual_calculator.cfd + + result = Dict{String, Any}() + new(config, domain, solution, reconstructor, residual_calculator, integrator, boundary_condition, result) + end +end + +""" +通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界 +""" +function exact_solution(cfd::Cfd) + x = cfd.domain.mesh.xcc + T = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = @. (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = if cfd.config.ic_type == "step" + StepFunctionIC(cfd.config) + elseif cfd.config.ic_type == "sin" + SineWaveIC(cfd.config) + elseif cfd.config.ic_type == "gaussian" + GaussianPulseIC(cfd.config) + else + error("未知初始条件: $(cfd.config.ic_type)") + end + + return evaluate_at(ic, x_shifted) +end + +""" +主求解循环 +""" +function run!(cfd::Cfd) + # 应用初始边界条件并同步 old field + apply!(cfd.boundary_condition, cfd.solution.u) + update_old_field(cfd.solution) + + t = 0.0 + dt_old = cfd.config.dt + dt = dt_old + + while t < cfd.config.final_time + if t + dt > cfd.config.final_time + dt = cfd.config.final_time - t + end + #@show t, dt, maximum(cfd.solution.u), minimum(cfd.solution.u) + # 执行时间步 + step(cfd.integrator, dt) + t += dt + end + + # 恢复 dt + cfd.config.dt = dt_old + + # 整理结果 + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied-1] # Python: [ist:ied] + analytical = exact_solution(cfd) + + cfd.result = Dict( + "x" => cfd.domain.mesh.xcc, + "numerical" => u_numerical, + "analytical" => analytical, + "config" => Dict( + "scheme" => cfd.config.recon_scheme, + "order" => cfd.config.spatial_order, + "rk_order" => cfd.config.rk_order, + "final_time" => cfd.config.final_time + ) + ) + + return u_numerical +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02b/time_integration.jl b/example/1d-linear-convection/weno3/julia/02b/time_integration.jl new file mode 100644 index 000000000..43b540af7 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02b/time_integration.jl @@ -0,0 +1,168 @@ +# julia/time_integration.jl +""" +时间推进器模块(与 time_integration.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("residual.jl") +include("boundary.jl") + +# ---------------------- 抽象时间推进器基类 ---------------------- +abstract type TimeIntegrator end + +mutable struct TimeIntegratorBase <: TimeIntegrator + cfd::Any + config::Any + domain::Domain + solution::Solution + residual_calculator::Any # ResidualCalculator +end + +function TimeIntegratorBase(cfd::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + residual_calculator = cfd.residual_calculator + TimeIntegratorBase(cfd, config, domain, solution, residual_calculator) +end + +function compute_residual(integrator::TimeIntegratorBase) + compute!(integrator.residual_calculator) +end + +function apply_boundary(integrator::TimeIntegratorBase) + apply!(integrator.cfd.boundary_condition, integrator.solution.u) +end + +function map_idx(integrator::TimeIntegratorBase, i::Int) + return i - integrator.domain.ist + 1 # ← +1 转为 1-based +end + +# ---------------------- RK1Integrator ---------------------- +mutable struct RK1Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK1Integrator(cfd::Any) + RK1Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK1Integrator, dt::Float64) + compute_residual(integrator.base) + for i in integrator.base.domain.ist:(integrator.base.domain.ied - 1) + j = map_idx(integrator.base, i) + integrator.base.solution.u[i] += dt * integrator.base.solution.res[j] + end + apply_boundary(integrator.base) + update_old_field(integrator.base.solution) +end + +# ---------------------- RK2Integrator ---------------------- +mutable struct RK2Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK2Integrator(cfd::Any) + RK2Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK2Integrator, dt::Float64) + base = integrator.base + # 阶段1:预测步 + compute_residual(base) + u_pred = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u_pred[i] += dt * base.solution.res[j] + end + base.solution.u .= u_pred + apply_boundary(base) + # 阶段2:校正步 + compute_residual(base) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = 0.5 * base.solution.un[i] + 0.5 * base.solution.u[i] + 0.5 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end + +# ---------------------- RK3Integrator ---------------------- +mutable struct RK3Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK3Integrator(cfd::Any) + RK3Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK3Integrator, dt::Float64) + base = integrator.base + # 阶段1 + compute_residual(base) + u1 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u1[i] += dt * base.solution.res[j] + end + base.solution.u .= u1 + apply_boundary(base) + # 阶段2 + compute_residual(base) + u2 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u2[i] = 0.75 * base.solution.un[i] + 0.25 * base.solution.u[i] + 0.25 * dt * base.solution.res[j] + end + base.solution.u .= u2 + apply_boundary(base) + # 阶段3 + compute_residual(base) + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = c1 * base.solution.un[i] + c2 * base.solution.u[i] + c3 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end + +# ---------------------- 注册表 + 工厂(方案2)---------------------- + +const INTEGRATOR_REGISTRY = Dict{String, Function}() + +function register_integrator(name::String, ctor::Function) + if haskey(INTEGRATOR_REGISTRY, name) + @warn "积分器 '$name' 已注册,将被覆盖" + end + INTEGRATOR_REGISTRY[name] = ctor +end + +function create_integrator(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + config = cfd.config + if !hasproperty(config, :rk_order) + error("cfd.config 缺少 rk_order 字段") + end + rk_order = config.rk_order + if !(rk_order isa Integer) || rk_order < 1 + error("rk_order 必须为正整数,当前值: $rk_order (类型: $(typeof(rk_order)))") + end + + name = "rk$rk_order" + if !haskey(INTEGRATOR_REGISTRY, name) + available = sort(collect(keys(INTEGRATOR_REGISTRY))) + error("未注册的时间积分器: '$name'。可用选项: $available") + end + + return INTEGRATOR_REGISTRY[name](cfd) +end + +# 注册内置积分器 +register_integrator("rk1", cfd -> RK1Integrator(cfd)) +register_integrator("rk2", cfd -> RK2Integrator(cfd)) +register_integrator("rk3", cfd -> RK3Integrator(cfd)) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02c/boundary.jl b/example/1d-linear-convection/weno3/julia/02c/boundary.jl new file mode 100644 index 000000000..5b0baaf43 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02c/boundary.jl @@ -0,0 +1,90 @@ +# julia/boundary.jl +# 目标:与 Python boundary.py 行为完全一致(1:1 同构) +# 前提:ist/ied 在 Julia 中按 1-based 设置(ist = nghosts + 1) + +using NPZ # 仅用于测试,实际模块可不依赖 + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象基类(Julia 风格:用函数接口) ---------------------- +# Julia 无 abstract class,用文档+约定 +# 所有子类型必须实现 apply!(bc, u) + +# ---------------------- PeriodicBoundary ---------------------- +mutable struct PeriodicBoundary + cfd::Any +end + +function apply!(self::PeriodicBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist # 1-based, e.g., 3 + ied = self.cfd.domain.ied # 1-based, e.g., 43 + + # 左 ghost: u[ist - 1 - ig] = u[ied - 1 - ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ied - 1 - ig] + end + # 右 ghost: u[ied + ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ied + ig] = u[ist + ig] + end +end + +# ---------------------- DirichletBoundary ---------------------- +mutable struct DirichletBoundary + cfd::Any +end + +function apply!(self::DirichletBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + left_value = getfield_safe(self.cfd.config, :left_boundary_value, 1.0) + right_value = getfield_safe(self.cfd.config, :right_boundary_value, 2.0) + + # 左边界 + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = left_value + end + # 右边界 + for ig in 0:(nghosts-1) + u[ied + ig] = right_value + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Dirichlet边界: 左值=$left_value, 右值=$right_value") + end +end + +# ---------------------- NeumannBoundary ---------------------- +mutable struct NeumannBoundary + cfd::Any +end + +function apply!(self::NeumannBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + # 左边界零梯度:u[ist - 1 - ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ist + ig] + end + # 右边界零梯度:u[ied + ig] = u[ied - 1 - ig] ← 注意是 ied - 1 - ig + for ig in 0:(nghosts-1) + u[ied + ig] = u[ied - 1 - ig] + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Neumann边界: 零梯度") + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02c/config.jl b/example/1d-linear-convection/weno3/julia/02c/config.jl new file mode 100644 index 000000000..bf48de3a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02c/config.jl @@ -0,0 +1,68 @@ +# julia/config.jl +""" +CfdConfig:与 Python config.py 完全同构 +""" +mutable struct CfdConfig + ic_type::String + recon_scheme::String + flux_type::String + rk_order::Int + wave_speed::Float64 + final_time::Float64 + dt::Float64 + boundary_type::String + left_boundary_value::Float64 + right_boundary_value::Float64 + spatial_order::Int + + function CfdConfig() + new( + "step", + "eno", + "rusanov", + 1, + 1.0, + 0.625, + 0.025, + "periodic", + 1.0, + 2.0, + 2 + ) + end +end + +""" +专用配置:重建方案(链式调用) +""" +function with_reconstruction(cfg::CfdConfig, scheme::String, order::Union{Int, Nothing}=nothing) + cfg.recon_scheme = lowercase(scheme) + + if order !== nothing + cfg.spatial_order = order + else + if startswith(cfg.recon_scheme, "weno") + cfg.spatial_order = 5 + elseif cfg.recon_scheme == "eno" + cfg.spatial_order = 3 + else + error("不支持的重建格式:$scheme(仅支持 eno/weno)") + end + end + + return cfg # 支持链式调用 +end + +""" +专用配置:边界条件(链式调用) +""" +function with_boundary(cfg::CfdConfig, bc_type::String; left_value=nothing, right_value=nothing) + cfg.boundary_type = bc_type + if left_value !== nothing + cfg.left_boundary_value = left_value + end + if right_value !== nothing + cfg.right_boundary_value = right_value + end + return cfg +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02c/domain.jl b/example/1d-linear-convection/weno3/julia/02c/domain.jl new file mode 100644 index 000000000..a7edc2260 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02c/domain.jl @@ -0,0 +1,61 @@ +# julia/domain.jl +""" +Domain 结构体(与 domain.py 完全同构) +- 保存 config +- nghosts 计算逻辑 1:1 +- ist/ied 为 0-based(与 Python 一致) +""" + +include("mesh.jl") + +mutable struct Domain + config::Any # 保存 config(与 Python 一致) + mesh::Mesh + nghosts::Int + ist::Int # 0-based + ied::Int # 0-based + ntcells::Int +end + +function Domain(config::Any, mesh::Mesh) + nghosts = _calc_nghosts(config) + ist = nghosts + 1 # ← 1-based 起始索引 + ied = ist + mesh.ncells # ← 1-based 结束索引(不包含) + ntcells = mesh.ncells + 2 * nghosts + Domain(config, mesh, nghosts, ist, ied, ntcells) +end + +function _calc_nghosts(config::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + order = getfield_safe(config, :spatial_order, 2) + + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + if scheme == "eno" + nghosts = order + elseif startswith(scheme, "weno") + nghosts = order ÷ 2 + 1 + else + error("未知重建格式 $(scheme),无法计算ghost层!") + end + + if nghosts <= 0 + error("计算得到的ghost层数量无效:$(nghosts)(阶数$(order),格式$(scheme))") + end + + return nghosts +end + +function is_physical_cell(domain::Domain, idx::Int) + return domain.ist <= idx < domain.ied +end + +function get_physical_indices(domain::Domain) + return domain.ist:(domain.ied - 1) +end + +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02c/flux.jl b/example/1d-linear-convection/weno3/julia/02c/flux.jl new file mode 100644 index 000000000..cff235824 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02c/flux.jl @@ -0,0 +1,112 @@ +# julia/flux.jl +""" +通量计算器模块(与 flux.py 完全同构) +- 抽象基类 + 具体实现 +- 字段:cfd, config, mesh, wave_speed +""" + +include("mesh.jl") + +# ---------------------- 抽象基类 ---------------------- +""" +InviscidFluxCalculator 抽象类型 +Julia 无 ABC,用文档约定 +所有子类型必须实现 compute! +""" +abstract type InviscidFluxCalculator end + +# ---------------------- RusanovFluxCalculator ---------------------- +mutable struct RusanovFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function RusanovFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::RusanovFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = calc.wave_speed + c_R = calc.wave_speed + F_L = c_L * u_L + F_R = c_R * u_R + Smax = max(abs(c_L), abs(c_R)) + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + end +end + +# ---------------------- EngquistOsherFluxCalculator ---------------------- +mutable struct EngquistOsherFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function EngquistOsherFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::EngquistOsherFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + c = calc.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + end +end + +# ---------------------- FluxCalculatorFactory ---------------------- +module FluxCalculatorFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "通量计算器 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + config = cfd.config + if !hasproperty(config, :flux_type) + error("cfd.config 缺少 flux_type 字段") + end + + flux_type = config.flux_type + if !(flux_type isa AbstractString) + error("flux_type 必须为字符串,当前值: $flux_type") + end + + if !haskey(_REGISTRY, flux_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的通量计算器: '$flux_type'。可用选项: $available") + end + + return _REGISTRY[flux_type](cfd) +end + +# ✅ 修正:使用 Main. 前缀 +register("rusanov", cfd -> Main.RusanovFluxCalculator(cfd)) +register("engquist-osher", cfd -> Main.EngquistOsherFluxCalculator(cfd)) + +end # module FluxCalculatorFactory + +export FluxCalculatorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02c/initial_condition.jl b/example/1d-linear-convection/weno3/julia/02c/initial_condition.jl new file mode 100644 index 000000000..5e029e8d2 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02c/initial_condition.jl @@ -0,0 +1,86 @@ +# julia/initial_condition.jl +""" +初始条件模块(Julia 版) +目标:与 Python initial_condition.py 行为完全一致 +""" + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象接口(Julia 无继承,用函数约定) ---------------------- +# 所有初始条件必须实现: +# evaluate_at(ic, x::AbstractVector{Float64}) -> Vector{Float64} +# apply(ic, solution) + +# ---------------------- StepFunctionIC ---------------------- +struct StepFunctionIC + config::Any +end + +function evaluate_at(ic::StepFunctionIC, x::AbstractVector{Float64}) + u0 = ones(Float64, length(x)) + for i in eachindex(x) + if 0.5 <= x[i] <= 1.0 + u0[i] = 2.0 + end + end + return u0 +end + +function apply(ic::StepFunctionIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- SineWaveIC ---------------------- +struct SineWaveIC + config::Any +end + +function evaluate_at(ic::SineWaveIC, x::AbstractVector{Float64}) + L = getfield_safe(ic.config, :domain_length, 2.0) + return sin.(2π * x / L) +end + +function apply(ic::SineWaveIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- GaussianPulseIC ---------------------- +struct GaussianPulseIC + config::Any +end + +function evaluate_at(ic::GaussianPulseIC, x::AbstractVector{Float64}) + center = getfield_safe(ic.config, :pulse_center, 0.5) + width = getfield_safe(ic.config, :pulse_width, 0.1) + return exp.(-((x .- center) ./ width).^2) +end + +function apply(ic::GaussianPulseIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- 公共辅助函数 ---------------------- +""" +将初始场 values 应用到 solution.u 的物理区域(含 ghost 的数组) +""" +function _apply_to_interior(solution, values) + domain = solution.domain + # Python: for i in range(domain.ist, domain.ied) + # Julia: for i in domain.ist:domain.ied-1 (因为 ied 是结束索引,不包含) + for i in domain.ist:(domain.ied - 1) + j = i - domain.ist + 1 # values 是 1-based,i - ist 是 0-based → +1 + solution.u[i] = values[j] + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02c/mesh.jl b/example/1d-linear-convection/weno3/julia/02c/mesh.jl new file mode 100644 index 000000000..1b0dd4bf9 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02c/mesh.jl @@ -0,0 +1,45 @@ +# julia/mesh.jl +""" +Mesh 结构体(与提供的 mesh.py 完全同构) +- 字段名、初始化顺序、循环逻辑完全一致 +- 不做任何泛化或优化 +""" + +mutable struct Mesh + xmin::Float64 + xmax::Float64 + ncells::Int + nnodes::Int + nx::Int + x::Vector{Float64} + xcc::Vector{Float64} + L::Float64 # 在 init_mesh 中赋值 + dx::Float64 # 在 init_mesh 中赋值 + + function Mesh() + # 与 Python __init__ 完全一致 + xmin = 0.0 + xmax = 2.0 + ncells = 40 + nnodes = ncells + 1 + nx = ncells + x = zeros(Float64, nnodes) + xcc = zeros(Float64, ncells) + + # 调用 init_mesh(模拟 Python) + L = xmax - xmin + dx = L / ncells + + # Generate node coordinates: for i in range(self.nnodes) + for i in 1:nnodes + x[i] = xmin + (i - 1) * dx # Python i → Julia i-1 + end + + # Generate cell center coordinates + for i in 1:ncells + xcc[i] = 0.5 * (x[i] + x[i+1]) + end + + new(xmin, xmax, ncells, nnodes, nx, x, xcc, L, dx) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02c/plotter.jl b/example/1d-linear-convection/weno3/julia/02c/plotter.jl new file mode 100644 index 000000000..a77d8d688 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02c/plotter.jl @@ -0,0 +1,147 @@ +# julia/plotter.jl +""" +CFDPlotter 的 Julia 实现(通过 PythonCall.jl 调用 Matplotlib) +确保与 Python plotter.py 行为完全一致 +""" + +using PythonCall + +# 初始化 Python 环境(加载 matplotlib, inflect) +const plt = pyimport("matplotlib.pyplot") +const inflect = pyimport("inflect") + +mutable struct CFDPlotter + default_styles::Dict{String, Any} + p::Py +end + +function CFDPlotter() + default_styles = Dict{String, Any}( + "numerical" => Dict( + :color => "blue", + :linestyle => "-", + :marker => "o", + :markerfacecolor => "none" + ), + "analytical" => Dict( + :color => "red", + :linestyle => "--", + :marker => "", + :linewidth => 1.5 + ), + "comparison" => [ + Dict(:color => "black", :linestyle => "-", :marker => "o", :markerfacecolor => "none"), + Dict(:color => "blue", :linestyle => "--", :marker => "s", :markerfacecolor => "none"), + Dict(:color => "green", :linestyle => ":", :marker => "^", :markerfacecolor => "none") + ] + ) + p = inflect.engine() + CFDPlotter(default_styles, p) +end + +""" +轻量即时绘图(快速验证结果) +""" +function plot_quick(plotter::CFDPlotter, cfd_result::Dict; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + rk_order = cfd_result["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = cfd_result["config"]["final_time"] + order = cfd_result["config"]["order"] + scheme = uppercase(cfd_result["config"]["scheme"]) + actual_title = "1D Convection (t=$(final_time))\n$(order)th-order $(scheme) + $(rk_str)-order RK" + end + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"]; + label="Numerical ($(uppercase(cfd_result["config"]["scheme"])))", + plotter.default_styles["numerical"]..., + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + # 通用样式 + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +""" +多格式/多精度对比绘图 +""" +function plot_comparison(plotter::CFDPlotter, result_list::Vector{Dict{String, Any}}; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + schemes = [uppercase(r["config"]["scheme"]) * string(r["config"]["order"]) for r in result_list] + rk_order = result_list[1]["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = result_list[1]["config"]["final_time"] + actual_title = "1D Convection Comparison (t=$(final_time))\n$(join(schemes, ", ")) + $(rk_str)-order RK" + end + + # 绘制多个数值解 + for (i, res) in enumerate(result_list) + style = plotter.default_styles["comparison"][mod1(i, length(plotter.default_styles["comparison"]))] + label = "Numerical ($(uppercase(res["config"]["scheme"]))$(res["config"]["order"]))" + plt.plot( + res["x"], res["numerical"]; + label=label, + style..., + markersize=5, linewidth=0.5 + ) + end + + # 绘制解析解 + plt.plot( + result_list[1]["x"], result_list[1]["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +function _set_common_style(plotter::CFDPlotter, title::String) + plt.title(title, fontsize=12) + plt.xlabel("x", fontsize=10) + plt.ylabel("u", fontsize=10) + plt.legend(fontsize=9) + plt.grid(true, color="gray", linestyle="--", linewidth=0.5, alpha=0.7) + plt.tight_layout() +end + +""" +快捷函数:ENO/WENO对比绘图 +""" +function plot_eno_weno_comparison(eno_result::Dict, weno_result::Dict; save_path=nothing) + plotter = CFDPlotter() + plot_comparison(plotter, [eno_result, weno_result]; save_path=save_path) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02c/reconstructor/eno.jl b/example/1d-linear-convection/weno3/julia/02c/reconstructor/eno.jl new file mode 100644 index 000000000..e78a636f3 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02c/reconstructor/eno.jl @@ -0,0 +1,107 @@ +# julia/reconstructor/eno.jl +""" +ENO 重构器(与 reconstructor/eno.py 完全同构) +""" + +# ---------------------- ENO 系数初始化 ---------------------- +function _init_eno_coef!(spatial_order::Int, coef::Matrix{Float64}) + if spatial_order == 1 + coef[1, 1] = 1.0 + coef[2, 1] = 1.0 + elseif spatial_order == 2 + coef[1, 1:2] = [3.0/2.0, -1.0/2.0] + coef[2, 1:2] = [1.0/2.0, 1.0/2.0] + coef[3, 1:2] = [-1.0/2.0, 3.0/2.0] + elseif spatial_order == 3 + coef[1, 1:3] = [11.0/6.0, -7.0/6.0, 1.0/3.0] + coef[2, 1:3] = [1.0/3.0, 5.0/6.0, -1.0/6.0] + coef[3, 1:3] = [-1.0/6.0, 5.0/6.0, 1.0/3.0] + coef[4, 1:3] = [1.0/3.0, -7.0/6.0, 11.0/6.0] + elseif spatial_order == 4 + coef[1, 1:4] = [25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0] + coef[2, 1:4] = [1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0] + coef[3, 1:4] = [-1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0] + coef[4, 1:4] = [1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0] + coef[5, 1:4] = [-1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0] + elseif spatial_order == 5 + coef[1, 1:5] = [137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0] + coef[2, 1:5] = [1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0] + coef[3, 1:5] = [-1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0] + coef[4, 1:5] = [1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0] + coef[5, 1:5] = [-1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0] + coef[6, 1:5] = [1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0] + elseif spatial_order == 6 + coef[1, 1:6] = [49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0] + coef[2, 1:6] = [1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0] + coef[3, 1:6] = [-1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0] + coef[4, 1:6] = [1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0] + coef[5, 1:6] = [-1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0] + coef[6, 1:6] = [1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0] + coef[7, 1:6] = [-1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0] + elseif spatial_order == 7 + coef[1, 1:7] = [363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0] + coef[2, 1:7] = [1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0] + coef[3, 1:7] = [-1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0] + coef[4, 1:7] = [1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0] + coef[5, 1:7] = [-1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0] + coef[6, 1:7] = [1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0] + coef[7, 1:7] = [-1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0] + coef[8, 1:7] = [1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0] + else + error("ENO 系数未实现 order=$spatial_order") + end +end + +# ---------------------- ENO 重构器 ---------------------- +mutable struct EnoReconstructor + spatial_order::Int + ntcells::Int + lmc::Vector{Int} + coef::Matrix{Float64} + dd::Matrix{Float64} + + function EnoReconstructor(spatial_order::Int, ntcells::Int) + lmc = zeros(Int, ntcells) + coef = zeros(Float64, spatial_order + 1, spatial_order) + dd = zeros(Float64, spatial_order, ntcells) + _init_eno_coef!(spatial_order, coef) + new(spatial_order, ntcells, lmc, coef, dd) + end +end + +function reconstruct(rec::EnoReconstructor, q::Vector{Float64}, cfd::Any) + # 1. 差商计算 (dd[1,:] = q) + @views rec.dd[1, :] .= q + for m in 2:rec.spatial_order + for j in 1:(rec.ntcells - m + 1) + rec.dd[m, j] = rec.dd[m-1, j+1] - rec.dd[m-1, j] + end + end + + # 2. 选择 smoothest stencil + domain = cfd.domain + for i in (domain.ist - 1):(domain.ied) # Python: range(ist-1, ied+1) → ied+1-1 = ied + rec.lmc[i] = i + for m in 2:rec.spatial_order + if abs(rec.dd[m, rec.lmc[i] - 1]) < abs(rec.dd[m, rec.lmc[i]]) + rec.lmc[i] -= 1 + end + end + end + + # 3. 重构界面值 + solution = cfd.solution + for i in domain.ist:(domain.ied) # Python: range(ist, ied+1) → ied+1-1 = ied + j = i - domain.ist + 1 # Julia 1-based + k1 = rec.lmc[i - 1] + k2 = rec.lmc[i] + r1 = (i - 1) - k1 + 1 + r2 = i - k2 + 1 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in 1:rec.spatial_order + solution.q_face_left[j] += q[k1 + m - 1] * rec.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m - 1] * rec.coef[r2, m] + end + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02c/reconstructor/weno3.jl b/example/1d-linear-convection/weno3/julia/02c/reconstructor/weno3.jl new file mode 100644 index 000000000..2b6fe1abb --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02c/reconstructor/weno3.jl @@ -0,0 +1,65 @@ +# julia/reconstructor/weno3.jl +""" +WENO3 重构器(与 reconstructor/weno3.py 完全同构) +""" + +mutable struct Weno3Reconstructor + # 无字段,与 Python 一致 +end + +function reconstruct(rec::Weno3Reconstructor, q::Vector{Float64}, cfd::Any) + domain = cfd.domain + solution = cfd.solution + _reconstruct_left_interfaces(domain, q, solution.q_face_left) + _reconstruct_right_interfaces(domain, q, solution.q_face_right) +end + +function _reconstruct_left_interfaces(domain, u, qL) + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in (domain.ist - 1):(domain.ied - 1) + j = i - (domain.ist - 1) + 1 # ← Julia 1-based: j = i - (ist-1) 对应 Python j = i - (ist-1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = _reconstruct_from_left_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_right_interfaces(domain, u, qR) + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in domain.ist:domain.ied + j = i - domain.ist + 1 # ← Julia 1-based: j = i - ist 对应 Python j = i - ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = _reconstruct_from_right_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_from_left_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -0.5*v1 + 1.5*v2 # r=1 stencil + q1 = 0.5*v2 + 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end + +function _reconstruct_from_right_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 0.5*v1 + 0.5*v2 # r=1 stencil + q1 = 1.5*v2 - 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02c/residual.jl b/example/1d-linear-convection/weno3/julia/02c/residual.jl new file mode 100644 index 000000000..e8fd65843 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02c/residual.jl @@ -0,0 +1,65 @@ +# julia/residual.jl +""" +残差计算器(与 residual.py 完全同构) +- 封装重建→通量→散度完整流程 +- 依赖 cfd 的多个字段 +""" + +include("mesh.jl") + +mutable struct ResidualCalculator + cfd::Any + config::Any + domain::Any + solution::Any + mesh::Mesh + reconstructor::Any + flux_calculator::Any # 通量计算器(外部传入,替代工厂) + + function ResidualCalculator(cfd::Any, flux_calculator::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + mesh = domain.mesh + reconstructor = cfd.reconstructor + + new(cfd, config, domain, solution, mesh, reconstructor, flux_calculator) + end +end + +""" +计算完整残差(对外唯一接口) +""" +function compute!(calc::ResidualCalculator) + _reconstruct(calc) + _compute_inviscid_flux(calc) + _compute_flux_divergence(calc) +end + +""" +私有方法:界面值重建 +""" +function _reconstruct(calc::ResidualCalculator) + reconstruct(calc.reconstructor, calc.solution.u, calc.cfd) +end + +""" +私有方法:计算无粘通量 +""" +function _compute_inviscid_flux(calc::ResidualCalculator) + compute!(calc.flux_calculator, + calc.solution.q_face_left, + calc.solution.q_face_right, + calc.solution.flux) +end + +""" +私有方法:计算通量散度(残差 = -dF/dx) +""" +function _compute_flux_divergence(calc::ResidualCalculator) + solution = calc.solution + mesh = calc.mesh + for i in 1:mesh.ncells + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / mesh.dx + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02c/run_eno_weno.jl b/example/1d-linear-convection/weno3/julia/02c/run_eno_weno.jl new file mode 100644 index 000000000..58b6cfa5f --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02c/run_eno_weno.jl @@ -0,0 +1,50 @@ +# julia/run_eno_weno.jl +""" +1:1 复刻 run_eno_weno.py 的 Julia 版本 +""" + +include("config.jl") +include("mesh.jl") +include("solver.jl") +include("plotter.jl") + +function performEnoWenoAnalysis() + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO3 求解 + println("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + with_reconstruction(config_eno3, "eno", 3) # 显式指定 3 阶 + config_eno3.dt = 0.0025 # 覆盖默认值 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + run!(cfd_eno3) # 求解并生成 result 字典 + + # 3. 配置并运行 WENO3 求解 + println("Running WENO3 solver...") + config_weno3 = CfdConfig() + with_reconstruction(config_weno3, "weno", 3) # 显式指定 3 阶(WENO 默认 5 阶) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + run!(cfd_weno3) + + # 5. 绘制 ENO/WENO 对比图 + println("Plotting comparison results...") + plot_eno_weno_comparison( + cfd_eno3.result, + cfd_weno3.result; + save_path="eno_weno_comparison.png" + ) + + return cfd_eno3, cfd_weno3 +end + +# 主程序入口 +if abspath(PROGRAM_FILE) == @__FILE__ + performEnoWenoAnalysis() + println("Analysis completed!") +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02c/solution.jl b/example/1d-linear-convection/weno3/julia/02c/solution.jl new file mode 100644 index 000000000..90ca03931 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02c/solution.jl @@ -0,0 +1,75 @@ +# julia/solution.jl +""" +Solution 结构体(与真实 solution.py 完全同构) +字段顺序、方法、逻辑 1:1 对齐 +""" + +include("mesh.jl") +include("domain.jl") +include("initial_condition.jl") + +mutable struct Solution + domain::Domain + q_face_left::Vector{Float64} + q_face_right::Vector{Float64} + flux::Vector{Float64} + res::Vector{Float64} + u::Vector{Float64} + un::Vector{Float64} + + function Solution(config::Any, domain::Domain) + mesh = domain.mesh + + q_face_left = zeros(Float64, mesh.nnodes) + q_face_right = zeros(Float64, mesh.nnodes) + flux = zeros(Float64, mesh.nnodes) + res = zeros(Float64, mesh.ncells) + u = zeros(Float64, domain.ntcells) + un = zeros(Float64, domain.ntcells) + + sol = new(domain, q_face_left, q_face_right, flux, res, u, un) + initialize_from_config(sol, config) + return sol + end +end + +""" +重置解数组为初始状态 +""" +function reset_solution(sol::Solution) + fill!(sol.u, 0.0) + fill!(sol.un, 0.0) +end + +""" +根据配置初始化场 +注:暂用硬编码替代 InitialConditionFactory +""" +function initialize_from_config(sol::Solution, config::Any) + ic_type = getfield_safe(config, :ic_type, "step") + + # 硬编码创建 IC(替代 InitialConditionFactory) + ic = if ic_type == "step" + StepFunctionIC(config) + elseif ic_type == "sin" + SineWaveIC(config) + elseif ic_type == "gaussian" + GaussianPulseIC(config) + else + error("未知初始条件类型: $ic_type") + end + + apply(ic, sol) # 调用 IC.apply +end + +""" +更新旧场:un = u +""" +function update_old_field(sol::Solution) + sol.un .= sol.u # 等价于 Python 的 un[:] = u[:] +end + +# ---------------------- 辅助函数 ---------------------- +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02c/solver.jl b/example/1d-linear-convection/weno3/julia/02c/solver.jl new file mode 100644 index 000000000..c45aa8970 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02c/solver.jl @@ -0,0 +1,171 @@ +# julia/solver.jl +""" +CFD 求解器主类(与 solver.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("initial_condition.jl") +include("boundary.jl") +include("flux.jl") +include("residual.jl") +include("time_integration.jl") +include("reconstructor/eno.jl") +include("reconstructor/weno3.jl") + +# 导入工厂模块(必须在顶层!) +using .FluxCalculatorFactory +using .TimeIntegratorFactory + +# ---------------------- Cfd 求解器 ---------------------- +mutable struct Cfd + config::Any + domain::Domain + solution::Solution + reconstructor::Any + residual_calculator::ResidualCalculator + integrator::Any + boundary_condition::Any + result::Dict{String, Any} + + function Cfd(config::Any, mesh::Mesh) + domain = Domain(config, mesh) + solution = Solution(config, domain) + + # 在 Cfd 构造函数中 + # =============== 重建器创建 =============== + recon_scheme = config.recon_scheme + spatial_order = config.spatial_order + + # ✅ 模拟 Python ReconstructorFactory 的逻辑 + if recon_scheme == "weno" + recon_scheme = "weno$(spatial_order)" + end + + # 现在根据 recon_scheme 创建 + reconstructor = if recon_scheme == "eno" + EnoReconstructor(spatial_order, domain.ntcells) + elseif recon_scheme == "weno3" + Weno3Reconstructor() + else + error("不支持的重建格式: $recon_scheme") + end + + # 构造 cfd 上下文(NamedTuple) + cfd_context = ( + config = config, + domain = domain + ) + + # 创建通量计算器 + flux_calculator = FluxCalculatorFactory.create(cfd_context) + + # 残差计算器 + residual_calculator = ResidualCalculator( + (config=config, domain=domain, solution=solution, reconstructor=reconstructor), + flux_calculator + ) + + # 边界条件 + boundary_condition = if config.boundary_type == "periodic" + PeriodicBoundary((config=config, domain=domain)) + elseif config.boundary_type == "dirichlet" + DirichletBoundary((config=config, domain=domain)) + elseif config.boundary_type == "neumann" + NeumannBoundary((config=config, domain=domain)) + else + error("不支持的边界类型: $(config.boundary_type)") + end + + # 构建时间推进器所需上下文 + integrator_context = ( + config = config, + domain = domain, + solution = solution, + residual_calculator = residual_calculator, + boundary_condition = boundary_condition + ) + + # 使用工厂创建时间推进器 + integrator = TimeIntegratorFactory.create(integrator_context) + #@show typeof(integrator) + + # 注入 cfd 到 residual_calculator 和 integrator + residual_calculator.cfd = (config=config, domain=domain, solution=solution, reconstructor=reconstructor, residual_calculator=residual_calculator, integrator=integrator, boundary_condition=boundary_condition) + integrator.base.cfd = residual_calculator.cfd + + result = Dict{String, Any}() + new(config, domain, solution, reconstructor, residual_calculator, integrator, boundary_condition, result) + end +end + +""" +通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界 +""" +function exact_solution(cfd::Cfd) + x = cfd.domain.mesh.xcc + T = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = @. (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = if cfd.config.ic_type == "step" + StepFunctionIC(cfd.config) + elseif cfd.config.ic_type == "sin" + SineWaveIC(cfd.config) + elseif cfd.config.ic_type == "gaussian" + GaussianPulseIC(cfd.config) + else + error("未知初始条件: $(cfd.config.ic_type)") + end + + return evaluate_at(ic, x_shifted) +end + +""" +主求解循环 +""" +function run!(cfd::Cfd) + # 应用初始边界条件并同步 old field + apply!(cfd.boundary_condition, cfd.solution.u) + update_old_field(cfd.solution) + + t = 0.0 + dt_old = cfd.config.dt + dt = dt_old + + while t < cfd.config.final_time + if t + dt > cfd.config.final_time + dt = cfd.config.final_time - t + end + #@show t, dt, maximum(cfd.solution.u), minimum(cfd.solution.u) + # 执行时间步 + step(cfd.integrator, dt) + t += dt + end + + # 恢复 dt + cfd.config.dt = dt_old + + # 整理结果 + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied-1] # Python: [ist:ied] + analytical = exact_solution(cfd) + + cfd.result = Dict( + "x" => cfd.domain.mesh.xcc, + "numerical" => u_numerical, + "analytical" => analytical, + "config" => Dict( + "scheme" => cfd.config.recon_scheme, + "order" => cfd.config.spatial_order, + "rk_order" => cfd.config.rk_order, + "final_time" => cfd.config.final_time + ) + ) + + return u_numerical +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02c/time_integration.jl b/example/1d-linear-convection/weno3/julia/02c/time_integration.jl new file mode 100644 index 000000000..add564ba5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02c/time_integration.jl @@ -0,0 +1,169 @@ +# julia/time_integration.jl +""" +时间推进器模块(与 time_integration.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("residual.jl") +include("boundary.jl") + +# ---------------------- 抽象时间推进器基类 ---------------------- +abstract type TimeIntegrator end + +mutable struct TimeIntegratorBase <: TimeIntegrator + cfd::Any + config::Any + domain::Domain + solution::Solution + residual_calculator::Any # ResidualCalculator +end + +function TimeIntegratorBase(cfd::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + residual_calculator = cfd.residual_calculator + TimeIntegratorBase(cfd, config, domain, solution, residual_calculator) +end + +function compute_residual(integrator::TimeIntegratorBase) + compute!(integrator.residual_calculator) +end + +function apply_boundary(integrator::TimeIntegratorBase) + apply!(integrator.cfd.boundary_condition, integrator.solution.u) +end + +function map_idx(integrator::TimeIntegratorBase, i::Int) + return i - integrator.domain.ist + 1 # ← +1 转为 1-based +end + +# ---------------------- RK1Integrator ---------------------- +mutable struct RK1Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK1Integrator(cfd::Any) + RK1Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK1Integrator, dt::Float64) + compute_residual(integrator.base) + for i in integrator.base.domain.ist:(integrator.base.domain.ied - 1) + j = map_idx(integrator.base, i) + integrator.base.solution.u[i] += dt * integrator.base.solution.res[j] + end + apply_boundary(integrator.base) + update_old_field(integrator.base.solution) +end + +# ---------------------- RK2Integrator ---------------------- +mutable struct RK2Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK2Integrator(cfd::Any) + RK2Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK2Integrator, dt::Float64) + base = integrator.base + # 阶段1:预测步 + compute_residual(base) + u_pred = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u_pred[i] += dt * base.solution.res[j] + end + base.solution.u .= u_pred + apply_boundary(base) + # 阶段2:校正步 + compute_residual(base) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = 0.5 * base.solution.un[i] + 0.5 * base.solution.u[i] + 0.5 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end + +# ---------------------- RK3Integrator ---------------------- +mutable struct RK3Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK3Integrator(cfd::Any) + RK3Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK3Integrator, dt::Float64) + base = integrator.base + # 阶段1 + compute_residual(base) + u1 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u1[i] += dt * base.solution.res[j] + end + base.solution.u .= u1 + apply_boundary(base) + # 阶段2 + compute_residual(base) + u2 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u2[i] = 0.75 * base.solution.un[i] + 0.25 * base.solution.u[i] + 0.25 * dt * base.solution.res[j] + end + base.solution.u .= u2 + apply_boundary(base) + # 阶段3 + compute_residual(base) + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = c1 * base.solution.un[i] + c2 * base.solution.u[i] + c3 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end + +# ---------------------- TimeIntegratorFactory ---------------------- +module TimeIntegratorFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "时间积分器 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + rk_order = cfd.config.rk_order + if !(rk_order isa Integer) || rk_order < 1 + error("rk_order 必须为正整数,当前值: $rk_order") + end + + name = "rk$rk_order" + if !haskey(_REGISTRY, name) + available = sort(collect(keys(_REGISTRY))) + error("未注册的时间积分器: '$name'。可用选项: $available") + end + + return _REGISTRY[name](cfd) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("rk1", cfd -> Main.RK1Integrator(cfd)) +register("rk2", cfd -> Main.RK2Integrator(cfd)) +register("rk3", cfd -> Main.RK3Integrator(cfd)) + +end # module TimeIntegratorFactory + +export TimeIntegratorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02d/boundary.jl b/example/1d-linear-convection/weno3/julia/02d/boundary.jl new file mode 100644 index 000000000..396c12642 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02d/boundary.jl @@ -0,0 +1,129 @@ +# julia/boundary.jl +# 目标:与 Python boundary.py 行为完全一致(1:1 同构) +# 前提:ist/ied 在 Julia 中按 1-based 设置(ist = nghosts + 1) + +using NPZ # 仅用于测试,实际模块可不依赖 + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象基类(Julia 风格:用函数接口) ---------------------- +# Julia 无 abstract class,用文档+约定 +# 所有子类型必须实现 apply!(bc, u) + +# ---------------------- PeriodicBoundary ---------------------- +mutable struct PeriodicBoundary + cfd::Any +end + +function apply!(self::PeriodicBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist # 1-based, e.g., 3 + ied = self.cfd.domain.ied # 1-based, e.g., 43 + + # 左 ghost: u[ist - 1 - ig] = u[ied - 1 - ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ied - 1 - ig] + end + # 右 ghost: u[ied + ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ied + ig] = u[ist + ig] + end +end + +# ---------------------- DirichletBoundary ---------------------- +mutable struct DirichletBoundary + cfd::Any +end + +function apply!(self::DirichletBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + left_value = getfield_safe(self.cfd.config, :left_boundary_value, 1.0) + right_value = getfield_safe(self.cfd.config, :right_boundary_value, 2.0) + + # 左边界 + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = left_value + end + # 右边界 + for ig in 0:(nghosts-1) + u[ied + ig] = right_value + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Dirichlet边界: 左值=$left_value, 右值=$right_value") + end +end + +# ---------------------- NeumannBoundary ---------------------- +mutable struct NeumannBoundary + cfd::Any +end + +function apply!(self::NeumannBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + # 左边界零梯度:u[ist - 1 - ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ist + ig] + end + # 右边界零梯度:u[ied + ig] = u[ied - 1 - ig] ← 注意是 ied - 1 - ig + for ig in 0:(nghosts-1) + u[ied + ig] = u[ied - 1 - ig] + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Neumann边界: 零梯度") + end +end + + +# ---------------------- BoundaryConditionFactory ---------------------- +module BoundaryConditionFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "边界条件 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + bc_type = cfd.config.boundary_type + if !(bc_type isa AbstractString) + error("boundary_type 必须为字符串,当前值: $bc_type") + end + + if !haskey(_REGISTRY, bc_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的边界条件: '$bc_type'。可用选项: $available") + end + + return _REGISTRY[bc_type](cfd) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("periodic", cfd -> Main.PeriodicBoundary(cfd)) +register("dirichlet", cfd -> Main.DirichletBoundary(cfd)) +register("neumann", cfd -> Main.NeumannBoundary(cfd)) + +end # module BoundaryConditionFactory + +export BoundaryConditionFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02d/config.jl b/example/1d-linear-convection/weno3/julia/02d/config.jl new file mode 100644 index 000000000..bf48de3a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02d/config.jl @@ -0,0 +1,68 @@ +# julia/config.jl +""" +CfdConfig:与 Python config.py 完全同构 +""" +mutable struct CfdConfig + ic_type::String + recon_scheme::String + flux_type::String + rk_order::Int + wave_speed::Float64 + final_time::Float64 + dt::Float64 + boundary_type::String + left_boundary_value::Float64 + right_boundary_value::Float64 + spatial_order::Int + + function CfdConfig() + new( + "step", + "eno", + "rusanov", + 1, + 1.0, + 0.625, + 0.025, + "periodic", + 1.0, + 2.0, + 2 + ) + end +end + +""" +专用配置:重建方案(链式调用) +""" +function with_reconstruction(cfg::CfdConfig, scheme::String, order::Union{Int, Nothing}=nothing) + cfg.recon_scheme = lowercase(scheme) + + if order !== nothing + cfg.spatial_order = order + else + if startswith(cfg.recon_scheme, "weno") + cfg.spatial_order = 5 + elseif cfg.recon_scheme == "eno" + cfg.spatial_order = 3 + else + error("不支持的重建格式:$scheme(仅支持 eno/weno)") + end + end + + return cfg # 支持链式调用 +end + +""" +专用配置:边界条件(链式调用) +""" +function with_boundary(cfg::CfdConfig, bc_type::String; left_value=nothing, right_value=nothing) + cfg.boundary_type = bc_type + if left_value !== nothing + cfg.left_boundary_value = left_value + end + if right_value !== nothing + cfg.right_boundary_value = right_value + end + return cfg +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02d/domain.jl b/example/1d-linear-convection/weno3/julia/02d/domain.jl new file mode 100644 index 000000000..a7edc2260 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02d/domain.jl @@ -0,0 +1,61 @@ +# julia/domain.jl +""" +Domain 结构体(与 domain.py 完全同构) +- 保存 config +- nghosts 计算逻辑 1:1 +- ist/ied 为 0-based(与 Python 一致) +""" + +include("mesh.jl") + +mutable struct Domain + config::Any # 保存 config(与 Python 一致) + mesh::Mesh + nghosts::Int + ist::Int # 0-based + ied::Int # 0-based + ntcells::Int +end + +function Domain(config::Any, mesh::Mesh) + nghosts = _calc_nghosts(config) + ist = nghosts + 1 # ← 1-based 起始索引 + ied = ist + mesh.ncells # ← 1-based 结束索引(不包含) + ntcells = mesh.ncells + 2 * nghosts + Domain(config, mesh, nghosts, ist, ied, ntcells) +end + +function _calc_nghosts(config::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + order = getfield_safe(config, :spatial_order, 2) + + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + if scheme == "eno" + nghosts = order + elseif startswith(scheme, "weno") + nghosts = order ÷ 2 + 1 + else + error("未知重建格式 $(scheme),无法计算ghost层!") + end + + if nghosts <= 0 + error("计算得到的ghost层数量无效:$(nghosts)(阶数$(order),格式$(scheme))") + end + + return nghosts +end + +function is_physical_cell(domain::Domain, idx::Int) + return domain.ist <= idx < domain.ied +end + +function get_physical_indices(domain::Domain) + return domain.ist:(domain.ied - 1) +end + +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02d/flux.jl b/example/1d-linear-convection/weno3/julia/02d/flux.jl new file mode 100644 index 000000000..cff235824 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02d/flux.jl @@ -0,0 +1,112 @@ +# julia/flux.jl +""" +通量计算器模块(与 flux.py 完全同构) +- 抽象基类 + 具体实现 +- 字段:cfd, config, mesh, wave_speed +""" + +include("mesh.jl") + +# ---------------------- 抽象基类 ---------------------- +""" +InviscidFluxCalculator 抽象类型 +Julia 无 ABC,用文档约定 +所有子类型必须实现 compute! +""" +abstract type InviscidFluxCalculator end + +# ---------------------- RusanovFluxCalculator ---------------------- +mutable struct RusanovFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function RusanovFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::RusanovFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = calc.wave_speed + c_R = calc.wave_speed + F_L = c_L * u_L + F_R = c_R * u_R + Smax = max(abs(c_L), abs(c_R)) + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + end +end + +# ---------------------- EngquistOsherFluxCalculator ---------------------- +mutable struct EngquistOsherFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function EngquistOsherFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::EngquistOsherFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + c = calc.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + end +end + +# ---------------------- FluxCalculatorFactory ---------------------- +module FluxCalculatorFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "通量计算器 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + config = cfd.config + if !hasproperty(config, :flux_type) + error("cfd.config 缺少 flux_type 字段") + end + + flux_type = config.flux_type + if !(flux_type isa AbstractString) + error("flux_type 必须为字符串,当前值: $flux_type") + end + + if !haskey(_REGISTRY, flux_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的通量计算器: '$flux_type'。可用选项: $available") + end + + return _REGISTRY[flux_type](cfd) +end + +# ✅ 修正:使用 Main. 前缀 +register("rusanov", cfd -> Main.RusanovFluxCalculator(cfd)) +register("engquist-osher", cfd -> Main.EngquistOsherFluxCalculator(cfd)) + +end # module FluxCalculatorFactory + +export FluxCalculatorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02d/initial_condition.jl b/example/1d-linear-convection/weno3/julia/02d/initial_condition.jl new file mode 100644 index 000000000..5e029e8d2 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02d/initial_condition.jl @@ -0,0 +1,86 @@ +# julia/initial_condition.jl +""" +初始条件模块(Julia 版) +目标:与 Python initial_condition.py 行为完全一致 +""" + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象接口(Julia 无继承,用函数约定) ---------------------- +# 所有初始条件必须实现: +# evaluate_at(ic, x::AbstractVector{Float64}) -> Vector{Float64} +# apply(ic, solution) + +# ---------------------- StepFunctionIC ---------------------- +struct StepFunctionIC + config::Any +end + +function evaluate_at(ic::StepFunctionIC, x::AbstractVector{Float64}) + u0 = ones(Float64, length(x)) + for i in eachindex(x) + if 0.5 <= x[i] <= 1.0 + u0[i] = 2.0 + end + end + return u0 +end + +function apply(ic::StepFunctionIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- SineWaveIC ---------------------- +struct SineWaveIC + config::Any +end + +function evaluate_at(ic::SineWaveIC, x::AbstractVector{Float64}) + L = getfield_safe(ic.config, :domain_length, 2.0) + return sin.(2π * x / L) +end + +function apply(ic::SineWaveIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- GaussianPulseIC ---------------------- +struct GaussianPulseIC + config::Any +end + +function evaluate_at(ic::GaussianPulseIC, x::AbstractVector{Float64}) + center = getfield_safe(ic.config, :pulse_center, 0.5) + width = getfield_safe(ic.config, :pulse_width, 0.1) + return exp.(-((x .- center) ./ width).^2) +end + +function apply(ic::GaussianPulseIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- 公共辅助函数 ---------------------- +""" +将初始场 values 应用到 solution.u 的物理区域(含 ghost 的数组) +""" +function _apply_to_interior(solution, values) + domain = solution.domain + # Python: for i in range(domain.ist, domain.ied) + # Julia: for i in domain.ist:domain.ied-1 (因为 ied 是结束索引,不包含) + for i in domain.ist:(domain.ied - 1) + j = i - domain.ist + 1 # values 是 1-based,i - ist 是 0-based → +1 + solution.u[i] = values[j] + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02d/mesh.jl b/example/1d-linear-convection/weno3/julia/02d/mesh.jl new file mode 100644 index 000000000..1b0dd4bf9 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02d/mesh.jl @@ -0,0 +1,45 @@ +# julia/mesh.jl +""" +Mesh 结构体(与提供的 mesh.py 完全同构) +- 字段名、初始化顺序、循环逻辑完全一致 +- 不做任何泛化或优化 +""" + +mutable struct Mesh + xmin::Float64 + xmax::Float64 + ncells::Int + nnodes::Int + nx::Int + x::Vector{Float64} + xcc::Vector{Float64} + L::Float64 # 在 init_mesh 中赋值 + dx::Float64 # 在 init_mesh 中赋值 + + function Mesh() + # 与 Python __init__ 完全一致 + xmin = 0.0 + xmax = 2.0 + ncells = 40 + nnodes = ncells + 1 + nx = ncells + x = zeros(Float64, nnodes) + xcc = zeros(Float64, ncells) + + # 调用 init_mesh(模拟 Python) + L = xmax - xmin + dx = L / ncells + + # Generate node coordinates: for i in range(self.nnodes) + for i in 1:nnodes + x[i] = xmin + (i - 1) * dx # Python i → Julia i-1 + end + + # Generate cell center coordinates + for i in 1:ncells + xcc[i] = 0.5 * (x[i] + x[i+1]) + end + + new(xmin, xmax, ncells, nnodes, nx, x, xcc, L, dx) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02d/plotter.jl b/example/1d-linear-convection/weno3/julia/02d/plotter.jl new file mode 100644 index 000000000..a77d8d688 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02d/plotter.jl @@ -0,0 +1,147 @@ +# julia/plotter.jl +""" +CFDPlotter 的 Julia 实现(通过 PythonCall.jl 调用 Matplotlib) +确保与 Python plotter.py 行为完全一致 +""" + +using PythonCall + +# 初始化 Python 环境(加载 matplotlib, inflect) +const plt = pyimport("matplotlib.pyplot") +const inflect = pyimport("inflect") + +mutable struct CFDPlotter + default_styles::Dict{String, Any} + p::Py +end + +function CFDPlotter() + default_styles = Dict{String, Any}( + "numerical" => Dict( + :color => "blue", + :linestyle => "-", + :marker => "o", + :markerfacecolor => "none" + ), + "analytical" => Dict( + :color => "red", + :linestyle => "--", + :marker => "", + :linewidth => 1.5 + ), + "comparison" => [ + Dict(:color => "black", :linestyle => "-", :marker => "o", :markerfacecolor => "none"), + Dict(:color => "blue", :linestyle => "--", :marker => "s", :markerfacecolor => "none"), + Dict(:color => "green", :linestyle => ":", :marker => "^", :markerfacecolor => "none") + ] + ) + p = inflect.engine() + CFDPlotter(default_styles, p) +end + +""" +轻量即时绘图(快速验证结果) +""" +function plot_quick(plotter::CFDPlotter, cfd_result::Dict; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + rk_order = cfd_result["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = cfd_result["config"]["final_time"] + order = cfd_result["config"]["order"] + scheme = uppercase(cfd_result["config"]["scheme"]) + actual_title = "1D Convection (t=$(final_time))\n$(order)th-order $(scheme) + $(rk_str)-order RK" + end + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"]; + label="Numerical ($(uppercase(cfd_result["config"]["scheme"])))", + plotter.default_styles["numerical"]..., + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + # 通用样式 + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +""" +多格式/多精度对比绘图 +""" +function plot_comparison(plotter::CFDPlotter, result_list::Vector{Dict{String, Any}}; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + schemes = [uppercase(r["config"]["scheme"]) * string(r["config"]["order"]) for r in result_list] + rk_order = result_list[1]["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = result_list[1]["config"]["final_time"] + actual_title = "1D Convection Comparison (t=$(final_time))\n$(join(schemes, ", ")) + $(rk_str)-order RK" + end + + # 绘制多个数值解 + for (i, res) in enumerate(result_list) + style = plotter.default_styles["comparison"][mod1(i, length(plotter.default_styles["comparison"]))] + label = "Numerical ($(uppercase(res["config"]["scheme"]))$(res["config"]["order"]))" + plt.plot( + res["x"], res["numerical"]; + label=label, + style..., + markersize=5, linewidth=0.5 + ) + end + + # 绘制解析解 + plt.plot( + result_list[1]["x"], result_list[1]["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +function _set_common_style(plotter::CFDPlotter, title::String) + plt.title(title, fontsize=12) + plt.xlabel("x", fontsize=10) + plt.ylabel("u", fontsize=10) + plt.legend(fontsize=9) + plt.grid(true, color="gray", linestyle="--", linewidth=0.5, alpha=0.7) + plt.tight_layout() +end + +""" +快捷函数:ENO/WENO对比绘图 +""" +function plot_eno_weno_comparison(eno_result::Dict, weno_result::Dict; save_path=nothing) + plotter = CFDPlotter() + plot_comparison(plotter, [eno_result, weno_result]; save_path=save_path) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02d/reconstructor/eno.jl b/example/1d-linear-convection/weno3/julia/02d/reconstructor/eno.jl new file mode 100644 index 000000000..e78a636f3 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02d/reconstructor/eno.jl @@ -0,0 +1,107 @@ +# julia/reconstructor/eno.jl +""" +ENO 重构器(与 reconstructor/eno.py 完全同构) +""" + +# ---------------------- ENO 系数初始化 ---------------------- +function _init_eno_coef!(spatial_order::Int, coef::Matrix{Float64}) + if spatial_order == 1 + coef[1, 1] = 1.0 + coef[2, 1] = 1.0 + elseif spatial_order == 2 + coef[1, 1:2] = [3.0/2.0, -1.0/2.0] + coef[2, 1:2] = [1.0/2.0, 1.0/2.0] + coef[3, 1:2] = [-1.0/2.0, 3.0/2.0] + elseif spatial_order == 3 + coef[1, 1:3] = [11.0/6.0, -7.0/6.0, 1.0/3.0] + coef[2, 1:3] = [1.0/3.0, 5.0/6.0, -1.0/6.0] + coef[3, 1:3] = [-1.0/6.0, 5.0/6.0, 1.0/3.0] + coef[4, 1:3] = [1.0/3.0, -7.0/6.0, 11.0/6.0] + elseif spatial_order == 4 + coef[1, 1:4] = [25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0] + coef[2, 1:4] = [1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0] + coef[3, 1:4] = [-1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0] + coef[4, 1:4] = [1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0] + coef[5, 1:4] = [-1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0] + elseif spatial_order == 5 + coef[1, 1:5] = [137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0] + coef[2, 1:5] = [1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0] + coef[3, 1:5] = [-1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0] + coef[4, 1:5] = [1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0] + coef[5, 1:5] = [-1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0] + coef[6, 1:5] = [1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0] + elseif spatial_order == 6 + coef[1, 1:6] = [49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0] + coef[2, 1:6] = [1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0] + coef[3, 1:6] = [-1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0] + coef[4, 1:6] = [1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0] + coef[5, 1:6] = [-1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0] + coef[6, 1:6] = [1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0] + coef[7, 1:6] = [-1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0] + elseif spatial_order == 7 + coef[1, 1:7] = [363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0] + coef[2, 1:7] = [1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0] + coef[3, 1:7] = [-1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0] + coef[4, 1:7] = [1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0] + coef[5, 1:7] = [-1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0] + coef[6, 1:7] = [1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0] + coef[7, 1:7] = [-1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0] + coef[8, 1:7] = [1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0] + else + error("ENO 系数未实现 order=$spatial_order") + end +end + +# ---------------------- ENO 重构器 ---------------------- +mutable struct EnoReconstructor + spatial_order::Int + ntcells::Int + lmc::Vector{Int} + coef::Matrix{Float64} + dd::Matrix{Float64} + + function EnoReconstructor(spatial_order::Int, ntcells::Int) + lmc = zeros(Int, ntcells) + coef = zeros(Float64, spatial_order + 1, spatial_order) + dd = zeros(Float64, spatial_order, ntcells) + _init_eno_coef!(spatial_order, coef) + new(spatial_order, ntcells, lmc, coef, dd) + end +end + +function reconstruct(rec::EnoReconstructor, q::Vector{Float64}, cfd::Any) + # 1. 差商计算 (dd[1,:] = q) + @views rec.dd[1, :] .= q + for m in 2:rec.spatial_order + for j in 1:(rec.ntcells - m + 1) + rec.dd[m, j] = rec.dd[m-1, j+1] - rec.dd[m-1, j] + end + end + + # 2. 选择 smoothest stencil + domain = cfd.domain + for i in (domain.ist - 1):(domain.ied) # Python: range(ist-1, ied+1) → ied+1-1 = ied + rec.lmc[i] = i + for m in 2:rec.spatial_order + if abs(rec.dd[m, rec.lmc[i] - 1]) < abs(rec.dd[m, rec.lmc[i]]) + rec.lmc[i] -= 1 + end + end + end + + # 3. 重构界面值 + solution = cfd.solution + for i in domain.ist:(domain.ied) # Python: range(ist, ied+1) → ied+1-1 = ied + j = i - domain.ist + 1 # Julia 1-based + k1 = rec.lmc[i - 1] + k2 = rec.lmc[i] + r1 = (i - 1) - k1 + 1 + r2 = i - k2 + 1 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in 1:rec.spatial_order + solution.q_face_left[j] += q[k1 + m - 1] * rec.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m - 1] * rec.coef[r2, m] + end + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02d/reconstructor/weno3.jl b/example/1d-linear-convection/weno3/julia/02d/reconstructor/weno3.jl new file mode 100644 index 000000000..2b6fe1abb --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02d/reconstructor/weno3.jl @@ -0,0 +1,65 @@ +# julia/reconstructor/weno3.jl +""" +WENO3 重构器(与 reconstructor/weno3.py 完全同构) +""" + +mutable struct Weno3Reconstructor + # 无字段,与 Python 一致 +end + +function reconstruct(rec::Weno3Reconstructor, q::Vector{Float64}, cfd::Any) + domain = cfd.domain + solution = cfd.solution + _reconstruct_left_interfaces(domain, q, solution.q_face_left) + _reconstruct_right_interfaces(domain, q, solution.q_face_right) +end + +function _reconstruct_left_interfaces(domain, u, qL) + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in (domain.ist - 1):(domain.ied - 1) + j = i - (domain.ist - 1) + 1 # ← Julia 1-based: j = i - (ist-1) 对应 Python j = i - (ist-1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = _reconstruct_from_left_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_right_interfaces(domain, u, qR) + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in domain.ist:domain.ied + j = i - domain.ist + 1 # ← Julia 1-based: j = i - ist 对应 Python j = i - ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = _reconstruct_from_right_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_from_left_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -0.5*v1 + 1.5*v2 # r=1 stencil + q1 = 0.5*v2 + 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end + +function _reconstruct_from_right_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 0.5*v1 + 0.5*v2 # r=1 stencil + q1 = 1.5*v2 - 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02d/residual.jl b/example/1d-linear-convection/weno3/julia/02d/residual.jl new file mode 100644 index 000000000..e8fd65843 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02d/residual.jl @@ -0,0 +1,65 @@ +# julia/residual.jl +""" +残差计算器(与 residual.py 完全同构) +- 封装重建→通量→散度完整流程 +- 依赖 cfd 的多个字段 +""" + +include("mesh.jl") + +mutable struct ResidualCalculator + cfd::Any + config::Any + domain::Any + solution::Any + mesh::Mesh + reconstructor::Any + flux_calculator::Any # 通量计算器(外部传入,替代工厂) + + function ResidualCalculator(cfd::Any, flux_calculator::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + mesh = domain.mesh + reconstructor = cfd.reconstructor + + new(cfd, config, domain, solution, mesh, reconstructor, flux_calculator) + end +end + +""" +计算完整残差(对外唯一接口) +""" +function compute!(calc::ResidualCalculator) + _reconstruct(calc) + _compute_inviscid_flux(calc) + _compute_flux_divergence(calc) +end + +""" +私有方法:界面值重建 +""" +function _reconstruct(calc::ResidualCalculator) + reconstruct(calc.reconstructor, calc.solution.u, calc.cfd) +end + +""" +私有方法:计算无粘通量 +""" +function _compute_inviscid_flux(calc::ResidualCalculator) + compute!(calc.flux_calculator, + calc.solution.q_face_left, + calc.solution.q_face_right, + calc.solution.flux) +end + +""" +私有方法:计算通量散度(残差 = -dF/dx) +""" +function _compute_flux_divergence(calc::ResidualCalculator) + solution = calc.solution + mesh = calc.mesh + for i in 1:mesh.ncells + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / mesh.dx + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02d/run_eno_weno.jl b/example/1d-linear-convection/weno3/julia/02d/run_eno_weno.jl new file mode 100644 index 000000000..58b6cfa5f --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02d/run_eno_weno.jl @@ -0,0 +1,50 @@ +# julia/run_eno_weno.jl +""" +1:1 复刻 run_eno_weno.py 的 Julia 版本 +""" + +include("config.jl") +include("mesh.jl") +include("solver.jl") +include("plotter.jl") + +function performEnoWenoAnalysis() + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO3 求解 + println("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + with_reconstruction(config_eno3, "eno", 3) # 显式指定 3 阶 + config_eno3.dt = 0.0025 # 覆盖默认值 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + run!(cfd_eno3) # 求解并生成 result 字典 + + # 3. 配置并运行 WENO3 求解 + println("Running WENO3 solver...") + config_weno3 = CfdConfig() + with_reconstruction(config_weno3, "weno", 3) # 显式指定 3 阶(WENO 默认 5 阶) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + run!(cfd_weno3) + + # 5. 绘制 ENO/WENO 对比图 + println("Plotting comparison results...") + plot_eno_weno_comparison( + cfd_eno3.result, + cfd_weno3.result; + save_path="eno_weno_comparison.png" + ) + + return cfd_eno3, cfd_weno3 +end + +# 主程序入口 +if abspath(PROGRAM_FILE) == @__FILE__ + performEnoWenoAnalysis() + println("Analysis completed!") +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02d/solution.jl b/example/1d-linear-convection/weno3/julia/02d/solution.jl new file mode 100644 index 000000000..90ca03931 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02d/solution.jl @@ -0,0 +1,75 @@ +# julia/solution.jl +""" +Solution 结构体(与真实 solution.py 完全同构) +字段顺序、方法、逻辑 1:1 对齐 +""" + +include("mesh.jl") +include("domain.jl") +include("initial_condition.jl") + +mutable struct Solution + domain::Domain + q_face_left::Vector{Float64} + q_face_right::Vector{Float64} + flux::Vector{Float64} + res::Vector{Float64} + u::Vector{Float64} + un::Vector{Float64} + + function Solution(config::Any, domain::Domain) + mesh = domain.mesh + + q_face_left = zeros(Float64, mesh.nnodes) + q_face_right = zeros(Float64, mesh.nnodes) + flux = zeros(Float64, mesh.nnodes) + res = zeros(Float64, mesh.ncells) + u = zeros(Float64, domain.ntcells) + un = zeros(Float64, domain.ntcells) + + sol = new(domain, q_face_left, q_face_right, flux, res, u, un) + initialize_from_config(sol, config) + return sol + end +end + +""" +重置解数组为初始状态 +""" +function reset_solution(sol::Solution) + fill!(sol.u, 0.0) + fill!(sol.un, 0.0) +end + +""" +根据配置初始化场 +注:暂用硬编码替代 InitialConditionFactory +""" +function initialize_from_config(sol::Solution, config::Any) + ic_type = getfield_safe(config, :ic_type, "step") + + # 硬编码创建 IC(替代 InitialConditionFactory) + ic = if ic_type == "step" + StepFunctionIC(config) + elseif ic_type == "sin" + SineWaveIC(config) + elseif ic_type == "gaussian" + GaussianPulseIC(config) + else + error("未知初始条件类型: $ic_type") + end + + apply(ic, sol) # 调用 IC.apply +end + +""" +更新旧场:un = u +""" +function update_old_field(sol::Solution) + sol.un .= sol.u # 等价于 Python 的 un[:] = u[:] +end + +# ---------------------- 辅助函数 ---------------------- +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02d/solver.jl b/example/1d-linear-convection/weno3/julia/02d/solver.jl new file mode 100644 index 000000000..ea015f6eb --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02d/solver.jl @@ -0,0 +1,167 @@ +# julia/solver.jl +""" +CFD 求解器主类(与 solver.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("initial_condition.jl") +include("boundary.jl") +include("flux.jl") +include("residual.jl") +include("time_integration.jl") +include("reconstructor/eno.jl") +include("reconstructor/weno3.jl") + +# 导入工厂模块(必须在顶层!) +using .FluxCalculatorFactory +using .TimeIntegratorFactory +using .BoundaryConditionFactory + +# ---------------------- Cfd 求解器 ---------------------- +mutable struct Cfd + config::Any + domain::Domain + solution::Solution + reconstructor::Any + residual_calculator::ResidualCalculator + integrator::Any + boundary_condition::Any + result::Dict{String, Any} + + function Cfd(config::Any, mesh::Mesh) + domain = Domain(config, mesh) + solution = Solution(config, domain) + + # 在 Cfd 构造函数中 + # =============== 重建器创建 =============== + recon_scheme = config.recon_scheme + spatial_order = config.spatial_order + + # ✅ 模拟 Python ReconstructorFactory 的逻辑 + if recon_scheme == "weno" + recon_scheme = "weno$(spatial_order)" + end + + # 现在根据 recon_scheme 创建 + reconstructor = if recon_scheme == "eno" + EnoReconstructor(spatial_order, domain.ntcells) + elseif recon_scheme == "weno3" + Weno3Reconstructor() + else + error("不支持的重建格式: $recon_scheme") + end + + # 构造 cfd 上下文(NamedTuple) + cfd_context = ( + config = config, + domain = domain + ) + + # 创建通量计算器 + flux_calculator = FluxCalculatorFactory.create(cfd_context) + + # 残差计算器 + residual_calculator = ResidualCalculator( + (config=config, domain=domain, solution=solution, reconstructor=reconstructor), + flux_calculator + ) + + # 构建边界条件上下文 + bc_context = (config = config, domain = domain) + + # 使用工厂创建边界条件 + boundary_condition = BoundaryConditionFactory.create(bc_context) + + # 构建时间推进器所需上下文 + integrator_context = ( + config = config, + domain = domain, + solution = solution, + residual_calculator = residual_calculator, + boundary_condition = boundary_condition + ) + + # 使用工厂创建时间推进器 + integrator = TimeIntegratorFactory.create(integrator_context) + #@show typeof(integrator) + + # 注入 cfd 到 residual_calculator 和 integrator + residual_calculator.cfd = (config=config, domain=domain, solution=solution, reconstructor=reconstructor, residual_calculator=residual_calculator, integrator=integrator, boundary_condition=boundary_condition) + integrator.base.cfd = residual_calculator.cfd + + result = Dict{String, Any}() + new(config, domain, solution, reconstructor, residual_calculator, integrator, boundary_condition, result) + end +end + +""" +通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界 +""" +function exact_solution(cfd::Cfd) + x = cfd.domain.mesh.xcc + T = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = @. (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = if cfd.config.ic_type == "step" + StepFunctionIC(cfd.config) + elseif cfd.config.ic_type == "sin" + SineWaveIC(cfd.config) + elseif cfd.config.ic_type == "gaussian" + GaussianPulseIC(cfd.config) + else + error("未知初始条件: $(cfd.config.ic_type)") + end + + return evaluate_at(ic, x_shifted) +end + +""" +主求解循环 +""" +function run!(cfd::Cfd) + # 应用初始边界条件并同步 old field + apply!(cfd.boundary_condition, cfd.solution.u) + update_old_field(cfd.solution) + + t = 0.0 + dt_old = cfd.config.dt + dt = dt_old + + while t < cfd.config.final_time + if t + dt > cfd.config.final_time + dt = cfd.config.final_time - t + end + #@show t, dt, maximum(cfd.solution.u), minimum(cfd.solution.u) + # 执行时间步 + step(cfd.integrator, dt) + t += dt + end + + # 恢复 dt + cfd.config.dt = dt_old + + # 整理结果 + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied-1] # Python: [ist:ied] + analytical = exact_solution(cfd) + + cfd.result = Dict( + "x" => cfd.domain.mesh.xcc, + "numerical" => u_numerical, + "analytical" => analytical, + "config" => Dict( + "scheme" => cfd.config.recon_scheme, + "order" => cfd.config.spatial_order, + "rk_order" => cfd.config.rk_order, + "final_time" => cfd.config.final_time + ) + ) + + return u_numerical +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02d/time_integration.jl b/example/1d-linear-convection/weno3/julia/02d/time_integration.jl new file mode 100644 index 000000000..add564ba5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02d/time_integration.jl @@ -0,0 +1,169 @@ +# julia/time_integration.jl +""" +时间推进器模块(与 time_integration.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("residual.jl") +include("boundary.jl") + +# ---------------------- 抽象时间推进器基类 ---------------------- +abstract type TimeIntegrator end + +mutable struct TimeIntegratorBase <: TimeIntegrator + cfd::Any + config::Any + domain::Domain + solution::Solution + residual_calculator::Any # ResidualCalculator +end + +function TimeIntegratorBase(cfd::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + residual_calculator = cfd.residual_calculator + TimeIntegratorBase(cfd, config, domain, solution, residual_calculator) +end + +function compute_residual(integrator::TimeIntegratorBase) + compute!(integrator.residual_calculator) +end + +function apply_boundary(integrator::TimeIntegratorBase) + apply!(integrator.cfd.boundary_condition, integrator.solution.u) +end + +function map_idx(integrator::TimeIntegratorBase, i::Int) + return i - integrator.domain.ist + 1 # ← +1 转为 1-based +end + +# ---------------------- RK1Integrator ---------------------- +mutable struct RK1Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK1Integrator(cfd::Any) + RK1Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK1Integrator, dt::Float64) + compute_residual(integrator.base) + for i in integrator.base.domain.ist:(integrator.base.domain.ied - 1) + j = map_idx(integrator.base, i) + integrator.base.solution.u[i] += dt * integrator.base.solution.res[j] + end + apply_boundary(integrator.base) + update_old_field(integrator.base.solution) +end + +# ---------------------- RK2Integrator ---------------------- +mutable struct RK2Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK2Integrator(cfd::Any) + RK2Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK2Integrator, dt::Float64) + base = integrator.base + # 阶段1:预测步 + compute_residual(base) + u_pred = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u_pred[i] += dt * base.solution.res[j] + end + base.solution.u .= u_pred + apply_boundary(base) + # 阶段2:校正步 + compute_residual(base) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = 0.5 * base.solution.un[i] + 0.5 * base.solution.u[i] + 0.5 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end + +# ---------------------- RK3Integrator ---------------------- +mutable struct RK3Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK3Integrator(cfd::Any) + RK3Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK3Integrator, dt::Float64) + base = integrator.base + # 阶段1 + compute_residual(base) + u1 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u1[i] += dt * base.solution.res[j] + end + base.solution.u .= u1 + apply_boundary(base) + # 阶段2 + compute_residual(base) + u2 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u2[i] = 0.75 * base.solution.un[i] + 0.25 * base.solution.u[i] + 0.25 * dt * base.solution.res[j] + end + base.solution.u .= u2 + apply_boundary(base) + # 阶段3 + compute_residual(base) + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = c1 * base.solution.un[i] + c2 * base.solution.u[i] + c3 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end + +# ---------------------- TimeIntegratorFactory ---------------------- +module TimeIntegratorFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "时间积分器 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + rk_order = cfd.config.rk_order + if !(rk_order isa Integer) || rk_order < 1 + error("rk_order 必须为正整数,当前值: $rk_order") + end + + name = "rk$rk_order" + if !haskey(_REGISTRY, name) + available = sort(collect(keys(_REGISTRY))) + error("未注册的时间积分器: '$name'。可用选项: $available") + end + + return _REGISTRY[name](cfd) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("rk1", cfd -> Main.RK1Integrator(cfd)) +register("rk2", cfd -> Main.RK2Integrator(cfd)) +register("rk3", cfd -> Main.RK3Integrator(cfd)) + +end # module TimeIntegratorFactory + +export TimeIntegratorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02e/boundary.jl b/example/1d-linear-convection/weno3/julia/02e/boundary.jl new file mode 100644 index 000000000..396c12642 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02e/boundary.jl @@ -0,0 +1,129 @@ +# julia/boundary.jl +# 目标:与 Python boundary.py 行为完全一致(1:1 同构) +# 前提:ist/ied 在 Julia 中按 1-based 设置(ist = nghosts + 1) + +using NPZ # 仅用于测试,实际模块可不依赖 + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象基类(Julia 风格:用函数接口) ---------------------- +# Julia 无 abstract class,用文档+约定 +# 所有子类型必须实现 apply!(bc, u) + +# ---------------------- PeriodicBoundary ---------------------- +mutable struct PeriodicBoundary + cfd::Any +end + +function apply!(self::PeriodicBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist # 1-based, e.g., 3 + ied = self.cfd.domain.ied # 1-based, e.g., 43 + + # 左 ghost: u[ist - 1 - ig] = u[ied - 1 - ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ied - 1 - ig] + end + # 右 ghost: u[ied + ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ied + ig] = u[ist + ig] + end +end + +# ---------------------- DirichletBoundary ---------------------- +mutable struct DirichletBoundary + cfd::Any +end + +function apply!(self::DirichletBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + left_value = getfield_safe(self.cfd.config, :left_boundary_value, 1.0) + right_value = getfield_safe(self.cfd.config, :right_boundary_value, 2.0) + + # 左边界 + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = left_value + end + # 右边界 + for ig in 0:(nghosts-1) + u[ied + ig] = right_value + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Dirichlet边界: 左值=$left_value, 右值=$right_value") + end +end + +# ---------------------- NeumannBoundary ---------------------- +mutable struct NeumannBoundary + cfd::Any +end + +function apply!(self::NeumannBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + # 左边界零梯度:u[ist - 1 - ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ist + ig] + end + # 右边界零梯度:u[ied + ig] = u[ied - 1 - ig] ← 注意是 ied - 1 - ig + for ig in 0:(nghosts-1) + u[ied + ig] = u[ied - 1 - ig] + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Neumann边界: 零梯度") + end +end + + +# ---------------------- BoundaryConditionFactory ---------------------- +module BoundaryConditionFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "边界条件 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + bc_type = cfd.config.boundary_type + if !(bc_type isa AbstractString) + error("boundary_type 必须为字符串,当前值: $bc_type") + end + + if !haskey(_REGISTRY, bc_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的边界条件: '$bc_type'。可用选项: $available") + end + + return _REGISTRY[bc_type](cfd) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("periodic", cfd -> Main.PeriodicBoundary(cfd)) +register("dirichlet", cfd -> Main.DirichletBoundary(cfd)) +register("neumann", cfd -> Main.NeumannBoundary(cfd)) + +end # module BoundaryConditionFactory + +export BoundaryConditionFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02e/config.jl b/example/1d-linear-convection/weno3/julia/02e/config.jl new file mode 100644 index 000000000..bf48de3a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02e/config.jl @@ -0,0 +1,68 @@ +# julia/config.jl +""" +CfdConfig:与 Python config.py 完全同构 +""" +mutable struct CfdConfig + ic_type::String + recon_scheme::String + flux_type::String + rk_order::Int + wave_speed::Float64 + final_time::Float64 + dt::Float64 + boundary_type::String + left_boundary_value::Float64 + right_boundary_value::Float64 + spatial_order::Int + + function CfdConfig() + new( + "step", + "eno", + "rusanov", + 1, + 1.0, + 0.625, + 0.025, + "periodic", + 1.0, + 2.0, + 2 + ) + end +end + +""" +专用配置:重建方案(链式调用) +""" +function with_reconstruction(cfg::CfdConfig, scheme::String, order::Union{Int, Nothing}=nothing) + cfg.recon_scheme = lowercase(scheme) + + if order !== nothing + cfg.spatial_order = order + else + if startswith(cfg.recon_scheme, "weno") + cfg.spatial_order = 5 + elseif cfg.recon_scheme == "eno" + cfg.spatial_order = 3 + else + error("不支持的重建格式:$scheme(仅支持 eno/weno)") + end + end + + return cfg # 支持链式调用 +end + +""" +专用配置:边界条件(链式调用) +""" +function with_boundary(cfg::CfdConfig, bc_type::String; left_value=nothing, right_value=nothing) + cfg.boundary_type = bc_type + if left_value !== nothing + cfg.left_boundary_value = left_value + end + if right_value !== nothing + cfg.right_boundary_value = right_value + end + return cfg +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02e/domain.jl b/example/1d-linear-convection/weno3/julia/02e/domain.jl new file mode 100644 index 000000000..a7edc2260 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02e/domain.jl @@ -0,0 +1,61 @@ +# julia/domain.jl +""" +Domain 结构体(与 domain.py 完全同构) +- 保存 config +- nghosts 计算逻辑 1:1 +- ist/ied 为 0-based(与 Python 一致) +""" + +include("mesh.jl") + +mutable struct Domain + config::Any # 保存 config(与 Python 一致) + mesh::Mesh + nghosts::Int + ist::Int # 0-based + ied::Int # 0-based + ntcells::Int +end + +function Domain(config::Any, mesh::Mesh) + nghosts = _calc_nghosts(config) + ist = nghosts + 1 # ← 1-based 起始索引 + ied = ist + mesh.ncells # ← 1-based 结束索引(不包含) + ntcells = mesh.ncells + 2 * nghosts + Domain(config, mesh, nghosts, ist, ied, ntcells) +end + +function _calc_nghosts(config::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + order = getfield_safe(config, :spatial_order, 2) + + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + if scheme == "eno" + nghosts = order + elseif startswith(scheme, "weno") + nghosts = order ÷ 2 + 1 + else + error("未知重建格式 $(scheme),无法计算ghost层!") + end + + if nghosts <= 0 + error("计算得到的ghost层数量无效:$(nghosts)(阶数$(order),格式$(scheme))") + end + + return nghosts +end + +function is_physical_cell(domain::Domain, idx::Int) + return domain.ist <= idx < domain.ied +end + +function get_physical_indices(domain::Domain) + return domain.ist:(domain.ied - 1) +end + +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02e/flux.jl b/example/1d-linear-convection/weno3/julia/02e/flux.jl new file mode 100644 index 000000000..cff235824 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02e/flux.jl @@ -0,0 +1,112 @@ +# julia/flux.jl +""" +通量计算器模块(与 flux.py 完全同构) +- 抽象基类 + 具体实现 +- 字段:cfd, config, mesh, wave_speed +""" + +include("mesh.jl") + +# ---------------------- 抽象基类 ---------------------- +""" +InviscidFluxCalculator 抽象类型 +Julia 无 ABC,用文档约定 +所有子类型必须实现 compute! +""" +abstract type InviscidFluxCalculator end + +# ---------------------- RusanovFluxCalculator ---------------------- +mutable struct RusanovFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function RusanovFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::RusanovFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = calc.wave_speed + c_R = calc.wave_speed + F_L = c_L * u_L + F_R = c_R * u_R + Smax = max(abs(c_L), abs(c_R)) + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + end +end + +# ---------------------- EngquistOsherFluxCalculator ---------------------- +mutable struct EngquistOsherFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function EngquistOsherFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::EngquistOsherFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + c = calc.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + end +end + +# ---------------------- FluxCalculatorFactory ---------------------- +module FluxCalculatorFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "通量计算器 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + config = cfd.config + if !hasproperty(config, :flux_type) + error("cfd.config 缺少 flux_type 字段") + end + + flux_type = config.flux_type + if !(flux_type isa AbstractString) + error("flux_type 必须为字符串,当前值: $flux_type") + end + + if !haskey(_REGISTRY, flux_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的通量计算器: '$flux_type'。可用选项: $available") + end + + return _REGISTRY[flux_type](cfd) +end + +# ✅ 修正:使用 Main. 前缀 +register("rusanov", cfd -> Main.RusanovFluxCalculator(cfd)) +register("engquist-osher", cfd -> Main.EngquistOsherFluxCalculator(cfd)) + +end # module FluxCalculatorFactory + +export FluxCalculatorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02e/initial_condition.jl b/example/1d-linear-convection/weno3/julia/02e/initial_condition.jl new file mode 100644 index 000000000..aa120160a --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02e/initial_condition.jl @@ -0,0 +1,120 @@ +# julia/initial_condition.jl +""" +初始条件模块(Julia 版) +目标:与 Python initial_condition.py 行为完全一致 +""" + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象接口(Julia 无继承,用函数约定) ---------------------- +# 所有初始条件必须实现: +# evaluate_at(ic, x::AbstractVector{Float64}) -> Vector{Float64} +# apply(ic, solution) + +# ---------------------- StepFunctionIC ---------------------- +struct StepFunctionIC + config::Any +end + +function evaluate_at(ic::StepFunctionIC, x::AbstractVector{Float64}) + u0 = ones(Float64, length(x)) + for i in eachindex(x) + if 0.5 <= x[i] <= 1.0 + u0[i] = 2.0 + end + end + return u0 +end + +function apply(ic::StepFunctionIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- SineWaveIC ---------------------- +struct SineWaveIC + config::Any +end + +function evaluate_at(ic::SineWaveIC, x::AbstractVector{Float64}) + L = getfield_safe(ic.config, :domain_length, 2.0) + return sin.(2π * x / L) +end + +function apply(ic::SineWaveIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- GaussianPulseIC ---------------------- +struct GaussianPulseIC + config::Any +end + +function evaluate_at(ic::GaussianPulseIC, x::AbstractVector{Float64}) + center = getfield_safe(ic.config, :pulse_center, 0.5) + width = getfield_safe(ic.config, :pulse_width, 0.1) + return exp.(-((x .- center) ./ width).^2) +end + +function apply(ic::GaussianPulseIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- 公共辅助函数 ---------------------- +""" +将初始场 values 应用到 solution.u 的物理区域(含 ghost 的数组) +""" +function _apply_to_interior(solution, values) + domain = solution.domain + # Python: for i in range(domain.ist, domain.ied) + # Julia: for i in domain.ist:domain.ied-1 (因为 ied 是结束索引,不包含) + for i in domain.ist:(domain.ied - 1) + j = i - domain.ist + 1 # values 是 1-based,i - ist 是 0-based → +1 + solution.u[i] = values[j] + end +end + +# ---------------------- InitialConditionFactory ---------------------- +module InitialConditionFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "初始条件 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(ic_type::String, config::Any) + if !(ic_type isa AbstractString) + error("ic_type 必须为字符串,当前值: $ic_type") + end + + if !haskey(_REGISTRY, ic_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的初始条件: '$ic_type'。可用选项: $available") + end + + return _REGISTRY[ic_type](config) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("step", config -> Main.StepFunctionIC(config)) +register("sin", config -> Main.SineWaveIC(config)) +register("gaussian", config -> Main.GaussianPulseIC(config)) + +end # module InitialConditionFactory + +export InitialConditionFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02e/mesh.jl b/example/1d-linear-convection/weno3/julia/02e/mesh.jl new file mode 100644 index 000000000..1b0dd4bf9 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02e/mesh.jl @@ -0,0 +1,45 @@ +# julia/mesh.jl +""" +Mesh 结构体(与提供的 mesh.py 完全同构) +- 字段名、初始化顺序、循环逻辑完全一致 +- 不做任何泛化或优化 +""" + +mutable struct Mesh + xmin::Float64 + xmax::Float64 + ncells::Int + nnodes::Int + nx::Int + x::Vector{Float64} + xcc::Vector{Float64} + L::Float64 # 在 init_mesh 中赋值 + dx::Float64 # 在 init_mesh 中赋值 + + function Mesh() + # 与 Python __init__ 完全一致 + xmin = 0.0 + xmax = 2.0 + ncells = 40 + nnodes = ncells + 1 + nx = ncells + x = zeros(Float64, nnodes) + xcc = zeros(Float64, ncells) + + # 调用 init_mesh(模拟 Python) + L = xmax - xmin + dx = L / ncells + + # Generate node coordinates: for i in range(self.nnodes) + for i in 1:nnodes + x[i] = xmin + (i - 1) * dx # Python i → Julia i-1 + end + + # Generate cell center coordinates + for i in 1:ncells + xcc[i] = 0.5 * (x[i] + x[i+1]) + end + + new(xmin, xmax, ncells, nnodes, nx, x, xcc, L, dx) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02e/plotter.jl b/example/1d-linear-convection/weno3/julia/02e/plotter.jl new file mode 100644 index 000000000..a77d8d688 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02e/plotter.jl @@ -0,0 +1,147 @@ +# julia/plotter.jl +""" +CFDPlotter 的 Julia 实现(通过 PythonCall.jl 调用 Matplotlib) +确保与 Python plotter.py 行为完全一致 +""" + +using PythonCall + +# 初始化 Python 环境(加载 matplotlib, inflect) +const plt = pyimport("matplotlib.pyplot") +const inflect = pyimport("inflect") + +mutable struct CFDPlotter + default_styles::Dict{String, Any} + p::Py +end + +function CFDPlotter() + default_styles = Dict{String, Any}( + "numerical" => Dict( + :color => "blue", + :linestyle => "-", + :marker => "o", + :markerfacecolor => "none" + ), + "analytical" => Dict( + :color => "red", + :linestyle => "--", + :marker => "", + :linewidth => 1.5 + ), + "comparison" => [ + Dict(:color => "black", :linestyle => "-", :marker => "o", :markerfacecolor => "none"), + Dict(:color => "blue", :linestyle => "--", :marker => "s", :markerfacecolor => "none"), + Dict(:color => "green", :linestyle => ":", :marker => "^", :markerfacecolor => "none") + ] + ) + p = inflect.engine() + CFDPlotter(default_styles, p) +end + +""" +轻量即时绘图(快速验证结果) +""" +function plot_quick(plotter::CFDPlotter, cfd_result::Dict; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + rk_order = cfd_result["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = cfd_result["config"]["final_time"] + order = cfd_result["config"]["order"] + scheme = uppercase(cfd_result["config"]["scheme"]) + actual_title = "1D Convection (t=$(final_time))\n$(order)th-order $(scheme) + $(rk_str)-order RK" + end + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"]; + label="Numerical ($(uppercase(cfd_result["config"]["scheme"])))", + plotter.default_styles["numerical"]..., + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + # 通用样式 + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +""" +多格式/多精度对比绘图 +""" +function plot_comparison(plotter::CFDPlotter, result_list::Vector{Dict{String, Any}}; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + schemes = [uppercase(r["config"]["scheme"]) * string(r["config"]["order"]) for r in result_list] + rk_order = result_list[1]["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = result_list[1]["config"]["final_time"] + actual_title = "1D Convection Comparison (t=$(final_time))\n$(join(schemes, ", ")) + $(rk_str)-order RK" + end + + # 绘制多个数值解 + for (i, res) in enumerate(result_list) + style = plotter.default_styles["comparison"][mod1(i, length(plotter.default_styles["comparison"]))] + label = "Numerical ($(uppercase(res["config"]["scheme"]))$(res["config"]["order"]))" + plt.plot( + res["x"], res["numerical"]; + label=label, + style..., + markersize=5, linewidth=0.5 + ) + end + + # 绘制解析解 + plt.plot( + result_list[1]["x"], result_list[1]["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +function _set_common_style(plotter::CFDPlotter, title::String) + plt.title(title, fontsize=12) + plt.xlabel("x", fontsize=10) + plt.ylabel("u", fontsize=10) + plt.legend(fontsize=9) + plt.grid(true, color="gray", linestyle="--", linewidth=0.5, alpha=0.7) + plt.tight_layout() +end + +""" +快捷函数:ENO/WENO对比绘图 +""" +function plot_eno_weno_comparison(eno_result::Dict, weno_result::Dict; save_path=nothing) + plotter = CFDPlotter() + plot_comparison(plotter, [eno_result, weno_result]; save_path=save_path) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02e/reconstructor/eno.jl b/example/1d-linear-convection/weno3/julia/02e/reconstructor/eno.jl new file mode 100644 index 000000000..e78a636f3 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02e/reconstructor/eno.jl @@ -0,0 +1,107 @@ +# julia/reconstructor/eno.jl +""" +ENO 重构器(与 reconstructor/eno.py 完全同构) +""" + +# ---------------------- ENO 系数初始化 ---------------------- +function _init_eno_coef!(spatial_order::Int, coef::Matrix{Float64}) + if spatial_order == 1 + coef[1, 1] = 1.0 + coef[2, 1] = 1.0 + elseif spatial_order == 2 + coef[1, 1:2] = [3.0/2.0, -1.0/2.0] + coef[2, 1:2] = [1.0/2.0, 1.0/2.0] + coef[3, 1:2] = [-1.0/2.0, 3.0/2.0] + elseif spatial_order == 3 + coef[1, 1:3] = [11.0/6.0, -7.0/6.0, 1.0/3.0] + coef[2, 1:3] = [1.0/3.0, 5.0/6.0, -1.0/6.0] + coef[3, 1:3] = [-1.0/6.0, 5.0/6.0, 1.0/3.0] + coef[4, 1:3] = [1.0/3.0, -7.0/6.0, 11.0/6.0] + elseif spatial_order == 4 + coef[1, 1:4] = [25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0] + coef[2, 1:4] = [1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0] + coef[3, 1:4] = [-1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0] + coef[4, 1:4] = [1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0] + coef[5, 1:4] = [-1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0] + elseif spatial_order == 5 + coef[1, 1:5] = [137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0] + coef[2, 1:5] = [1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0] + coef[3, 1:5] = [-1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0] + coef[4, 1:5] = [1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0] + coef[5, 1:5] = [-1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0] + coef[6, 1:5] = [1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0] + elseif spatial_order == 6 + coef[1, 1:6] = [49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0] + coef[2, 1:6] = [1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0] + coef[3, 1:6] = [-1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0] + coef[4, 1:6] = [1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0] + coef[5, 1:6] = [-1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0] + coef[6, 1:6] = [1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0] + coef[7, 1:6] = [-1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0] + elseif spatial_order == 7 + coef[1, 1:7] = [363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0] + coef[2, 1:7] = [1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0] + coef[3, 1:7] = [-1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0] + coef[4, 1:7] = [1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0] + coef[5, 1:7] = [-1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0] + coef[6, 1:7] = [1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0] + coef[7, 1:7] = [-1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0] + coef[8, 1:7] = [1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0] + else + error("ENO 系数未实现 order=$spatial_order") + end +end + +# ---------------------- ENO 重构器 ---------------------- +mutable struct EnoReconstructor + spatial_order::Int + ntcells::Int + lmc::Vector{Int} + coef::Matrix{Float64} + dd::Matrix{Float64} + + function EnoReconstructor(spatial_order::Int, ntcells::Int) + lmc = zeros(Int, ntcells) + coef = zeros(Float64, spatial_order + 1, spatial_order) + dd = zeros(Float64, spatial_order, ntcells) + _init_eno_coef!(spatial_order, coef) + new(spatial_order, ntcells, lmc, coef, dd) + end +end + +function reconstruct(rec::EnoReconstructor, q::Vector{Float64}, cfd::Any) + # 1. 差商计算 (dd[1,:] = q) + @views rec.dd[1, :] .= q + for m in 2:rec.spatial_order + for j in 1:(rec.ntcells - m + 1) + rec.dd[m, j] = rec.dd[m-1, j+1] - rec.dd[m-1, j] + end + end + + # 2. 选择 smoothest stencil + domain = cfd.domain + for i in (domain.ist - 1):(domain.ied) # Python: range(ist-1, ied+1) → ied+1-1 = ied + rec.lmc[i] = i + for m in 2:rec.spatial_order + if abs(rec.dd[m, rec.lmc[i] - 1]) < abs(rec.dd[m, rec.lmc[i]]) + rec.lmc[i] -= 1 + end + end + end + + # 3. 重构界面值 + solution = cfd.solution + for i in domain.ist:(domain.ied) # Python: range(ist, ied+1) → ied+1-1 = ied + j = i - domain.ist + 1 # Julia 1-based + k1 = rec.lmc[i - 1] + k2 = rec.lmc[i] + r1 = (i - 1) - k1 + 1 + r2 = i - k2 + 1 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in 1:rec.spatial_order + solution.q_face_left[j] += q[k1 + m - 1] * rec.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m - 1] * rec.coef[r2, m] + end + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02e/reconstructor/weno3.jl b/example/1d-linear-convection/weno3/julia/02e/reconstructor/weno3.jl new file mode 100644 index 000000000..2b6fe1abb --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02e/reconstructor/weno3.jl @@ -0,0 +1,65 @@ +# julia/reconstructor/weno3.jl +""" +WENO3 重构器(与 reconstructor/weno3.py 完全同构) +""" + +mutable struct Weno3Reconstructor + # 无字段,与 Python 一致 +end + +function reconstruct(rec::Weno3Reconstructor, q::Vector{Float64}, cfd::Any) + domain = cfd.domain + solution = cfd.solution + _reconstruct_left_interfaces(domain, q, solution.q_face_left) + _reconstruct_right_interfaces(domain, q, solution.q_face_right) +end + +function _reconstruct_left_interfaces(domain, u, qL) + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in (domain.ist - 1):(domain.ied - 1) + j = i - (domain.ist - 1) + 1 # ← Julia 1-based: j = i - (ist-1) 对应 Python j = i - (ist-1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = _reconstruct_from_left_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_right_interfaces(domain, u, qR) + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in domain.ist:domain.ied + j = i - domain.ist + 1 # ← Julia 1-based: j = i - ist 对应 Python j = i - ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = _reconstruct_from_right_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_from_left_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -0.5*v1 + 1.5*v2 # r=1 stencil + q1 = 0.5*v2 + 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end + +function _reconstruct_from_right_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 0.5*v1 + 0.5*v2 # r=1 stencil + q1 = 1.5*v2 - 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02e/residual.jl b/example/1d-linear-convection/weno3/julia/02e/residual.jl new file mode 100644 index 000000000..e8fd65843 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02e/residual.jl @@ -0,0 +1,65 @@ +# julia/residual.jl +""" +残差计算器(与 residual.py 完全同构) +- 封装重建→通量→散度完整流程 +- 依赖 cfd 的多个字段 +""" + +include("mesh.jl") + +mutable struct ResidualCalculator + cfd::Any + config::Any + domain::Any + solution::Any + mesh::Mesh + reconstructor::Any + flux_calculator::Any # 通量计算器(外部传入,替代工厂) + + function ResidualCalculator(cfd::Any, flux_calculator::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + mesh = domain.mesh + reconstructor = cfd.reconstructor + + new(cfd, config, domain, solution, mesh, reconstructor, flux_calculator) + end +end + +""" +计算完整残差(对外唯一接口) +""" +function compute!(calc::ResidualCalculator) + _reconstruct(calc) + _compute_inviscid_flux(calc) + _compute_flux_divergence(calc) +end + +""" +私有方法:界面值重建 +""" +function _reconstruct(calc::ResidualCalculator) + reconstruct(calc.reconstructor, calc.solution.u, calc.cfd) +end + +""" +私有方法:计算无粘通量 +""" +function _compute_inviscid_flux(calc::ResidualCalculator) + compute!(calc.flux_calculator, + calc.solution.q_face_left, + calc.solution.q_face_right, + calc.solution.flux) +end + +""" +私有方法:计算通量散度(残差 = -dF/dx) +""" +function _compute_flux_divergence(calc::ResidualCalculator) + solution = calc.solution + mesh = calc.mesh + for i in 1:mesh.ncells + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / mesh.dx + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02e/run_eno_weno.jl b/example/1d-linear-convection/weno3/julia/02e/run_eno_weno.jl new file mode 100644 index 000000000..58b6cfa5f --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02e/run_eno_weno.jl @@ -0,0 +1,50 @@ +# julia/run_eno_weno.jl +""" +1:1 复刻 run_eno_weno.py 的 Julia 版本 +""" + +include("config.jl") +include("mesh.jl") +include("solver.jl") +include("plotter.jl") + +function performEnoWenoAnalysis() + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO3 求解 + println("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + with_reconstruction(config_eno3, "eno", 3) # 显式指定 3 阶 + config_eno3.dt = 0.0025 # 覆盖默认值 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + run!(cfd_eno3) # 求解并生成 result 字典 + + # 3. 配置并运行 WENO3 求解 + println("Running WENO3 solver...") + config_weno3 = CfdConfig() + with_reconstruction(config_weno3, "weno", 3) # 显式指定 3 阶(WENO 默认 5 阶) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + run!(cfd_weno3) + + # 5. 绘制 ENO/WENO 对比图 + println("Plotting comparison results...") + plot_eno_weno_comparison( + cfd_eno3.result, + cfd_weno3.result; + save_path="eno_weno_comparison.png" + ) + + return cfd_eno3, cfd_weno3 +end + +# 主程序入口 +if abspath(PROGRAM_FILE) == @__FILE__ + performEnoWenoAnalysis() + println("Analysis completed!") +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02e/solution.jl b/example/1d-linear-convection/weno3/julia/02e/solution.jl new file mode 100644 index 000000000..d1f24e687 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02e/solution.jl @@ -0,0 +1,64 @@ +# julia/solution.jl +""" +Solution 结构体(与真实 solution.py 完全同构) +字段顺序、方法、逻辑 1:1 对齐 +""" + +include("mesh.jl") +include("domain.jl") +include("initial_condition.jl") + +mutable struct Solution + domain::Domain + q_face_left::Vector{Float64} + q_face_right::Vector{Float64} + flux::Vector{Float64} + res::Vector{Float64} + u::Vector{Float64} + un::Vector{Float64} + + function Solution(config::Any, domain::Domain) + mesh = domain.mesh + + q_face_left = zeros(Float64, mesh.nnodes) + q_face_right = zeros(Float64, mesh.nnodes) + flux = zeros(Float64, mesh.nnodes) + res = zeros(Float64, mesh.ncells) + u = zeros(Float64, domain.ntcells) + un = zeros(Float64, domain.ntcells) + + sol = new(domain, q_face_left, q_face_right, flux, res, u, un) + initialize_from_config(sol, config) + return sol + end +end + +""" +重置解数组为初始状态 +""" +function reset_solution(sol::Solution) + fill!(sol.u, 0.0) + fill!(sol.un, 0.0) +end + +""" +根据配置初始化场 +""" + +function initialize_from_config(sol::Solution, config::Any) + ic_type = getfield_safe(config, :ic_type, "step") + ic = InitialConditionFactory.create(ic_type, config) + apply(ic, sol) +end + +""" +更新旧场:un = u +""" +function update_old_field(sol::Solution) + sol.un .= sol.u # 等价于 Python 的 un[:] = u[:] +end + +# ---------------------- 辅助函数 ---------------------- +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02e/solver.jl b/example/1d-linear-convection/weno3/julia/02e/solver.jl new file mode 100644 index 000000000..ea015f6eb --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02e/solver.jl @@ -0,0 +1,167 @@ +# julia/solver.jl +""" +CFD 求解器主类(与 solver.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("initial_condition.jl") +include("boundary.jl") +include("flux.jl") +include("residual.jl") +include("time_integration.jl") +include("reconstructor/eno.jl") +include("reconstructor/weno3.jl") + +# 导入工厂模块(必须在顶层!) +using .FluxCalculatorFactory +using .TimeIntegratorFactory +using .BoundaryConditionFactory + +# ---------------------- Cfd 求解器 ---------------------- +mutable struct Cfd + config::Any + domain::Domain + solution::Solution + reconstructor::Any + residual_calculator::ResidualCalculator + integrator::Any + boundary_condition::Any + result::Dict{String, Any} + + function Cfd(config::Any, mesh::Mesh) + domain = Domain(config, mesh) + solution = Solution(config, domain) + + # 在 Cfd 构造函数中 + # =============== 重建器创建 =============== + recon_scheme = config.recon_scheme + spatial_order = config.spatial_order + + # ✅ 模拟 Python ReconstructorFactory 的逻辑 + if recon_scheme == "weno" + recon_scheme = "weno$(spatial_order)" + end + + # 现在根据 recon_scheme 创建 + reconstructor = if recon_scheme == "eno" + EnoReconstructor(spatial_order, domain.ntcells) + elseif recon_scheme == "weno3" + Weno3Reconstructor() + else + error("不支持的重建格式: $recon_scheme") + end + + # 构造 cfd 上下文(NamedTuple) + cfd_context = ( + config = config, + domain = domain + ) + + # 创建通量计算器 + flux_calculator = FluxCalculatorFactory.create(cfd_context) + + # 残差计算器 + residual_calculator = ResidualCalculator( + (config=config, domain=domain, solution=solution, reconstructor=reconstructor), + flux_calculator + ) + + # 构建边界条件上下文 + bc_context = (config = config, domain = domain) + + # 使用工厂创建边界条件 + boundary_condition = BoundaryConditionFactory.create(bc_context) + + # 构建时间推进器所需上下文 + integrator_context = ( + config = config, + domain = domain, + solution = solution, + residual_calculator = residual_calculator, + boundary_condition = boundary_condition + ) + + # 使用工厂创建时间推进器 + integrator = TimeIntegratorFactory.create(integrator_context) + #@show typeof(integrator) + + # 注入 cfd 到 residual_calculator 和 integrator + residual_calculator.cfd = (config=config, domain=domain, solution=solution, reconstructor=reconstructor, residual_calculator=residual_calculator, integrator=integrator, boundary_condition=boundary_condition) + integrator.base.cfd = residual_calculator.cfd + + result = Dict{String, Any}() + new(config, domain, solution, reconstructor, residual_calculator, integrator, boundary_condition, result) + end +end + +""" +通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界 +""" +function exact_solution(cfd::Cfd) + x = cfd.domain.mesh.xcc + T = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = @. (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = if cfd.config.ic_type == "step" + StepFunctionIC(cfd.config) + elseif cfd.config.ic_type == "sin" + SineWaveIC(cfd.config) + elseif cfd.config.ic_type == "gaussian" + GaussianPulseIC(cfd.config) + else + error("未知初始条件: $(cfd.config.ic_type)") + end + + return evaluate_at(ic, x_shifted) +end + +""" +主求解循环 +""" +function run!(cfd::Cfd) + # 应用初始边界条件并同步 old field + apply!(cfd.boundary_condition, cfd.solution.u) + update_old_field(cfd.solution) + + t = 0.0 + dt_old = cfd.config.dt + dt = dt_old + + while t < cfd.config.final_time + if t + dt > cfd.config.final_time + dt = cfd.config.final_time - t + end + #@show t, dt, maximum(cfd.solution.u), minimum(cfd.solution.u) + # 执行时间步 + step(cfd.integrator, dt) + t += dt + end + + # 恢复 dt + cfd.config.dt = dt_old + + # 整理结果 + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied-1] # Python: [ist:ied] + analytical = exact_solution(cfd) + + cfd.result = Dict( + "x" => cfd.domain.mesh.xcc, + "numerical" => u_numerical, + "analytical" => analytical, + "config" => Dict( + "scheme" => cfd.config.recon_scheme, + "order" => cfd.config.spatial_order, + "rk_order" => cfd.config.rk_order, + "final_time" => cfd.config.final_time + ) + ) + + return u_numerical +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02e/time_integration.jl b/example/1d-linear-convection/weno3/julia/02e/time_integration.jl new file mode 100644 index 000000000..add564ba5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02e/time_integration.jl @@ -0,0 +1,169 @@ +# julia/time_integration.jl +""" +时间推进器模块(与 time_integration.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("residual.jl") +include("boundary.jl") + +# ---------------------- 抽象时间推进器基类 ---------------------- +abstract type TimeIntegrator end + +mutable struct TimeIntegratorBase <: TimeIntegrator + cfd::Any + config::Any + domain::Domain + solution::Solution + residual_calculator::Any # ResidualCalculator +end + +function TimeIntegratorBase(cfd::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + residual_calculator = cfd.residual_calculator + TimeIntegratorBase(cfd, config, domain, solution, residual_calculator) +end + +function compute_residual(integrator::TimeIntegratorBase) + compute!(integrator.residual_calculator) +end + +function apply_boundary(integrator::TimeIntegratorBase) + apply!(integrator.cfd.boundary_condition, integrator.solution.u) +end + +function map_idx(integrator::TimeIntegratorBase, i::Int) + return i - integrator.domain.ist + 1 # ← +1 转为 1-based +end + +# ---------------------- RK1Integrator ---------------------- +mutable struct RK1Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK1Integrator(cfd::Any) + RK1Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK1Integrator, dt::Float64) + compute_residual(integrator.base) + for i in integrator.base.domain.ist:(integrator.base.domain.ied - 1) + j = map_idx(integrator.base, i) + integrator.base.solution.u[i] += dt * integrator.base.solution.res[j] + end + apply_boundary(integrator.base) + update_old_field(integrator.base.solution) +end + +# ---------------------- RK2Integrator ---------------------- +mutable struct RK2Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK2Integrator(cfd::Any) + RK2Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK2Integrator, dt::Float64) + base = integrator.base + # 阶段1:预测步 + compute_residual(base) + u_pred = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u_pred[i] += dt * base.solution.res[j] + end + base.solution.u .= u_pred + apply_boundary(base) + # 阶段2:校正步 + compute_residual(base) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = 0.5 * base.solution.un[i] + 0.5 * base.solution.u[i] + 0.5 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end + +# ---------------------- RK3Integrator ---------------------- +mutable struct RK3Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK3Integrator(cfd::Any) + RK3Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK3Integrator, dt::Float64) + base = integrator.base + # 阶段1 + compute_residual(base) + u1 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u1[i] += dt * base.solution.res[j] + end + base.solution.u .= u1 + apply_boundary(base) + # 阶段2 + compute_residual(base) + u2 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u2[i] = 0.75 * base.solution.un[i] + 0.25 * base.solution.u[i] + 0.25 * dt * base.solution.res[j] + end + base.solution.u .= u2 + apply_boundary(base) + # 阶段3 + compute_residual(base) + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = c1 * base.solution.un[i] + c2 * base.solution.u[i] + c3 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end + +# ---------------------- TimeIntegratorFactory ---------------------- +module TimeIntegratorFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "时间积分器 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + rk_order = cfd.config.rk_order + if !(rk_order isa Integer) || rk_order < 1 + error("rk_order 必须为正整数,当前值: $rk_order") + end + + name = "rk$rk_order" + if !haskey(_REGISTRY, name) + available = sort(collect(keys(_REGISTRY))) + error("未注册的时间积分器: '$name'。可用选项: $available") + end + + return _REGISTRY[name](cfd) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("rk1", cfd -> Main.RK1Integrator(cfd)) +register("rk2", cfd -> Main.RK2Integrator(cfd)) +register("rk3", cfd -> Main.RK3Integrator(cfd)) + +end # module TimeIntegratorFactory + +export TimeIntegratorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02f/boundary.jl b/example/1d-linear-convection/weno3/julia/02f/boundary.jl new file mode 100644 index 000000000..396c12642 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02f/boundary.jl @@ -0,0 +1,129 @@ +# julia/boundary.jl +# 目标:与 Python boundary.py 行为完全一致(1:1 同构) +# 前提:ist/ied 在 Julia 中按 1-based 设置(ist = nghosts + 1) + +using NPZ # 仅用于测试,实际模块可不依赖 + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象基类(Julia 风格:用函数接口) ---------------------- +# Julia 无 abstract class,用文档+约定 +# 所有子类型必须实现 apply!(bc, u) + +# ---------------------- PeriodicBoundary ---------------------- +mutable struct PeriodicBoundary + cfd::Any +end + +function apply!(self::PeriodicBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist # 1-based, e.g., 3 + ied = self.cfd.domain.ied # 1-based, e.g., 43 + + # 左 ghost: u[ist - 1 - ig] = u[ied - 1 - ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ied - 1 - ig] + end + # 右 ghost: u[ied + ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ied + ig] = u[ist + ig] + end +end + +# ---------------------- DirichletBoundary ---------------------- +mutable struct DirichletBoundary + cfd::Any +end + +function apply!(self::DirichletBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + left_value = getfield_safe(self.cfd.config, :left_boundary_value, 1.0) + right_value = getfield_safe(self.cfd.config, :right_boundary_value, 2.0) + + # 左边界 + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = left_value + end + # 右边界 + for ig in 0:(nghosts-1) + u[ied + ig] = right_value + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Dirichlet边界: 左值=$left_value, 右值=$right_value") + end +end + +# ---------------------- NeumannBoundary ---------------------- +mutable struct NeumannBoundary + cfd::Any +end + +function apply!(self::NeumannBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + # 左边界零梯度:u[ist - 1 - ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ist + ig] + end + # 右边界零梯度:u[ied + ig] = u[ied - 1 - ig] ← 注意是 ied - 1 - ig + for ig in 0:(nghosts-1) + u[ied + ig] = u[ied - 1 - ig] + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Neumann边界: 零梯度") + end +end + + +# ---------------------- BoundaryConditionFactory ---------------------- +module BoundaryConditionFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "边界条件 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + bc_type = cfd.config.boundary_type + if !(bc_type isa AbstractString) + error("boundary_type 必须为字符串,当前值: $bc_type") + end + + if !haskey(_REGISTRY, bc_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的边界条件: '$bc_type'。可用选项: $available") + end + + return _REGISTRY[bc_type](cfd) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("periodic", cfd -> Main.PeriodicBoundary(cfd)) +register("dirichlet", cfd -> Main.DirichletBoundary(cfd)) +register("neumann", cfd -> Main.NeumannBoundary(cfd)) + +end # module BoundaryConditionFactory + +export BoundaryConditionFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02f/config.jl b/example/1d-linear-convection/weno3/julia/02f/config.jl new file mode 100644 index 000000000..bf48de3a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02f/config.jl @@ -0,0 +1,68 @@ +# julia/config.jl +""" +CfdConfig:与 Python config.py 完全同构 +""" +mutable struct CfdConfig + ic_type::String + recon_scheme::String + flux_type::String + rk_order::Int + wave_speed::Float64 + final_time::Float64 + dt::Float64 + boundary_type::String + left_boundary_value::Float64 + right_boundary_value::Float64 + spatial_order::Int + + function CfdConfig() + new( + "step", + "eno", + "rusanov", + 1, + 1.0, + 0.625, + 0.025, + "periodic", + 1.0, + 2.0, + 2 + ) + end +end + +""" +专用配置:重建方案(链式调用) +""" +function with_reconstruction(cfg::CfdConfig, scheme::String, order::Union{Int, Nothing}=nothing) + cfg.recon_scheme = lowercase(scheme) + + if order !== nothing + cfg.spatial_order = order + else + if startswith(cfg.recon_scheme, "weno") + cfg.spatial_order = 5 + elseif cfg.recon_scheme == "eno" + cfg.spatial_order = 3 + else + error("不支持的重建格式:$scheme(仅支持 eno/weno)") + end + end + + return cfg # 支持链式调用 +end + +""" +专用配置:边界条件(链式调用) +""" +function with_boundary(cfg::CfdConfig, bc_type::String; left_value=nothing, right_value=nothing) + cfg.boundary_type = bc_type + if left_value !== nothing + cfg.left_boundary_value = left_value + end + if right_value !== nothing + cfg.right_boundary_value = right_value + end + return cfg +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02f/domain.jl b/example/1d-linear-convection/weno3/julia/02f/domain.jl new file mode 100644 index 000000000..a7edc2260 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02f/domain.jl @@ -0,0 +1,61 @@ +# julia/domain.jl +""" +Domain 结构体(与 domain.py 完全同构) +- 保存 config +- nghosts 计算逻辑 1:1 +- ist/ied 为 0-based(与 Python 一致) +""" + +include("mesh.jl") + +mutable struct Domain + config::Any # 保存 config(与 Python 一致) + mesh::Mesh + nghosts::Int + ist::Int # 0-based + ied::Int # 0-based + ntcells::Int +end + +function Domain(config::Any, mesh::Mesh) + nghosts = _calc_nghosts(config) + ist = nghosts + 1 # ← 1-based 起始索引 + ied = ist + mesh.ncells # ← 1-based 结束索引(不包含) + ntcells = mesh.ncells + 2 * nghosts + Domain(config, mesh, nghosts, ist, ied, ntcells) +end + +function _calc_nghosts(config::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + order = getfield_safe(config, :spatial_order, 2) + + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + if scheme == "eno" + nghosts = order + elseif startswith(scheme, "weno") + nghosts = order ÷ 2 + 1 + else + error("未知重建格式 $(scheme),无法计算ghost层!") + end + + if nghosts <= 0 + error("计算得到的ghost层数量无效:$(nghosts)(阶数$(order),格式$(scheme))") + end + + return nghosts +end + +function is_physical_cell(domain::Domain, idx::Int) + return domain.ist <= idx < domain.ied +end + +function get_physical_indices(domain::Domain) + return domain.ist:(domain.ied - 1) +end + +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02f/eno_weno_comparison.png b/example/1d-linear-convection/weno3/julia/02f/eno_weno_comparison.png new file mode 100644 index 0000000000000000000000000000000000000000..00a3f0d3f129fe0a2caca562ab37f648cb8381f7 GIT binary patch literal 225919 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,Ve7Lm> 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 BzxDtC literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/julia/02f/flux.jl b/example/1d-linear-convection/weno3/julia/02f/flux.jl new file mode 100644 index 000000000..cff235824 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02f/flux.jl @@ -0,0 +1,112 @@ +# julia/flux.jl +""" +通量计算器模块(与 flux.py 完全同构) +- 抽象基类 + 具体实现 +- 字段:cfd, config, mesh, wave_speed +""" + +include("mesh.jl") + +# ---------------------- 抽象基类 ---------------------- +""" +InviscidFluxCalculator 抽象类型 +Julia 无 ABC,用文档约定 +所有子类型必须实现 compute! +""" +abstract type InviscidFluxCalculator end + +# ---------------------- RusanovFluxCalculator ---------------------- +mutable struct RusanovFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function RusanovFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::RusanovFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = calc.wave_speed + c_R = calc.wave_speed + F_L = c_L * u_L + F_R = c_R * u_R + Smax = max(abs(c_L), abs(c_R)) + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + end +end + +# ---------------------- EngquistOsherFluxCalculator ---------------------- +mutable struct EngquistOsherFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function EngquistOsherFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::EngquistOsherFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + c = calc.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + end +end + +# ---------------------- FluxCalculatorFactory ---------------------- +module FluxCalculatorFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "通量计算器 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + config = cfd.config + if !hasproperty(config, :flux_type) + error("cfd.config 缺少 flux_type 字段") + end + + flux_type = config.flux_type + if !(flux_type isa AbstractString) + error("flux_type 必须为字符串,当前值: $flux_type") + end + + if !haskey(_REGISTRY, flux_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的通量计算器: '$flux_type'。可用选项: $available") + end + + return _REGISTRY[flux_type](cfd) +end + +# ✅ 修正:使用 Main. 前缀 +register("rusanov", cfd -> Main.RusanovFluxCalculator(cfd)) +register("engquist-osher", cfd -> Main.EngquistOsherFluxCalculator(cfd)) + +end # module FluxCalculatorFactory + +export FluxCalculatorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02f/initial_condition.jl b/example/1d-linear-convection/weno3/julia/02f/initial_condition.jl new file mode 100644 index 000000000..aa120160a --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02f/initial_condition.jl @@ -0,0 +1,120 @@ +# julia/initial_condition.jl +""" +初始条件模块(Julia 版) +目标:与 Python initial_condition.py 行为完全一致 +""" + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象接口(Julia 无继承,用函数约定) ---------------------- +# 所有初始条件必须实现: +# evaluate_at(ic, x::AbstractVector{Float64}) -> Vector{Float64} +# apply(ic, solution) + +# ---------------------- StepFunctionIC ---------------------- +struct StepFunctionIC + config::Any +end + +function evaluate_at(ic::StepFunctionIC, x::AbstractVector{Float64}) + u0 = ones(Float64, length(x)) + for i in eachindex(x) + if 0.5 <= x[i] <= 1.0 + u0[i] = 2.0 + end + end + return u0 +end + +function apply(ic::StepFunctionIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- SineWaveIC ---------------------- +struct SineWaveIC + config::Any +end + +function evaluate_at(ic::SineWaveIC, x::AbstractVector{Float64}) + L = getfield_safe(ic.config, :domain_length, 2.0) + return sin.(2π * x / L) +end + +function apply(ic::SineWaveIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- GaussianPulseIC ---------------------- +struct GaussianPulseIC + config::Any +end + +function evaluate_at(ic::GaussianPulseIC, x::AbstractVector{Float64}) + center = getfield_safe(ic.config, :pulse_center, 0.5) + width = getfield_safe(ic.config, :pulse_width, 0.1) + return exp.(-((x .- center) ./ width).^2) +end + +function apply(ic::GaussianPulseIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- 公共辅助函数 ---------------------- +""" +将初始场 values 应用到 solution.u 的物理区域(含 ghost 的数组) +""" +function _apply_to_interior(solution, values) + domain = solution.domain + # Python: for i in range(domain.ist, domain.ied) + # Julia: for i in domain.ist:domain.ied-1 (因为 ied 是结束索引,不包含) + for i in domain.ist:(domain.ied - 1) + j = i - domain.ist + 1 # values 是 1-based,i - ist 是 0-based → +1 + solution.u[i] = values[j] + end +end + +# ---------------------- InitialConditionFactory ---------------------- +module InitialConditionFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "初始条件 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(ic_type::String, config::Any) + if !(ic_type isa AbstractString) + error("ic_type 必须为字符串,当前值: $ic_type") + end + + if !haskey(_REGISTRY, ic_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的初始条件: '$ic_type'。可用选项: $available") + end + + return _REGISTRY[ic_type](config) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("step", config -> Main.StepFunctionIC(config)) +register("sin", config -> Main.SineWaveIC(config)) +register("gaussian", config -> Main.GaussianPulseIC(config)) + +end # module InitialConditionFactory + +export InitialConditionFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02f/mesh.jl b/example/1d-linear-convection/weno3/julia/02f/mesh.jl new file mode 100644 index 000000000..1b0dd4bf9 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02f/mesh.jl @@ -0,0 +1,45 @@ +# julia/mesh.jl +""" +Mesh 结构体(与提供的 mesh.py 完全同构) +- 字段名、初始化顺序、循环逻辑完全一致 +- 不做任何泛化或优化 +""" + +mutable struct Mesh + xmin::Float64 + xmax::Float64 + ncells::Int + nnodes::Int + nx::Int + x::Vector{Float64} + xcc::Vector{Float64} + L::Float64 # 在 init_mesh 中赋值 + dx::Float64 # 在 init_mesh 中赋值 + + function Mesh() + # 与 Python __init__ 完全一致 + xmin = 0.0 + xmax = 2.0 + ncells = 40 + nnodes = ncells + 1 + nx = ncells + x = zeros(Float64, nnodes) + xcc = zeros(Float64, ncells) + + # 调用 init_mesh(模拟 Python) + L = xmax - xmin + dx = L / ncells + + # Generate node coordinates: for i in range(self.nnodes) + for i in 1:nnodes + x[i] = xmin + (i - 1) * dx # Python i → Julia i-1 + end + + # Generate cell center coordinates + for i in 1:ncells + xcc[i] = 0.5 * (x[i] + x[i+1]) + end + + new(xmin, xmax, ncells, nnodes, nx, x, xcc, L, dx) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02f/plotter.jl b/example/1d-linear-convection/weno3/julia/02f/plotter.jl new file mode 100644 index 000000000..a77d8d688 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02f/plotter.jl @@ -0,0 +1,147 @@ +# julia/plotter.jl +""" +CFDPlotter 的 Julia 实现(通过 PythonCall.jl 调用 Matplotlib) +确保与 Python plotter.py 行为完全一致 +""" + +using PythonCall + +# 初始化 Python 环境(加载 matplotlib, inflect) +const plt = pyimport("matplotlib.pyplot") +const inflect = pyimport("inflect") + +mutable struct CFDPlotter + default_styles::Dict{String, Any} + p::Py +end + +function CFDPlotter() + default_styles = Dict{String, Any}( + "numerical" => Dict( + :color => "blue", + :linestyle => "-", + :marker => "o", + :markerfacecolor => "none" + ), + "analytical" => Dict( + :color => "red", + :linestyle => "--", + :marker => "", + :linewidth => 1.5 + ), + "comparison" => [ + Dict(:color => "black", :linestyle => "-", :marker => "o", :markerfacecolor => "none"), + Dict(:color => "blue", :linestyle => "--", :marker => "s", :markerfacecolor => "none"), + Dict(:color => "green", :linestyle => ":", :marker => "^", :markerfacecolor => "none") + ] + ) + p = inflect.engine() + CFDPlotter(default_styles, p) +end + +""" +轻量即时绘图(快速验证结果) +""" +function plot_quick(plotter::CFDPlotter, cfd_result::Dict; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + rk_order = cfd_result["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = cfd_result["config"]["final_time"] + order = cfd_result["config"]["order"] + scheme = uppercase(cfd_result["config"]["scheme"]) + actual_title = "1D Convection (t=$(final_time))\n$(order)th-order $(scheme) + $(rk_str)-order RK" + end + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"]; + label="Numerical ($(uppercase(cfd_result["config"]["scheme"])))", + plotter.default_styles["numerical"]..., + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + # 通用样式 + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +""" +多格式/多精度对比绘图 +""" +function plot_comparison(plotter::CFDPlotter, result_list::Vector{Dict{String, Any}}; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + schemes = [uppercase(r["config"]["scheme"]) * string(r["config"]["order"]) for r in result_list] + rk_order = result_list[1]["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = result_list[1]["config"]["final_time"] + actual_title = "1D Convection Comparison (t=$(final_time))\n$(join(schemes, ", ")) + $(rk_str)-order RK" + end + + # 绘制多个数值解 + for (i, res) in enumerate(result_list) + style = plotter.default_styles["comparison"][mod1(i, length(plotter.default_styles["comparison"]))] + label = "Numerical ($(uppercase(res["config"]["scheme"]))$(res["config"]["order"]))" + plt.plot( + res["x"], res["numerical"]; + label=label, + style..., + markersize=5, linewidth=0.5 + ) + end + + # 绘制解析解 + plt.plot( + result_list[1]["x"], result_list[1]["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +function _set_common_style(plotter::CFDPlotter, title::String) + plt.title(title, fontsize=12) + plt.xlabel("x", fontsize=10) + plt.ylabel("u", fontsize=10) + plt.legend(fontsize=9) + plt.grid(true, color="gray", linestyle="--", linewidth=0.5, alpha=0.7) + plt.tight_layout() +end + +""" +快捷函数:ENO/WENO对比绘图 +""" +function plot_eno_weno_comparison(eno_result::Dict, weno_result::Dict; save_path=nothing) + plotter = CFDPlotter() + plot_comparison(plotter, [eno_result, weno_result]; save_path=save_path) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02f/reconstructor/eno.jl b/example/1d-linear-convection/weno3/julia/02f/reconstructor/eno.jl new file mode 100644 index 000000000..e78a636f3 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02f/reconstructor/eno.jl @@ -0,0 +1,107 @@ +# julia/reconstructor/eno.jl +""" +ENO 重构器(与 reconstructor/eno.py 完全同构) +""" + +# ---------------------- ENO 系数初始化 ---------------------- +function _init_eno_coef!(spatial_order::Int, coef::Matrix{Float64}) + if spatial_order == 1 + coef[1, 1] = 1.0 + coef[2, 1] = 1.0 + elseif spatial_order == 2 + coef[1, 1:2] = [3.0/2.0, -1.0/2.0] + coef[2, 1:2] = [1.0/2.0, 1.0/2.0] + coef[3, 1:2] = [-1.0/2.0, 3.0/2.0] + elseif spatial_order == 3 + coef[1, 1:3] = [11.0/6.0, -7.0/6.0, 1.0/3.0] + coef[2, 1:3] = [1.0/3.0, 5.0/6.0, -1.0/6.0] + coef[3, 1:3] = [-1.0/6.0, 5.0/6.0, 1.0/3.0] + coef[4, 1:3] = [1.0/3.0, -7.0/6.0, 11.0/6.0] + elseif spatial_order == 4 + coef[1, 1:4] = [25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0] + coef[2, 1:4] = [1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0] + coef[3, 1:4] = [-1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0] + coef[4, 1:4] = [1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0] + coef[5, 1:4] = [-1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0] + elseif spatial_order == 5 + coef[1, 1:5] = [137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0] + coef[2, 1:5] = [1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0] + coef[3, 1:5] = [-1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0] + coef[4, 1:5] = [1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0] + coef[5, 1:5] = [-1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0] + coef[6, 1:5] = [1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0] + elseif spatial_order == 6 + coef[1, 1:6] = [49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0] + coef[2, 1:6] = [1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0] + coef[3, 1:6] = [-1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0] + coef[4, 1:6] = [1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0] + coef[5, 1:6] = [-1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0] + coef[6, 1:6] = [1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0] + coef[7, 1:6] = [-1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0] + elseif spatial_order == 7 + coef[1, 1:7] = [363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0] + coef[2, 1:7] = [1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0] + coef[3, 1:7] = [-1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0] + coef[4, 1:7] = [1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0] + coef[5, 1:7] = [-1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0] + coef[6, 1:7] = [1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0] + coef[7, 1:7] = [-1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0] + coef[8, 1:7] = [1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0] + else + error("ENO 系数未实现 order=$spatial_order") + end +end + +# ---------------------- ENO 重构器 ---------------------- +mutable struct EnoReconstructor + spatial_order::Int + ntcells::Int + lmc::Vector{Int} + coef::Matrix{Float64} + dd::Matrix{Float64} + + function EnoReconstructor(spatial_order::Int, ntcells::Int) + lmc = zeros(Int, ntcells) + coef = zeros(Float64, spatial_order + 1, spatial_order) + dd = zeros(Float64, spatial_order, ntcells) + _init_eno_coef!(spatial_order, coef) + new(spatial_order, ntcells, lmc, coef, dd) + end +end + +function reconstruct(rec::EnoReconstructor, q::Vector{Float64}, cfd::Any) + # 1. 差商计算 (dd[1,:] = q) + @views rec.dd[1, :] .= q + for m in 2:rec.spatial_order + for j in 1:(rec.ntcells - m + 1) + rec.dd[m, j] = rec.dd[m-1, j+1] - rec.dd[m-1, j] + end + end + + # 2. 选择 smoothest stencil + domain = cfd.domain + for i in (domain.ist - 1):(domain.ied) # Python: range(ist-1, ied+1) → ied+1-1 = ied + rec.lmc[i] = i + for m in 2:rec.spatial_order + if abs(rec.dd[m, rec.lmc[i] - 1]) < abs(rec.dd[m, rec.lmc[i]]) + rec.lmc[i] -= 1 + end + end + end + + # 3. 重构界面值 + solution = cfd.solution + for i in domain.ist:(domain.ied) # Python: range(ist, ied+1) → ied+1-1 = ied + j = i - domain.ist + 1 # Julia 1-based + k1 = rec.lmc[i - 1] + k2 = rec.lmc[i] + r1 = (i - 1) - k1 + 1 + r2 = i - k2 + 1 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in 1:rec.spatial_order + solution.q_face_left[j] += q[k1 + m - 1] * rec.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m - 1] * rec.coef[r2, m] + end + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02f/reconstructor/factory.jl b/example/1d-linear-convection/weno3/julia/02f/reconstructor/factory.jl new file mode 100644 index 000000000..f9c9cb4b3 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02f/reconstructor/factory.jl @@ -0,0 +1,41 @@ +# julia/reconstructor/factory.jl + +""" +ReconstructorFactory +对标 Python: ReconstructorFactory.create(config, domain) +""" +module ReconstructorFactory + +# ✅ 不要用 using ..EnoReconstructor(它们不是模块) +# 直接通过 Main. 引用顶层定义的类型 + +function create(config::Any, domain::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + # 处理 WENO 默认命名(如 Python) + if scheme == "weno" + order = getfield_safe(config, :spatial_order, 5) + scheme = "weno$(order)" + end + + if scheme == "eno" + order = getfield_safe(config, :spatial_order, 3) + return Main.EnoReconstructor(order, domain.ntcells) + elseif scheme == "weno3" + return Main.Weno3Reconstructor() + else + error("不支持的重建格式: $scheme(仅支持 eno/weno3)") + end +end + +# 辅助函数(复制自 domain.jl) +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +end # module ReconstructorFactory + +export ReconstructorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02f/reconstructor/weno3.jl b/example/1d-linear-convection/weno3/julia/02f/reconstructor/weno3.jl new file mode 100644 index 000000000..2b6fe1abb --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02f/reconstructor/weno3.jl @@ -0,0 +1,65 @@ +# julia/reconstructor/weno3.jl +""" +WENO3 重构器(与 reconstructor/weno3.py 完全同构) +""" + +mutable struct Weno3Reconstructor + # 无字段,与 Python 一致 +end + +function reconstruct(rec::Weno3Reconstructor, q::Vector{Float64}, cfd::Any) + domain = cfd.domain + solution = cfd.solution + _reconstruct_left_interfaces(domain, q, solution.q_face_left) + _reconstruct_right_interfaces(domain, q, solution.q_face_right) +end + +function _reconstruct_left_interfaces(domain, u, qL) + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in (domain.ist - 1):(domain.ied - 1) + j = i - (domain.ist - 1) + 1 # ← Julia 1-based: j = i - (ist-1) 对应 Python j = i - (ist-1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = _reconstruct_from_left_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_right_interfaces(domain, u, qR) + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in domain.ist:domain.ied + j = i - domain.ist + 1 # ← Julia 1-based: j = i - ist 对应 Python j = i - ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = _reconstruct_from_right_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_from_left_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -0.5*v1 + 1.5*v2 # r=1 stencil + q1 = 0.5*v2 + 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end + +function _reconstruct_from_right_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 0.5*v1 + 0.5*v2 # r=1 stencil + q1 = 1.5*v2 - 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02f/residual.jl b/example/1d-linear-convection/weno3/julia/02f/residual.jl new file mode 100644 index 000000000..e8fd65843 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02f/residual.jl @@ -0,0 +1,65 @@ +# julia/residual.jl +""" +残差计算器(与 residual.py 完全同构) +- 封装重建→通量→散度完整流程 +- 依赖 cfd 的多个字段 +""" + +include("mesh.jl") + +mutable struct ResidualCalculator + cfd::Any + config::Any + domain::Any + solution::Any + mesh::Mesh + reconstructor::Any + flux_calculator::Any # 通量计算器(外部传入,替代工厂) + + function ResidualCalculator(cfd::Any, flux_calculator::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + mesh = domain.mesh + reconstructor = cfd.reconstructor + + new(cfd, config, domain, solution, mesh, reconstructor, flux_calculator) + end +end + +""" +计算完整残差(对外唯一接口) +""" +function compute!(calc::ResidualCalculator) + _reconstruct(calc) + _compute_inviscid_flux(calc) + _compute_flux_divergence(calc) +end + +""" +私有方法:界面值重建 +""" +function _reconstruct(calc::ResidualCalculator) + reconstruct(calc.reconstructor, calc.solution.u, calc.cfd) +end + +""" +私有方法:计算无粘通量 +""" +function _compute_inviscid_flux(calc::ResidualCalculator) + compute!(calc.flux_calculator, + calc.solution.q_face_left, + calc.solution.q_face_right, + calc.solution.flux) +end + +""" +私有方法:计算通量散度(残差 = -dF/dx) +""" +function _compute_flux_divergence(calc::ResidualCalculator) + solution = calc.solution + mesh = calc.mesh + for i in 1:mesh.ncells + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / mesh.dx + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02f/run_eno_weno.jl b/example/1d-linear-convection/weno3/julia/02f/run_eno_weno.jl new file mode 100644 index 000000000..58b6cfa5f --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02f/run_eno_weno.jl @@ -0,0 +1,50 @@ +# julia/run_eno_weno.jl +""" +1:1 复刻 run_eno_weno.py 的 Julia 版本 +""" + +include("config.jl") +include("mesh.jl") +include("solver.jl") +include("plotter.jl") + +function performEnoWenoAnalysis() + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO3 求解 + println("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + with_reconstruction(config_eno3, "eno", 3) # 显式指定 3 阶 + config_eno3.dt = 0.0025 # 覆盖默认值 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + run!(cfd_eno3) # 求解并生成 result 字典 + + # 3. 配置并运行 WENO3 求解 + println("Running WENO3 solver...") + config_weno3 = CfdConfig() + with_reconstruction(config_weno3, "weno", 3) # 显式指定 3 阶(WENO 默认 5 阶) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + run!(cfd_weno3) + + # 5. 绘制 ENO/WENO 对比图 + println("Plotting comparison results...") + plot_eno_weno_comparison( + cfd_eno3.result, + cfd_weno3.result; + save_path="eno_weno_comparison.png" + ) + + return cfd_eno3, cfd_weno3 +end + +# 主程序入口 +if abspath(PROGRAM_FILE) == @__FILE__ + performEnoWenoAnalysis() + println("Analysis completed!") +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02f/solution.jl b/example/1d-linear-convection/weno3/julia/02f/solution.jl new file mode 100644 index 000000000..d1f24e687 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02f/solution.jl @@ -0,0 +1,64 @@ +# julia/solution.jl +""" +Solution 结构体(与真实 solution.py 完全同构) +字段顺序、方法、逻辑 1:1 对齐 +""" + +include("mesh.jl") +include("domain.jl") +include("initial_condition.jl") + +mutable struct Solution + domain::Domain + q_face_left::Vector{Float64} + q_face_right::Vector{Float64} + flux::Vector{Float64} + res::Vector{Float64} + u::Vector{Float64} + un::Vector{Float64} + + function Solution(config::Any, domain::Domain) + mesh = domain.mesh + + q_face_left = zeros(Float64, mesh.nnodes) + q_face_right = zeros(Float64, mesh.nnodes) + flux = zeros(Float64, mesh.nnodes) + res = zeros(Float64, mesh.ncells) + u = zeros(Float64, domain.ntcells) + un = zeros(Float64, domain.ntcells) + + sol = new(domain, q_face_left, q_face_right, flux, res, u, un) + initialize_from_config(sol, config) + return sol + end +end + +""" +重置解数组为初始状态 +""" +function reset_solution(sol::Solution) + fill!(sol.u, 0.0) + fill!(sol.un, 0.0) +end + +""" +根据配置初始化场 +""" + +function initialize_from_config(sol::Solution, config::Any) + ic_type = getfield_safe(config, :ic_type, "step") + ic = InitialConditionFactory.create(ic_type, config) + apply(ic, sol) +end + +""" +更新旧场:un = u +""" +function update_old_field(sol::Solution) + sol.un .= sol.u # 等价于 Python 的 un[:] = u[:] +end + +# ---------------------- 辅助函数 ---------------------- +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02f/solver.jl b/example/1d-linear-convection/weno3/julia/02f/solver.jl new file mode 100644 index 000000000..99fd60f6a --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02f/solver.jl @@ -0,0 +1,152 @@ +# julia/solver.jl +""" +CFD 求解器主类(与 solver.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("initial_condition.jl") +include("boundary.jl") +include("flux.jl") +include("residual.jl") +include("time_integration.jl") +include("reconstructor/eno.jl") +include("reconstructor/weno3.jl") +include("reconstructor/factory.jl") + +# 导入工厂模块(必须在顶层!) +using .FluxCalculatorFactory +using .TimeIntegratorFactory +using .BoundaryConditionFactory +using .ReconstructorFactory + +# ---------------------- Cfd 求解器 ---------------------- +mutable struct Cfd + config::Any + domain::Domain + solution::Solution + reconstructor::Any + residual_calculator::ResidualCalculator + integrator::Any + boundary_condition::Any + result::Dict{String, Any} + + function Cfd(config::Any, mesh::Mesh) + domain = Domain(config, mesh) + solution = Solution(config, domain) + + reconstructor = ReconstructorFactory.create(config, domain) + + # 构造 cfd 上下文(NamedTuple) + cfd_context = ( + config = config, + domain = domain + ) + + # 创建通量计算器 + flux_calculator = FluxCalculatorFactory.create(cfd_context) + + # 残差计算器 + residual_calculator = ResidualCalculator( + (config=config, domain=domain, solution=solution, reconstructor=reconstructor), + flux_calculator + ) + + # 构建边界条件上下文 + bc_context = (config = config, domain = domain) + + # 使用工厂创建边界条件 + boundary_condition = BoundaryConditionFactory.create(bc_context) + + # 构建时间推进器所需上下文 + integrator_context = ( + config = config, + domain = domain, + solution = solution, + residual_calculator = residual_calculator, + boundary_condition = boundary_condition + ) + + # 使用工厂创建时间推进器 + integrator = TimeIntegratorFactory.create(integrator_context) + #@show typeof(integrator) + + # 注入 cfd 到 residual_calculator 和 integrator + residual_calculator.cfd = (config=config, domain=domain, solution=solution, reconstructor=reconstructor, residual_calculator=residual_calculator, integrator=integrator, boundary_condition=boundary_condition) + integrator.base.cfd = residual_calculator.cfd + + result = Dict{String, Any}() + new(config, domain, solution, reconstructor, residual_calculator, integrator, boundary_condition, result) + end +end + +""" +通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界 +""" +function exact_solution(cfd::Cfd) + x = cfd.domain.mesh.xcc + T = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = @. (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = if cfd.config.ic_type == "step" + StepFunctionIC(cfd.config) + elseif cfd.config.ic_type == "sin" + SineWaveIC(cfd.config) + elseif cfd.config.ic_type == "gaussian" + GaussianPulseIC(cfd.config) + else + error("未知初始条件: $(cfd.config.ic_type)") + end + + return evaluate_at(ic, x_shifted) +end + +""" +主求解循环 +""" +function run!(cfd::Cfd) + # 应用初始边界条件并同步 old field + apply!(cfd.boundary_condition, cfd.solution.u) + update_old_field(cfd.solution) + + t = 0.0 + dt_old = cfd.config.dt + dt = dt_old + + while t < cfd.config.final_time + if t + dt > cfd.config.final_time + dt = cfd.config.final_time - t + end + #@show t, dt, maximum(cfd.solution.u), minimum(cfd.solution.u) + # 执行时间步 + step(cfd.integrator, dt) + t += dt + end + + # 恢复 dt + cfd.config.dt = dt_old + + # 整理结果 + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied-1] # Python: [ist:ied] + analytical = exact_solution(cfd) + + cfd.result = Dict( + "x" => cfd.domain.mesh.xcc, + "numerical" => u_numerical, + "analytical" => analytical, + "config" => Dict( + "scheme" => cfd.config.recon_scheme, + "order" => cfd.config.spatial_order, + "rk_order" => cfd.config.rk_order, + "final_time" => cfd.config.final_time + ) + ) + + return u_numerical +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02f/time_integration.jl b/example/1d-linear-convection/weno3/julia/02f/time_integration.jl new file mode 100644 index 000000000..add564ba5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02f/time_integration.jl @@ -0,0 +1,169 @@ +# julia/time_integration.jl +""" +时间推进器模块(与 time_integration.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("residual.jl") +include("boundary.jl") + +# ---------------------- 抽象时间推进器基类 ---------------------- +abstract type TimeIntegrator end + +mutable struct TimeIntegratorBase <: TimeIntegrator + cfd::Any + config::Any + domain::Domain + solution::Solution + residual_calculator::Any # ResidualCalculator +end + +function TimeIntegratorBase(cfd::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + residual_calculator = cfd.residual_calculator + TimeIntegratorBase(cfd, config, domain, solution, residual_calculator) +end + +function compute_residual(integrator::TimeIntegratorBase) + compute!(integrator.residual_calculator) +end + +function apply_boundary(integrator::TimeIntegratorBase) + apply!(integrator.cfd.boundary_condition, integrator.solution.u) +end + +function map_idx(integrator::TimeIntegratorBase, i::Int) + return i - integrator.domain.ist + 1 # ← +1 转为 1-based +end + +# ---------------------- RK1Integrator ---------------------- +mutable struct RK1Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK1Integrator(cfd::Any) + RK1Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK1Integrator, dt::Float64) + compute_residual(integrator.base) + for i in integrator.base.domain.ist:(integrator.base.domain.ied - 1) + j = map_idx(integrator.base, i) + integrator.base.solution.u[i] += dt * integrator.base.solution.res[j] + end + apply_boundary(integrator.base) + update_old_field(integrator.base.solution) +end + +# ---------------------- RK2Integrator ---------------------- +mutable struct RK2Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK2Integrator(cfd::Any) + RK2Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK2Integrator, dt::Float64) + base = integrator.base + # 阶段1:预测步 + compute_residual(base) + u_pred = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u_pred[i] += dt * base.solution.res[j] + end + base.solution.u .= u_pred + apply_boundary(base) + # 阶段2:校正步 + compute_residual(base) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = 0.5 * base.solution.un[i] + 0.5 * base.solution.u[i] + 0.5 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end + +# ---------------------- RK3Integrator ---------------------- +mutable struct RK3Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK3Integrator(cfd::Any) + RK3Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK3Integrator, dt::Float64) + base = integrator.base + # 阶段1 + compute_residual(base) + u1 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u1[i] += dt * base.solution.res[j] + end + base.solution.u .= u1 + apply_boundary(base) + # 阶段2 + compute_residual(base) + u2 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u2[i] = 0.75 * base.solution.un[i] + 0.25 * base.solution.u[i] + 0.25 * dt * base.solution.res[j] + end + base.solution.u .= u2 + apply_boundary(base) + # 阶段3 + compute_residual(base) + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = c1 * base.solution.un[i] + c2 * base.solution.u[i] + c3 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end + +# ---------------------- TimeIntegratorFactory ---------------------- +module TimeIntegratorFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "时间积分器 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + rk_order = cfd.config.rk_order + if !(rk_order isa Integer) || rk_order < 1 + error("rk_order 必须为正整数,当前值: $rk_order") + end + + name = "rk$rk_order" + if !haskey(_REGISTRY, name) + available = sort(collect(keys(_REGISTRY))) + error("未注册的时间积分器: '$name'。可用选项: $available") + end + + return _REGISTRY[name](cfd) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("rk1", cfd -> Main.RK1Integrator(cfd)) +register("rk2", cfd -> Main.RK2Integrator(cfd)) +register("rk3", cfd -> Main.RK3Integrator(cfd)) + +end # module TimeIntegratorFactory + +export TimeIntegratorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02g/boundary.jl b/example/1d-linear-convection/weno3/julia/02g/boundary.jl new file mode 100644 index 000000000..396c12642 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02g/boundary.jl @@ -0,0 +1,129 @@ +# julia/boundary.jl +# 目标:与 Python boundary.py 行为完全一致(1:1 同构) +# 前提:ist/ied 在 Julia 中按 1-based 设置(ist = nghosts + 1) + +using NPZ # 仅用于测试,实际模块可不依赖 + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象基类(Julia 风格:用函数接口) ---------------------- +# Julia 无 abstract class,用文档+约定 +# 所有子类型必须实现 apply!(bc, u) + +# ---------------------- PeriodicBoundary ---------------------- +mutable struct PeriodicBoundary + cfd::Any +end + +function apply!(self::PeriodicBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist # 1-based, e.g., 3 + ied = self.cfd.domain.ied # 1-based, e.g., 43 + + # 左 ghost: u[ist - 1 - ig] = u[ied - 1 - ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ied - 1 - ig] + end + # 右 ghost: u[ied + ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ied + ig] = u[ist + ig] + end +end + +# ---------------------- DirichletBoundary ---------------------- +mutable struct DirichletBoundary + cfd::Any +end + +function apply!(self::DirichletBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + left_value = getfield_safe(self.cfd.config, :left_boundary_value, 1.0) + right_value = getfield_safe(self.cfd.config, :right_boundary_value, 2.0) + + # 左边界 + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = left_value + end + # 右边界 + for ig in 0:(nghosts-1) + u[ied + ig] = right_value + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Dirichlet边界: 左值=$left_value, 右值=$right_value") + end +end + +# ---------------------- NeumannBoundary ---------------------- +mutable struct NeumannBoundary + cfd::Any +end + +function apply!(self::NeumannBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + # 左边界零梯度:u[ist - 1 - ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ist + ig] + end + # 右边界零梯度:u[ied + ig] = u[ied - 1 - ig] ← 注意是 ied - 1 - ig + for ig in 0:(nghosts-1) + u[ied + ig] = u[ied - 1 - ig] + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Neumann边界: 零梯度") + end +end + + +# ---------------------- BoundaryConditionFactory ---------------------- +module BoundaryConditionFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "边界条件 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + bc_type = cfd.config.boundary_type + if !(bc_type isa AbstractString) + error("boundary_type 必须为字符串,当前值: $bc_type") + end + + if !haskey(_REGISTRY, bc_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的边界条件: '$bc_type'。可用选项: $available") + end + + return _REGISTRY[bc_type](cfd) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("periodic", cfd -> Main.PeriodicBoundary(cfd)) +register("dirichlet", cfd -> Main.DirichletBoundary(cfd)) +register("neumann", cfd -> Main.NeumannBoundary(cfd)) + +end # module BoundaryConditionFactory + +export BoundaryConditionFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02g/config.jl b/example/1d-linear-convection/weno3/julia/02g/config.jl new file mode 100644 index 000000000..bf48de3a6 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02g/config.jl @@ -0,0 +1,68 @@ +# julia/config.jl +""" +CfdConfig:与 Python config.py 完全同构 +""" +mutable struct CfdConfig + ic_type::String + recon_scheme::String + flux_type::String + rk_order::Int + wave_speed::Float64 + final_time::Float64 + dt::Float64 + boundary_type::String + left_boundary_value::Float64 + right_boundary_value::Float64 + spatial_order::Int + + function CfdConfig() + new( + "step", + "eno", + "rusanov", + 1, + 1.0, + 0.625, + 0.025, + "periodic", + 1.0, + 2.0, + 2 + ) + end +end + +""" +专用配置:重建方案(链式调用) +""" +function with_reconstruction(cfg::CfdConfig, scheme::String, order::Union{Int, Nothing}=nothing) + cfg.recon_scheme = lowercase(scheme) + + if order !== nothing + cfg.spatial_order = order + else + if startswith(cfg.recon_scheme, "weno") + cfg.spatial_order = 5 + elseif cfg.recon_scheme == "eno" + cfg.spatial_order = 3 + else + error("不支持的重建格式:$scheme(仅支持 eno/weno)") + end + end + + return cfg # 支持链式调用 +end + +""" +专用配置:边界条件(链式调用) +""" +function with_boundary(cfg::CfdConfig, bc_type::String; left_value=nothing, right_value=nothing) + cfg.boundary_type = bc_type + if left_value !== nothing + cfg.left_boundary_value = left_value + end + if right_value !== nothing + cfg.right_boundary_value = right_value + end + return cfg +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02g/domain.jl b/example/1d-linear-convection/weno3/julia/02g/domain.jl new file mode 100644 index 000000000..a7edc2260 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02g/domain.jl @@ -0,0 +1,61 @@ +# julia/domain.jl +""" +Domain 结构体(与 domain.py 完全同构) +- 保存 config +- nghosts 计算逻辑 1:1 +- ist/ied 为 0-based(与 Python 一致) +""" + +include("mesh.jl") + +mutable struct Domain + config::Any # 保存 config(与 Python 一致) + mesh::Mesh + nghosts::Int + ist::Int # 0-based + ied::Int # 0-based + ntcells::Int +end + +function Domain(config::Any, mesh::Mesh) + nghosts = _calc_nghosts(config) + ist = nghosts + 1 # ← 1-based 起始索引 + ied = ist + mesh.ncells # ← 1-based 结束索引(不包含) + ntcells = mesh.ncells + 2 * nghosts + Domain(config, mesh, nghosts, ist, ied, ntcells) +end + +function _calc_nghosts(config::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + order = getfield_safe(config, :spatial_order, 2) + + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + if scheme == "eno" + nghosts = order + elseif startswith(scheme, "weno") + nghosts = order ÷ 2 + 1 + else + error("未知重建格式 $(scheme),无法计算ghost层!") + end + + if nghosts <= 0 + error("计算得到的ghost层数量无效:$(nghosts)(阶数$(order),格式$(scheme))") + end + + return nghosts +end + +function is_physical_cell(domain::Domain, idx::Int) + return domain.ist <= idx < domain.ied +end + +function get_physical_indices(domain::Domain) + return domain.ist:(domain.ied - 1) +end + +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02g/flux.jl b/example/1d-linear-convection/weno3/julia/02g/flux.jl new file mode 100644 index 000000000..cff235824 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02g/flux.jl @@ -0,0 +1,112 @@ +# julia/flux.jl +""" +通量计算器模块(与 flux.py 完全同构) +- 抽象基类 + 具体实现 +- 字段:cfd, config, mesh, wave_speed +""" + +include("mesh.jl") + +# ---------------------- 抽象基类 ---------------------- +""" +InviscidFluxCalculator 抽象类型 +Julia 无 ABC,用文档约定 +所有子类型必须实现 compute! +""" +abstract type InviscidFluxCalculator end + +# ---------------------- RusanovFluxCalculator ---------------------- +mutable struct RusanovFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function RusanovFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::RusanovFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = calc.wave_speed + c_R = calc.wave_speed + F_L = c_L * u_L + F_R = c_R * u_R + Smax = max(abs(c_L), abs(c_R)) + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + end +end + +# ---------------------- EngquistOsherFluxCalculator ---------------------- +mutable struct EngquistOsherFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function EngquistOsherFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::EngquistOsherFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + c = calc.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + end +end + +# ---------------------- FluxCalculatorFactory ---------------------- +module FluxCalculatorFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "通量计算器 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + config = cfd.config + if !hasproperty(config, :flux_type) + error("cfd.config 缺少 flux_type 字段") + end + + flux_type = config.flux_type + if !(flux_type isa AbstractString) + error("flux_type 必须为字符串,当前值: $flux_type") + end + + if !haskey(_REGISTRY, flux_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的通量计算器: '$flux_type'。可用选项: $available") + end + + return _REGISTRY[flux_type](cfd) +end + +# ✅ 修正:使用 Main. 前缀 +register("rusanov", cfd -> Main.RusanovFluxCalculator(cfd)) +register("engquist-osher", cfd -> Main.EngquistOsherFluxCalculator(cfd)) + +end # module FluxCalculatorFactory + +export FluxCalculatorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02g/initial_condition.jl b/example/1d-linear-convection/weno3/julia/02g/initial_condition.jl new file mode 100644 index 000000000..aa120160a --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02g/initial_condition.jl @@ -0,0 +1,120 @@ +# julia/initial_condition.jl +""" +初始条件模块(Julia 版) +目标:与 Python initial_condition.py 行为完全一致 +""" + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +# ---------------------- 抽象接口(Julia 无继承,用函数约定) ---------------------- +# 所有初始条件必须实现: +# evaluate_at(ic, x::AbstractVector{Float64}) -> Vector{Float64} +# apply(ic, solution) + +# ---------------------- StepFunctionIC ---------------------- +struct StepFunctionIC + config::Any +end + +function evaluate_at(ic::StepFunctionIC, x::AbstractVector{Float64}) + u0 = ones(Float64, length(x)) + for i in eachindex(x) + if 0.5 <= x[i] <= 1.0 + u0[i] = 2.0 + end + end + return u0 +end + +function apply(ic::StepFunctionIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- SineWaveIC ---------------------- +struct SineWaveIC + config::Any +end + +function evaluate_at(ic::SineWaveIC, x::AbstractVector{Float64}) + L = getfield_safe(ic.config, :domain_length, 2.0) + return sin.(2π * x / L) +end + +function apply(ic::SineWaveIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- GaussianPulseIC ---------------------- +struct GaussianPulseIC + config::Any +end + +function evaluate_at(ic::GaussianPulseIC, x::AbstractVector{Float64}) + center = getfield_safe(ic.config, :pulse_center, 0.5) + width = getfield_safe(ic.config, :pulse_width, 0.1) + return exp.(-((x .- center) ./ width).^2) +end + +function apply(ic::GaussianPulseIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- 公共辅助函数 ---------------------- +""" +将初始场 values 应用到 solution.u 的物理区域(含 ghost 的数组) +""" +function _apply_to_interior(solution, values) + domain = solution.domain + # Python: for i in range(domain.ist, domain.ied) + # Julia: for i in domain.ist:domain.ied-1 (因为 ied 是结束索引,不包含) + for i in domain.ist:(domain.ied - 1) + j = i - domain.ist + 1 # values 是 1-based,i - ist 是 0-based → +1 + solution.u[i] = values[j] + end +end + +# ---------------------- InitialConditionFactory ---------------------- +module InitialConditionFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "初始条件 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(ic_type::String, config::Any) + if !(ic_type isa AbstractString) + error("ic_type 必须为字符串,当前值: $ic_type") + end + + if !haskey(_REGISTRY, ic_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的初始条件: '$ic_type'。可用选项: $available") + end + + return _REGISTRY[ic_type](config) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("step", config -> Main.StepFunctionIC(config)) +register("sin", config -> Main.SineWaveIC(config)) +register("gaussian", config -> Main.GaussianPulseIC(config)) + +end # module InitialConditionFactory + +export InitialConditionFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02g/mesh.jl b/example/1d-linear-convection/weno3/julia/02g/mesh.jl new file mode 100644 index 000000000..1b0dd4bf9 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02g/mesh.jl @@ -0,0 +1,45 @@ +# julia/mesh.jl +""" +Mesh 结构体(与提供的 mesh.py 完全同构) +- 字段名、初始化顺序、循环逻辑完全一致 +- 不做任何泛化或优化 +""" + +mutable struct Mesh + xmin::Float64 + xmax::Float64 + ncells::Int + nnodes::Int + nx::Int + x::Vector{Float64} + xcc::Vector{Float64} + L::Float64 # 在 init_mesh 中赋值 + dx::Float64 # 在 init_mesh 中赋值 + + function Mesh() + # 与 Python __init__ 完全一致 + xmin = 0.0 + xmax = 2.0 + ncells = 40 + nnodes = ncells + 1 + nx = ncells + x = zeros(Float64, nnodes) + xcc = zeros(Float64, ncells) + + # 调用 init_mesh(模拟 Python) + L = xmax - xmin + dx = L / ncells + + # Generate node coordinates: for i in range(self.nnodes) + for i in 1:nnodes + x[i] = xmin + (i - 1) * dx # Python i → Julia i-1 + end + + # Generate cell center coordinates + for i in 1:ncells + xcc[i] = 0.5 * (x[i] + x[i+1]) + end + + new(xmin, xmax, ncells, nnodes, nx, x, xcc, L, dx) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02g/plotter.jl b/example/1d-linear-convection/weno3/julia/02g/plotter.jl new file mode 100644 index 000000000..a77d8d688 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02g/plotter.jl @@ -0,0 +1,147 @@ +# julia/plotter.jl +""" +CFDPlotter 的 Julia 实现(通过 PythonCall.jl 调用 Matplotlib) +确保与 Python plotter.py 行为完全一致 +""" + +using PythonCall + +# 初始化 Python 环境(加载 matplotlib, inflect) +const plt = pyimport("matplotlib.pyplot") +const inflect = pyimport("inflect") + +mutable struct CFDPlotter + default_styles::Dict{String, Any} + p::Py +end + +function CFDPlotter() + default_styles = Dict{String, Any}( + "numerical" => Dict( + :color => "blue", + :linestyle => "-", + :marker => "o", + :markerfacecolor => "none" + ), + "analytical" => Dict( + :color => "red", + :linestyle => "--", + :marker => "", + :linewidth => 1.5 + ), + "comparison" => [ + Dict(:color => "black", :linestyle => "-", :marker => "o", :markerfacecolor => "none"), + Dict(:color => "blue", :linestyle => "--", :marker => "s", :markerfacecolor => "none"), + Dict(:color => "green", :linestyle => ":", :marker => "^", :markerfacecolor => "none") + ] + ) + p = inflect.engine() + CFDPlotter(default_styles, p) +end + +""" +轻量即时绘图(快速验证结果) +""" +function plot_quick(plotter::CFDPlotter, cfd_result::Dict; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + rk_order = cfd_result["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = cfd_result["config"]["final_time"] + order = cfd_result["config"]["order"] + scheme = uppercase(cfd_result["config"]["scheme"]) + actual_title = "1D Convection (t=$(final_time))\n$(order)th-order $(scheme) + $(rk_str)-order RK" + end + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"]; + label="Numerical ($(uppercase(cfd_result["config"]["scheme"])))", + plotter.default_styles["numerical"]..., + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + # 通用样式 + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +""" +多格式/多精度对比绘图 +""" +function plot_comparison(plotter::CFDPlotter, result_list::Vector{Dict{String, Any}}; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + schemes = [uppercase(r["config"]["scheme"]) * string(r["config"]["order"]) for r in result_list] + rk_order = result_list[1]["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = result_list[1]["config"]["final_time"] + actual_title = "1D Convection Comparison (t=$(final_time))\n$(join(schemes, ", ")) + $(rk_str)-order RK" + end + + # 绘制多个数值解 + for (i, res) in enumerate(result_list) + style = plotter.default_styles["comparison"][mod1(i, length(plotter.default_styles["comparison"]))] + label = "Numerical ($(uppercase(res["config"]["scheme"]))$(res["config"]["order"]))" + plt.plot( + res["x"], res["numerical"]; + label=label, + style..., + markersize=5, linewidth=0.5 + ) + end + + # 绘制解析解 + plt.plot( + result_list[1]["x"], result_list[1]["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +function _set_common_style(plotter::CFDPlotter, title::String) + plt.title(title, fontsize=12) + plt.xlabel("x", fontsize=10) + plt.ylabel("u", fontsize=10) + plt.legend(fontsize=9) + plt.grid(true, color="gray", linestyle="--", linewidth=0.5, alpha=0.7) + plt.tight_layout() +end + +""" +快捷函数:ENO/WENO对比绘图 +""" +function plot_eno_weno_comparison(eno_result::Dict, weno_result::Dict; save_path=nothing) + plotter = CFDPlotter() + plot_comparison(plotter, [eno_result, weno_result]; save_path=save_path) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02g/reconstructor/eno.jl b/example/1d-linear-convection/weno3/julia/02g/reconstructor/eno.jl new file mode 100644 index 000000000..e78a636f3 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02g/reconstructor/eno.jl @@ -0,0 +1,107 @@ +# julia/reconstructor/eno.jl +""" +ENO 重构器(与 reconstructor/eno.py 完全同构) +""" + +# ---------------------- ENO 系数初始化 ---------------------- +function _init_eno_coef!(spatial_order::Int, coef::Matrix{Float64}) + if spatial_order == 1 + coef[1, 1] = 1.0 + coef[2, 1] = 1.0 + elseif spatial_order == 2 + coef[1, 1:2] = [3.0/2.0, -1.0/2.0] + coef[2, 1:2] = [1.0/2.0, 1.0/2.0] + coef[3, 1:2] = [-1.0/2.0, 3.0/2.0] + elseif spatial_order == 3 + coef[1, 1:3] = [11.0/6.0, -7.0/6.0, 1.0/3.0] + coef[2, 1:3] = [1.0/3.0, 5.0/6.0, -1.0/6.0] + coef[3, 1:3] = [-1.0/6.0, 5.0/6.0, 1.0/3.0] + coef[4, 1:3] = [1.0/3.0, -7.0/6.0, 11.0/6.0] + elseif spatial_order == 4 + coef[1, 1:4] = [25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0] + coef[2, 1:4] = [1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0] + coef[3, 1:4] = [-1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0] + coef[4, 1:4] = [1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0] + coef[5, 1:4] = [-1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0] + elseif spatial_order == 5 + coef[1, 1:5] = [137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0] + coef[2, 1:5] = [1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0] + coef[3, 1:5] = [-1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0] + coef[4, 1:5] = [1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0] + coef[5, 1:5] = [-1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0] + coef[6, 1:5] = [1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0] + elseif spatial_order == 6 + coef[1, 1:6] = [49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0] + coef[2, 1:6] = [1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0] + coef[3, 1:6] = [-1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0] + coef[4, 1:6] = [1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0] + coef[5, 1:6] = [-1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0] + coef[6, 1:6] = [1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0] + coef[7, 1:6] = [-1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0] + elseif spatial_order == 7 + coef[1, 1:7] = [363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0] + coef[2, 1:7] = [1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0] + coef[3, 1:7] = [-1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0] + coef[4, 1:7] = [1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0] + coef[5, 1:7] = [-1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0] + coef[6, 1:7] = [1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0] + coef[7, 1:7] = [-1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0] + coef[8, 1:7] = [1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0] + else + error("ENO 系数未实现 order=$spatial_order") + end +end + +# ---------------------- ENO 重构器 ---------------------- +mutable struct EnoReconstructor + spatial_order::Int + ntcells::Int + lmc::Vector{Int} + coef::Matrix{Float64} + dd::Matrix{Float64} + + function EnoReconstructor(spatial_order::Int, ntcells::Int) + lmc = zeros(Int, ntcells) + coef = zeros(Float64, spatial_order + 1, spatial_order) + dd = zeros(Float64, spatial_order, ntcells) + _init_eno_coef!(spatial_order, coef) + new(spatial_order, ntcells, lmc, coef, dd) + end +end + +function reconstruct(rec::EnoReconstructor, q::Vector{Float64}, cfd::Any) + # 1. 差商计算 (dd[1,:] = q) + @views rec.dd[1, :] .= q + for m in 2:rec.spatial_order + for j in 1:(rec.ntcells - m + 1) + rec.dd[m, j] = rec.dd[m-1, j+1] - rec.dd[m-1, j] + end + end + + # 2. 选择 smoothest stencil + domain = cfd.domain + for i in (domain.ist - 1):(domain.ied) # Python: range(ist-1, ied+1) → ied+1-1 = ied + rec.lmc[i] = i + for m in 2:rec.spatial_order + if abs(rec.dd[m, rec.lmc[i] - 1]) < abs(rec.dd[m, rec.lmc[i]]) + rec.lmc[i] -= 1 + end + end + end + + # 3. 重构界面值 + solution = cfd.solution + for i in domain.ist:(domain.ied) # Python: range(ist, ied+1) → ied+1-1 = ied + j = i - domain.ist + 1 # Julia 1-based + k1 = rec.lmc[i - 1] + k2 = rec.lmc[i] + r1 = (i - 1) - k1 + 1 + r2 = i - k2 + 1 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in 1:rec.spatial_order + solution.q_face_left[j] += q[k1 + m - 1] * rec.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m - 1] * rec.coef[r2, m] + end + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02g/reconstructor/factory.jl b/example/1d-linear-convection/weno3/julia/02g/reconstructor/factory.jl new file mode 100644 index 000000000..f9c9cb4b3 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02g/reconstructor/factory.jl @@ -0,0 +1,41 @@ +# julia/reconstructor/factory.jl + +""" +ReconstructorFactory +对标 Python: ReconstructorFactory.create(config, domain) +""" +module ReconstructorFactory + +# ✅ 不要用 using ..EnoReconstructor(它们不是模块) +# 直接通过 Main. 引用顶层定义的类型 + +function create(config::Any, domain::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + # 处理 WENO 默认命名(如 Python) + if scheme == "weno" + order = getfield_safe(config, :spatial_order, 5) + scheme = "weno$(order)" + end + + if scheme == "eno" + order = getfield_safe(config, :spatial_order, 3) + return Main.EnoReconstructor(order, domain.ntcells) + elseif scheme == "weno3" + return Main.Weno3Reconstructor() + else + error("不支持的重建格式: $scheme(仅支持 eno/weno3)") + end +end + +# 辅助函数(复制自 domain.jl) +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +end # module ReconstructorFactory + +export ReconstructorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02g/reconstructor/weno3.jl b/example/1d-linear-convection/weno3/julia/02g/reconstructor/weno3.jl new file mode 100644 index 000000000..2b6fe1abb --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02g/reconstructor/weno3.jl @@ -0,0 +1,65 @@ +# julia/reconstructor/weno3.jl +""" +WENO3 重构器(与 reconstructor/weno3.py 完全同构) +""" + +mutable struct Weno3Reconstructor + # 无字段,与 Python 一致 +end + +function reconstruct(rec::Weno3Reconstructor, q::Vector{Float64}, cfd::Any) + domain = cfd.domain + solution = cfd.solution + _reconstruct_left_interfaces(domain, q, solution.q_face_left) + _reconstruct_right_interfaces(domain, q, solution.q_face_right) +end + +function _reconstruct_left_interfaces(domain, u, qL) + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in (domain.ist - 1):(domain.ied - 1) + j = i - (domain.ist - 1) + 1 # ← Julia 1-based: j = i - (ist-1) 对应 Python j = i - (ist-1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = _reconstruct_from_left_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_right_interfaces(domain, u, qR) + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in domain.ist:domain.ied + j = i - domain.ist + 1 # ← Julia 1-based: j = i - ist 对应 Python j = i - ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = _reconstruct_from_right_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_from_left_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -0.5*v1 + 1.5*v2 # r=1 stencil + q1 = 0.5*v2 + 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end + +function _reconstruct_from_right_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 0.5*v1 + 0.5*v2 # r=1 stencil + q1 = 1.5*v2 - 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02g/residual.jl b/example/1d-linear-convection/weno3/julia/02g/residual.jl new file mode 100644 index 000000000..11a885934 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02g/residual.jl @@ -0,0 +1,69 @@ +# julia/residual.jl +""" +残差计算器(与 residual.py 完全同构) +- 封装重建→通量→散度完整流程 +- 依赖 cfd 的多个字段 +""" + +include("mesh.jl") + +mutable struct ResidualCalculator + cfd::Any + config::Any + domain::Any + solution::Any + mesh::Mesh + reconstructor::Any + flux_calculator::Any + + function ResidualCalculator(cfd::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + mesh = domain.mesh + reconstructor = cfd.reconstructor + + # ✅ 内部创建 flux_calculator(对标 Python) + flux_calculator = FluxCalculatorFactory.create(cfd) + + new(cfd, config, domain, solution, mesh, reconstructor, flux_calculator) + end +end + + +""" +计算完整残差(对外唯一接口) +""" +function compute!(calc::ResidualCalculator) + _reconstruct(calc) + _compute_inviscid_flux(calc) + _compute_flux_divergence(calc) +end + +""" +私有方法:界面值重建 +""" +function _reconstruct(calc::ResidualCalculator) + reconstruct(calc.reconstructor, calc.solution.u, calc.cfd) +end + +""" +私有方法:计算无粘通量 +""" +function _compute_inviscid_flux(calc::ResidualCalculator) + compute!(calc.flux_calculator, + calc.solution.q_face_left, + calc.solution.q_face_right, + calc.solution.flux) +end + +""" +私有方法:计算通量散度(残差 = -dF/dx) +""" +function _compute_flux_divergence(calc::ResidualCalculator) + solution = calc.solution + mesh = calc.mesh + for i in 1:mesh.ncells + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / mesh.dx + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02g/run_eno_weno.jl b/example/1d-linear-convection/weno3/julia/02g/run_eno_weno.jl new file mode 100644 index 000000000..58b6cfa5f --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02g/run_eno_weno.jl @@ -0,0 +1,50 @@ +# julia/run_eno_weno.jl +""" +1:1 复刻 run_eno_weno.py 的 Julia 版本 +""" + +include("config.jl") +include("mesh.jl") +include("solver.jl") +include("plotter.jl") + +function performEnoWenoAnalysis() + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO3 求解 + println("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + with_reconstruction(config_eno3, "eno", 3) # 显式指定 3 阶 + config_eno3.dt = 0.0025 # 覆盖默认值 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + run!(cfd_eno3) # 求解并生成 result 字典 + + # 3. 配置并运行 WENO3 求解 + println("Running WENO3 solver...") + config_weno3 = CfdConfig() + with_reconstruction(config_weno3, "weno", 3) # 显式指定 3 阶(WENO 默认 5 阶) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + run!(cfd_weno3) + + # 5. 绘制 ENO/WENO 对比图 + println("Plotting comparison results...") + plot_eno_weno_comparison( + cfd_eno3.result, + cfd_weno3.result; + save_path="eno_weno_comparison.png" + ) + + return cfd_eno3, cfd_weno3 +end + +# 主程序入口 +if abspath(PROGRAM_FILE) == @__FILE__ + performEnoWenoAnalysis() + println("Analysis completed!") +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02g/solution.jl b/example/1d-linear-convection/weno3/julia/02g/solution.jl new file mode 100644 index 000000000..d1f24e687 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02g/solution.jl @@ -0,0 +1,64 @@ +# julia/solution.jl +""" +Solution 结构体(与真实 solution.py 完全同构) +字段顺序、方法、逻辑 1:1 对齐 +""" + +include("mesh.jl") +include("domain.jl") +include("initial_condition.jl") + +mutable struct Solution + domain::Domain + q_face_left::Vector{Float64} + q_face_right::Vector{Float64} + flux::Vector{Float64} + res::Vector{Float64} + u::Vector{Float64} + un::Vector{Float64} + + function Solution(config::Any, domain::Domain) + mesh = domain.mesh + + q_face_left = zeros(Float64, mesh.nnodes) + q_face_right = zeros(Float64, mesh.nnodes) + flux = zeros(Float64, mesh.nnodes) + res = zeros(Float64, mesh.ncells) + u = zeros(Float64, domain.ntcells) + un = zeros(Float64, domain.ntcells) + + sol = new(domain, q_face_left, q_face_right, flux, res, u, un) + initialize_from_config(sol, config) + return sol + end +end + +""" +重置解数组为初始状态 +""" +function reset_solution(sol::Solution) + fill!(sol.u, 0.0) + fill!(sol.un, 0.0) +end + +""" +根据配置初始化场 +""" + +function initialize_from_config(sol::Solution, config::Any) + ic_type = getfield_safe(config, :ic_type, "step") + ic = InitialConditionFactory.create(ic_type, config) + apply(ic, sol) +end + +""" +更新旧场:un = u +""" +function update_old_field(sol::Solution) + sol.un .= sol.u # 等价于 Python 的 un[:] = u[:] +end + +# ---------------------- 辅助函数 ---------------------- +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02g/solver.jl b/example/1d-linear-convection/weno3/julia/02g/solver.jl new file mode 100644 index 000000000..d8193f0e8 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02g/solver.jl @@ -0,0 +1,130 @@ +# julia/solver.jl +""" +CFD 求解器主类(与 solver.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("initial_condition.jl") +include("boundary.jl") +include("flux.jl") +include("residual.jl") +include("time_integration.jl") +include("reconstructor/eno.jl") +include("reconstructor/weno3.jl") +include("reconstructor/factory.jl") + +# 导入工厂模块(必须在顶层!) +using .FluxCalculatorFactory +using .TimeIntegratorFactory +using .BoundaryConditionFactory +using .ReconstructorFactory + +# ---------------------- Cfd 求解器 ---------------------- +mutable struct Cfd + config::Any + domain::Domain + solution::Solution + reconstructor::Any + residual_calculator::ResidualCalculator + integrator::Any + boundary_condition::Any + result::Dict{String, Any} + + function Cfd(config::Any, mesh::Mesh) + domain = Domain(config, mesh) + solution = Solution(config, domain) + reconstructor = ReconstructorFactory.create(config, domain) + + # 1. 初始上下文(仅包含不依赖其他组件的字段) + full_cfd = ( + config = config, + domain = domain, + solution = solution, + reconstructor = reconstructor + # 注意:不预先占位 nothing! + ) + + # 2. 创建 boundary_condition(只依赖 config + domain) + boundary_condition = BoundaryConditionFactory.create(full_cfd) + full_cfd = merge(full_cfd, (boundary_condition = boundary_condition,)) + + # 3. 创建 residual_calculator(依赖上面所有字段) + residual_calculator = ResidualCalculator(full_cfd) + full_cfd = merge(full_cfd, (residual_calculator = residual_calculator,)) + + # 4. 创建 integrator(依赖 residual_calculator 等) + integrator = TimeIntegratorFactory.create(full_cfd) + full_cfd = merge(full_cfd, (integrator = integrator,)) + + # 5. 注入完整 self 到组件(确保它们能访问彼此) + residual_calculator.cfd = full_cfd + integrator.base.cfd = full_cfd + + result = Dict{String, Any}() + new(config, domain, solution, reconstructor, residual_calculator, integrator, boundary_condition, result) + end +end + +""" +通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界 +""" +function exact_solution(cfd::Cfd) + x = cfd.domain.mesh.xcc + T = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = @. (x - c * T + L) % L + + # ✅ 使用工厂创建初始条件(对标 Python) + ic = InitialConditionFactory.create(cfd.config.ic_type, cfd.config) + + return evaluate_at(ic, x_shifted) +end + +""" +主求解循环 +""" +function run!(cfd::Cfd) + # 应用初始边界条件并同步 old field + apply!(cfd.boundary_condition, cfd.solution.u) + update_old_field(cfd.solution) + + t = 0.0 + dt_old = cfd.config.dt + dt = dt_old + + while t < cfd.config.final_time + if t + dt > cfd.config.final_time + dt = cfd.config.final_time - t + end + #@show t, dt, maximum(cfd.solution.u), minimum(cfd.solution.u) + # 执行时间步 + step(cfd.integrator, dt) + t += dt + end + + # 恢复 dt + cfd.config.dt = dt_old + + # 整理结果 + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied-1] # Python: [ist:ied] + analytical = exact_solution(cfd) + + cfd.result = Dict( + "x" => cfd.domain.mesh.xcc, + "numerical" => u_numerical, + "analytical" => analytical, + "config" => Dict( + "scheme" => cfd.config.recon_scheme, + "order" => cfd.config.spatial_order, + "rk_order" => cfd.config.rk_order, + "final_time" => cfd.config.final_time + ) + ) + + return u_numerical +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/02g/time_integration.jl b/example/1d-linear-convection/weno3/julia/02g/time_integration.jl new file mode 100644 index 000000000..add564ba5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/02g/time_integration.jl @@ -0,0 +1,169 @@ +# julia/time_integration.jl +""" +时间推进器模块(与 time_integration.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("residual.jl") +include("boundary.jl") + +# ---------------------- 抽象时间推进器基类 ---------------------- +abstract type TimeIntegrator end + +mutable struct TimeIntegratorBase <: TimeIntegrator + cfd::Any + config::Any + domain::Domain + solution::Solution + residual_calculator::Any # ResidualCalculator +end + +function TimeIntegratorBase(cfd::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + residual_calculator = cfd.residual_calculator + TimeIntegratorBase(cfd, config, domain, solution, residual_calculator) +end + +function compute_residual(integrator::TimeIntegratorBase) + compute!(integrator.residual_calculator) +end + +function apply_boundary(integrator::TimeIntegratorBase) + apply!(integrator.cfd.boundary_condition, integrator.solution.u) +end + +function map_idx(integrator::TimeIntegratorBase, i::Int) + return i - integrator.domain.ist + 1 # ← +1 转为 1-based +end + +# ---------------------- RK1Integrator ---------------------- +mutable struct RK1Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK1Integrator(cfd::Any) + RK1Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK1Integrator, dt::Float64) + compute_residual(integrator.base) + for i in integrator.base.domain.ist:(integrator.base.domain.ied - 1) + j = map_idx(integrator.base, i) + integrator.base.solution.u[i] += dt * integrator.base.solution.res[j] + end + apply_boundary(integrator.base) + update_old_field(integrator.base.solution) +end + +# ---------------------- RK2Integrator ---------------------- +mutable struct RK2Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK2Integrator(cfd::Any) + RK2Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK2Integrator, dt::Float64) + base = integrator.base + # 阶段1:预测步 + compute_residual(base) + u_pred = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u_pred[i] += dt * base.solution.res[j] + end + base.solution.u .= u_pred + apply_boundary(base) + # 阶段2:校正步 + compute_residual(base) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = 0.5 * base.solution.un[i] + 0.5 * base.solution.u[i] + 0.5 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end + +# ---------------------- RK3Integrator ---------------------- +mutable struct RK3Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK3Integrator(cfd::Any) + RK3Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK3Integrator, dt::Float64) + base = integrator.base + # 阶段1 + compute_residual(base) + u1 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u1[i] += dt * base.solution.res[j] + end + base.solution.u .= u1 + apply_boundary(base) + # 阶段2 + compute_residual(base) + u2 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u2[i] = 0.75 * base.solution.un[i] + 0.25 * base.solution.u[i] + 0.25 * dt * base.solution.res[j] + end + base.solution.u .= u2 + apply_boundary(base) + # 阶段3 + compute_residual(base) + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = c1 * base.solution.un[i] + c2 * base.solution.u[i] + c3 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end + +# ---------------------- TimeIntegratorFactory ---------------------- +module TimeIntegratorFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "时间积分器 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + rk_order = cfd.config.rk_order + if !(rk_order isa Integer) || rk_order < 1 + error("rk_order 必须为正整数,当前值: $rk_order") + end + + name = "rk$rk_order" + if !haskey(_REGISTRY, name) + available = sort(collect(keys(_REGISTRY))) + error("未注册的时间积分器: '$name'。可用选项: $available") + end + + return _REGISTRY[name](cfd) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("rk1", cfd -> Main.RK1Integrator(cfd)) +register("rk2", cfd -> Main.RK2Integrator(cfd)) +register("rk3", cfd -> Main.RK3Integrator(cfd)) + +end # module TimeIntegratorFactory + +export TimeIntegratorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03/examples/run_eno_weno.jl b/example/1d-linear-convection/weno3/julia/03/examples/run_eno_weno.jl new file mode 100644 index 000000000..da24e6cd7 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03/examples/run_eno_weno.jl @@ -0,0 +1,50 @@ +# examples/run_eno_weno.jl +""" +1:1 复刻 run_eno_weno.py 的 Julia 版本 +""" + +include("../src/config.jl") +include("../src/mesh.jl") +include("../src/solver.jl") +include("../src/plotter.jl") + +function performEnoWenoAnalysis() + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO3 求解 + println("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + with_reconstruction(config_eno3, "eno", 3) # 显式指定 3 阶 + config_eno3.dt = 0.0025 # 覆盖默认值 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + run!(cfd_eno3) # 求解并生成 result 字典 + + # 3. 配置并运行 WENO3 求解 + println("Running WENO3 solver...") + config_weno3 = CfdConfig() + with_reconstruction(config_weno3, "weno", 3) # 显式指定 3 阶(WENO 默认 5 阶) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + run!(cfd_weno3) + + # 5. 绘制 ENO/WENO 对比图 + println("Plotting comparison results...") + plot_eno_weno_comparison( + cfd_eno3.result, + cfd_weno3.result; + save_path="eno_weno_comparison.png" + ) + + return cfd_eno3, cfd_weno3 +end + +# 主程序入口 +if abspath(PROGRAM_FILE) == @__FILE__ + performEnoWenoAnalysis() + println("Analysis completed!") +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03/src/boundary.jl b/example/1d-linear-convection/weno3/julia/03/src/boundary.jl new file mode 100644 index 000000000..82f58b71f --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03/src/boundary.jl @@ -0,0 +1,121 @@ +# julia/boundary.jl +# 目标:与 Python boundary.py 行为完全一致(1:1 同构) +# 前提:ist/ied 在 Julia 中按 1-based 设置(ist = nghosts + 1) + +#using NPZ # 仅用于测试,实际模块可不依赖 + +# ---------------------- 抽象基类(Julia 风格:用函数接口) ---------------------- +# Julia 无 abstract class,用文档+约定 +# 所有子类型必须实现 apply!(bc, u) + +# ---------------------- PeriodicBoundary ---------------------- +mutable struct PeriodicBoundary + cfd::Any +end + +function apply!(self::PeriodicBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist # 1-based, e.g., 3 + ied = self.cfd.domain.ied # 1-based, e.g., 43 + + # 左 ghost: u[ist - 1 - ig] = u[ied - 1 - ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ied - 1 - ig] + end + # 右 ghost: u[ied + ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ied + ig] = u[ist + ig] + end +end + +# ---------------------- DirichletBoundary ---------------------- +mutable struct DirichletBoundary + cfd::Any +end + +function apply!(self::DirichletBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + left_value = getfield_safe(self.cfd.config, :left_boundary_value, 1.0) + right_value = getfield_safe(self.cfd.config, :right_boundary_value, 2.0) + + # 左边界 + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = left_value + end + # 右边界 + for ig in 0:(nghosts-1) + u[ied + ig] = right_value + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Dirichlet边界: 左值=$left_value, 右值=$right_value") + end +end + +# ---------------------- NeumannBoundary ---------------------- +mutable struct NeumannBoundary + cfd::Any +end + +function apply!(self::NeumannBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + # 左边界零梯度:u[ist - 1 - ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ist + ig] + end + # 右边界零梯度:u[ied + ig] = u[ied - 1 - ig] ← 注意是 ied - 1 - ig + for ig in 0:(nghosts-1) + u[ied + ig] = u[ied - 1 - ig] + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Neumann边界: 零梯度") + end +end + + +# ---------------------- BoundaryConditionFactory ---------------------- +module BoundaryConditionFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "边界条件 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + bc_type = cfd.config.boundary_type + if !(bc_type isa AbstractString) + error("boundary_type 必须为字符串,当前值: $bc_type") + end + + if !haskey(_REGISTRY, bc_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的边界条件: '$bc_type'。可用选项: $available") + end + + return _REGISTRY[bc_type](cfd) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("periodic", cfd -> Main.PeriodicBoundary(cfd)) +register("dirichlet", cfd -> Main.DirichletBoundary(cfd)) +register("neumann", cfd -> Main.NeumannBoundary(cfd)) + +end # module BoundaryConditionFactory + +export BoundaryConditionFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03/src/config.jl b/example/1d-linear-convection/weno3/julia/03/src/config.jl new file mode 100644 index 000000000..4b9494c49 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03/src/config.jl @@ -0,0 +1,76 @@ +# julia/config.jl +""" +CfdConfig:与 Python config.py 完全同构 +""" +mutable struct CfdConfig + ic_type::String + recon_scheme::String + flux_type::String + rk_order::Int + wave_speed::Float64 + final_time::Float64 + dt::Float64 + boundary_type::String + left_boundary_value::Float64 + right_boundary_value::Float64 + spatial_order::Int + + function CfdConfig() + new( + "step", + "eno", + "rusanov", + 1, + 1.0, + 0.625, + 0.025, + "periodic", + 1.0, + 2.0, + 2 + ) + end +end + +""" +专用配置:重建方案(链式调用) +""" +function with_reconstruction(cfg::CfdConfig, scheme::String, order::Union{Int, Nothing}=nothing) + cfg.recon_scheme = lowercase(scheme) + + if order !== nothing + cfg.spatial_order = order + else + if startswith(cfg.recon_scheme, "weno") + cfg.spatial_order = 5 + elseif cfg.recon_scheme == "eno" + cfg.spatial_order = 3 + else + error("不支持的重建格式:$scheme(仅支持 eno/weno)") + end + end + + return cfg # 支持链式调用 +end + +""" +专用配置:边界条件(链式调用) +""" +function with_boundary(cfg::CfdConfig, bc_type::String; left_value=nothing, right_value=nothing) + cfg.boundary_type = bc_type + if left_value !== nothing + cfg.left_boundary_value = left_value + end + if right_value !== nothing + cfg.right_boundary_value = right_value + end + return cfg +end + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03/src/domain.jl b/example/1d-linear-convection/weno3/julia/03/src/domain.jl new file mode 100644 index 000000000..a06526b80 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03/src/domain.jl @@ -0,0 +1,57 @@ +# julia/domain.jl +""" +Domain 结构体(与 domain.py 完全同构) +- 保存 config +- nghosts 计算逻辑 1:1 +- ist/ied 为 0-based(与 Python 一致) +""" + +include("mesh.jl") + +mutable struct Domain + config::Any # 保存 config(与 Python 一致) + mesh::Mesh + nghosts::Int + ist::Int # 0-based + ied::Int # 0-based + ntcells::Int +end + +function Domain(config::Any, mesh::Mesh) + nghosts = _calc_nghosts(config) + ist = nghosts + 1 # ← 1-based 起始索引 + ied = ist + mesh.ncells # ← 1-based 结束索引(不包含) + ntcells = mesh.ncells + 2 * nghosts + Domain(config, mesh, nghosts, ist, ied, ntcells) +end + +function _calc_nghosts(config::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + order = getfield_safe(config, :spatial_order, 2) + + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + if scheme == "eno" + nghosts = order + elseif startswith(scheme, "weno") + nghosts = order ÷ 2 + 1 + else + error("未知重建格式 $(scheme),无法计算ghost层!") + end + + if nghosts <= 0 + error("计算得到的ghost层数量无效:$(nghosts)(阶数$(order),格式$(scheme))") + end + + return nghosts +end + +function is_physical_cell(domain::Domain, idx::Int) + return domain.ist <= idx < domain.ied +end + +function get_physical_indices(domain::Domain) + return domain.ist:(domain.ied - 1) +end diff --git a/example/1d-linear-convection/weno3/julia/03/src/flux.jl b/example/1d-linear-convection/weno3/julia/03/src/flux.jl new file mode 100644 index 000000000..cff235824 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03/src/flux.jl @@ -0,0 +1,112 @@ +# julia/flux.jl +""" +通量计算器模块(与 flux.py 完全同构) +- 抽象基类 + 具体实现 +- 字段:cfd, config, mesh, wave_speed +""" + +include("mesh.jl") + +# ---------------------- 抽象基类 ---------------------- +""" +InviscidFluxCalculator 抽象类型 +Julia 无 ABC,用文档约定 +所有子类型必须实现 compute! +""" +abstract type InviscidFluxCalculator end + +# ---------------------- RusanovFluxCalculator ---------------------- +mutable struct RusanovFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function RusanovFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::RusanovFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = calc.wave_speed + c_R = calc.wave_speed + F_L = c_L * u_L + F_R = c_R * u_R + Smax = max(abs(c_L), abs(c_R)) + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + end +end + +# ---------------------- EngquistOsherFluxCalculator ---------------------- +mutable struct EngquistOsherFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function EngquistOsherFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::EngquistOsherFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + c = calc.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + end +end + +# ---------------------- FluxCalculatorFactory ---------------------- +module FluxCalculatorFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "通量计算器 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + config = cfd.config + if !hasproperty(config, :flux_type) + error("cfd.config 缺少 flux_type 字段") + end + + flux_type = config.flux_type + if !(flux_type isa AbstractString) + error("flux_type 必须为字符串,当前值: $flux_type") + end + + if !haskey(_REGISTRY, flux_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的通量计算器: '$flux_type'。可用选项: $available") + end + + return _REGISTRY[flux_type](cfd) +end + +# ✅ 修正:使用 Main. 前缀 +register("rusanov", cfd -> Main.RusanovFluxCalculator(cfd)) +register("engquist-osher", cfd -> Main.EngquistOsherFluxCalculator(cfd)) + +end # module FluxCalculatorFactory + +export FluxCalculatorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03/src/initial_condition.jl b/example/1d-linear-convection/weno3/julia/03/src/initial_condition.jl new file mode 100644 index 000000000..1b9993aa8 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03/src/initial_condition.jl @@ -0,0 +1,112 @@ +# julia/initial_condition.jl +""" +初始条件模块(Julia 版) +目标:与 Python initial_condition.py 行为完全一致 +""" + +# ---------------------- 抽象接口(Julia 无继承,用函数约定) ---------------------- +# 所有初始条件必须实现: +# evaluate_at(ic, x::AbstractVector{Float64}) -> Vector{Float64} +# apply(ic, solution) + +# ---------------------- StepFunctionIC ---------------------- +struct StepFunctionIC + config::Any +end + +function evaluate_at(ic::StepFunctionIC, x::AbstractVector{Float64}) + u0 = ones(Float64, length(x)) + for i in eachindex(x) + if 0.5 <= x[i] <= 1.0 + u0[i] = 2.0 + end + end + return u0 +end + +function apply(ic::StepFunctionIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- SineWaveIC ---------------------- +struct SineWaveIC + config::Any +end + +function evaluate_at(ic::SineWaveIC, x::AbstractVector{Float64}) + L = getfield_safe(ic.config, :domain_length, 2.0) + return sin.(2π * x / L) +end + +function apply(ic::SineWaveIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- GaussianPulseIC ---------------------- +struct GaussianPulseIC + config::Any +end + +function evaluate_at(ic::GaussianPulseIC, x::AbstractVector{Float64}) + center = getfield_safe(ic.config, :pulse_center, 0.5) + width = getfield_safe(ic.config, :pulse_width, 0.1) + return exp.(-((x .- center) ./ width).^2) +end + +function apply(ic::GaussianPulseIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- 公共辅助函数 ---------------------- +""" +将初始场 values 应用到 solution.u 的物理区域(含 ghost 的数组) +""" +function _apply_to_interior(solution, values) + domain = solution.domain + # Python: for i in range(domain.ist, domain.ied) + # Julia: for i in domain.ist:domain.ied-1 (因为 ied 是结束索引,不包含) + for i in domain.ist:(domain.ied - 1) + j = i - domain.ist + 1 # values 是 1-based,i - ist 是 0-based → +1 + solution.u[i] = values[j] + end +end + +# ---------------------- InitialConditionFactory ---------------------- +module InitialConditionFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "初始条件 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(ic_type::String, config::Any) + if !(ic_type isa AbstractString) + error("ic_type 必须为字符串,当前值: $ic_type") + end + + if !haskey(_REGISTRY, ic_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的初始条件: '$ic_type'。可用选项: $available") + end + + return _REGISTRY[ic_type](config) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("step", config -> Main.StepFunctionIC(config)) +register("sin", config -> Main.SineWaveIC(config)) +register("gaussian", config -> Main.GaussianPulseIC(config)) + +end # module InitialConditionFactory + +export InitialConditionFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03/src/mesh.jl b/example/1d-linear-convection/weno3/julia/03/src/mesh.jl new file mode 100644 index 000000000..1b0dd4bf9 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03/src/mesh.jl @@ -0,0 +1,45 @@ +# julia/mesh.jl +""" +Mesh 结构体(与提供的 mesh.py 完全同构) +- 字段名、初始化顺序、循环逻辑完全一致 +- 不做任何泛化或优化 +""" + +mutable struct Mesh + xmin::Float64 + xmax::Float64 + ncells::Int + nnodes::Int + nx::Int + x::Vector{Float64} + xcc::Vector{Float64} + L::Float64 # 在 init_mesh 中赋值 + dx::Float64 # 在 init_mesh 中赋值 + + function Mesh() + # 与 Python __init__ 完全一致 + xmin = 0.0 + xmax = 2.0 + ncells = 40 + nnodes = ncells + 1 + nx = ncells + x = zeros(Float64, nnodes) + xcc = zeros(Float64, ncells) + + # 调用 init_mesh(模拟 Python) + L = xmax - xmin + dx = L / ncells + + # Generate node coordinates: for i in range(self.nnodes) + for i in 1:nnodes + x[i] = xmin + (i - 1) * dx # Python i → Julia i-1 + end + + # Generate cell center coordinates + for i in 1:ncells + xcc[i] = 0.5 * (x[i] + x[i+1]) + end + + new(xmin, xmax, ncells, nnodes, nx, x, xcc, L, dx) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03/src/plotter.jl b/example/1d-linear-convection/weno3/julia/03/src/plotter.jl new file mode 100644 index 000000000..a77d8d688 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03/src/plotter.jl @@ -0,0 +1,147 @@ +# julia/plotter.jl +""" +CFDPlotter 的 Julia 实现(通过 PythonCall.jl 调用 Matplotlib) +确保与 Python plotter.py 行为完全一致 +""" + +using PythonCall + +# 初始化 Python 环境(加载 matplotlib, inflect) +const plt = pyimport("matplotlib.pyplot") +const inflect = pyimport("inflect") + +mutable struct CFDPlotter + default_styles::Dict{String, Any} + p::Py +end + +function CFDPlotter() + default_styles = Dict{String, Any}( + "numerical" => Dict( + :color => "blue", + :linestyle => "-", + :marker => "o", + :markerfacecolor => "none" + ), + "analytical" => Dict( + :color => "red", + :linestyle => "--", + :marker => "", + :linewidth => 1.5 + ), + "comparison" => [ + Dict(:color => "black", :linestyle => "-", :marker => "o", :markerfacecolor => "none"), + Dict(:color => "blue", :linestyle => "--", :marker => "s", :markerfacecolor => "none"), + Dict(:color => "green", :linestyle => ":", :marker => "^", :markerfacecolor => "none") + ] + ) + p = inflect.engine() + CFDPlotter(default_styles, p) +end + +""" +轻量即时绘图(快速验证结果) +""" +function plot_quick(plotter::CFDPlotter, cfd_result::Dict; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + rk_order = cfd_result["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = cfd_result["config"]["final_time"] + order = cfd_result["config"]["order"] + scheme = uppercase(cfd_result["config"]["scheme"]) + actual_title = "1D Convection (t=$(final_time))\n$(order)th-order $(scheme) + $(rk_str)-order RK" + end + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"]; + label="Numerical ($(uppercase(cfd_result["config"]["scheme"])))", + plotter.default_styles["numerical"]..., + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + # 通用样式 + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +""" +多格式/多精度对比绘图 +""" +function plot_comparison(plotter::CFDPlotter, result_list::Vector{Dict{String, Any}}; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + schemes = [uppercase(r["config"]["scheme"]) * string(r["config"]["order"]) for r in result_list] + rk_order = result_list[1]["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = result_list[1]["config"]["final_time"] + actual_title = "1D Convection Comparison (t=$(final_time))\n$(join(schemes, ", ")) + $(rk_str)-order RK" + end + + # 绘制多个数值解 + for (i, res) in enumerate(result_list) + style = plotter.default_styles["comparison"][mod1(i, length(plotter.default_styles["comparison"]))] + label = "Numerical ($(uppercase(res["config"]["scheme"]))$(res["config"]["order"]))" + plt.plot( + res["x"], res["numerical"]; + label=label, + style..., + markersize=5, linewidth=0.5 + ) + end + + # 绘制解析解 + plt.plot( + result_list[1]["x"], result_list[1]["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +function _set_common_style(plotter::CFDPlotter, title::String) + plt.title(title, fontsize=12) + plt.xlabel("x", fontsize=10) + plt.ylabel("u", fontsize=10) + plt.legend(fontsize=9) + plt.grid(true, color="gray", linestyle="--", linewidth=0.5, alpha=0.7) + plt.tight_layout() +end + +""" +快捷函数:ENO/WENO对比绘图 +""" +function plot_eno_weno_comparison(eno_result::Dict, weno_result::Dict; save_path=nothing) + plotter = CFDPlotter() + plot_comparison(plotter, [eno_result, weno_result]; save_path=save_path) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03/src/reconstructor/eno.jl b/example/1d-linear-convection/weno3/julia/03/src/reconstructor/eno.jl new file mode 100644 index 000000000..e78a636f3 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03/src/reconstructor/eno.jl @@ -0,0 +1,107 @@ +# julia/reconstructor/eno.jl +""" +ENO 重构器(与 reconstructor/eno.py 完全同构) +""" + +# ---------------------- ENO 系数初始化 ---------------------- +function _init_eno_coef!(spatial_order::Int, coef::Matrix{Float64}) + if spatial_order == 1 + coef[1, 1] = 1.0 + coef[2, 1] = 1.0 + elseif spatial_order == 2 + coef[1, 1:2] = [3.0/2.0, -1.0/2.0] + coef[2, 1:2] = [1.0/2.0, 1.0/2.0] + coef[3, 1:2] = [-1.0/2.0, 3.0/2.0] + elseif spatial_order == 3 + coef[1, 1:3] = [11.0/6.0, -7.0/6.0, 1.0/3.0] + coef[2, 1:3] = [1.0/3.0, 5.0/6.0, -1.0/6.0] + coef[3, 1:3] = [-1.0/6.0, 5.0/6.0, 1.0/3.0] + coef[4, 1:3] = [1.0/3.0, -7.0/6.0, 11.0/6.0] + elseif spatial_order == 4 + coef[1, 1:4] = [25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0] + coef[2, 1:4] = [1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0] + coef[3, 1:4] = [-1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0] + coef[4, 1:4] = [1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0] + coef[5, 1:4] = [-1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0] + elseif spatial_order == 5 + coef[1, 1:5] = [137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0] + coef[2, 1:5] = [1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0] + coef[3, 1:5] = [-1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0] + coef[4, 1:5] = [1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0] + coef[5, 1:5] = [-1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0] + coef[6, 1:5] = [1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0] + elseif spatial_order == 6 + coef[1, 1:6] = [49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0] + coef[2, 1:6] = [1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0] + coef[3, 1:6] = [-1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0] + coef[4, 1:6] = [1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0] + coef[5, 1:6] = [-1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0] + coef[6, 1:6] = [1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0] + coef[7, 1:6] = [-1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0] + elseif spatial_order == 7 + coef[1, 1:7] = [363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0] + coef[2, 1:7] = [1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0] + coef[3, 1:7] = [-1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0] + coef[4, 1:7] = [1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0] + coef[5, 1:7] = [-1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0] + coef[6, 1:7] = [1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0] + coef[7, 1:7] = [-1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0] + coef[8, 1:7] = [1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0] + else + error("ENO 系数未实现 order=$spatial_order") + end +end + +# ---------------------- ENO 重构器 ---------------------- +mutable struct EnoReconstructor + spatial_order::Int + ntcells::Int + lmc::Vector{Int} + coef::Matrix{Float64} + dd::Matrix{Float64} + + function EnoReconstructor(spatial_order::Int, ntcells::Int) + lmc = zeros(Int, ntcells) + coef = zeros(Float64, spatial_order + 1, spatial_order) + dd = zeros(Float64, spatial_order, ntcells) + _init_eno_coef!(spatial_order, coef) + new(spatial_order, ntcells, lmc, coef, dd) + end +end + +function reconstruct(rec::EnoReconstructor, q::Vector{Float64}, cfd::Any) + # 1. 差商计算 (dd[1,:] = q) + @views rec.dd[1, :] .= q + for m in 2:rec.spatial_order + for j in 1:(rec.ntcells - m + 1) + rec.dd[m, j] = rec.dd[m-1, j+1] - rec.dd[m-1, j] + end + end + + # 2. 选择 smoothest stencil + domain = cfd.domain + for i in (domain.ist - 1):(domain.ied) # Python: range(ist-1, ied+1) → ied+1-1 = ied + rec.lmc[i] = i + for m in 2:rec.spatial_order + if abs(rec.dd[m, rec.lmc[i] - 1]) < abs(rec.dd[m, rec.lmc[i]]) + rec.lmc[i] -= 1 + end + end + end + + # 3. 重构界面值 + solution = cfd.solution + for i in domain.ist:(domain.ied) # Python: range(ist, ied+1) → ied+1-1 = ied + j = i - domain.ist + 1 # Julia 1-based + k1 = rec.lmc[i - 1] + k2 = rec.lmc[i] + r1 = (i - 1) - k1 + 1 + r2 = i - k2 + 1 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in 1:rec.spatial_order + solution.q_face_left[j] += q[k1 + m - 1] * rec.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m - 1] * rec.coef[r2, m] + end + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03/src/reconstructor/factory.jl b/example/1d-linear-convection/weno3/julia/03/src/reconstructor/factory.jl new file mode 100644 index 000000000..ac1ea1c87 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03/src/reconstructor/factory.jl @@ -0,0 +1,41 @@ +# julia/reconstructor/factory.jl + +""" +ReconstructorFactory +对标 Python: ReconstructorFactory.create(config, domain) +""" +module ReconstructorFactory + +# ✅ 不要用 using ..EnoReconstructor(它们不是模块) +# 直接通过 Main. 引用顶层定义的类型 + +function create(config::Any, domain::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + # 处理 WENO 默认命名(如 Python) + if scheme == "weno" + order = getfield_safe(config, :spatial_order, 5) + scheme = "weno$(order)" + end + + if scheme == "eno" + order = getfield_safe(config, :spatial_order, 3) + return Main.EnoReconstructor(order, domain.ntcells) + elseif scheme == "weno3" + return Main.Weno3Reconstructor() + else + error("不支持的重建格式: $scheme(仅支持 eno/weno3)") + end +end + +# ---------------------- 辅助函数 ---------------------- +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +end # module ReconstructorFactory + +export ReconstructorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03/src/reconstructor/weno3.jl b/example/1d-linear-convection/weno3/julia/03/src/reconstructor/weno3.jl new file mode 100644 index 000000000..2b6fe1abb --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03/src/reconstructor/weno3.jl @@ -0,0 +1,65 @@ +# julia/reconstructor/weno3.jl +""" +WENO3 重构器(与 reconstructor/weno3.py 完全同构) +""" + +mutable struct Weno3Reconstructor + # 无字段,与 Python 一致 +end + +function reconstruct(rec::Weno3Reconstructor, q::Vector{Float64}, cfd::Any) + domain = cfd.domain + solution = cfd.solution + _reconstruct_left_interfaces(domain, q, solution.q_face_left) + _reconstruct_right_interfaces(domain, q, solution.q_face_right) +end + +function _reconstruct_left_interfaces(domain, u, qL) + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in (domain.ist - 1):(domain.ied - 1) + j = i - (domain.ist - 1) + 1 # ← Julia 1-based: j = i - (ist-1) 对应 Python j = i - (ist-1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = _reconstruct_from_left_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_right_interfaces(domain, u, qR) + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in domain.ist:domain.ied + j = i - domain.ist + 1 # ← Julia 1-based: j = i - ist 对应 Python j = i - ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = _reconstruct_from_right_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_from_left_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -0.5*v1 + 1.5*v2 # r=1 stencil + q1 = 0.5*v2 + 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end + +function _reconstruct_from_right_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 0.5*v1 + 0.5*v2 # r=1 stencil + q1 = 1.5*v2 - 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03/src/registry.jl b/example/1d-linear-convection/weno3/julia/03/src/registry.jl new file mode 100644 index 000000000..6acd9aaaf --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03/src/registry.jl @@ -0,0 +1,164 @@ +""" +统一注册系统 + 通用工厂 +- ComponentRegistry: 组件注册表 +- @register_component: 装饰器宏 +- BaseFactory: 通用工厂接口 +""" + +module ComponentRegistry + +# 注册表:Dict{category => Dict{name => constructor}} +const _REGISTRIES = Dict{String, Dict{String, Function}}() +const _VERBOSE = Ref(true) + +# ---------------------- 注册核心逻辑 ---------------------- +""" + register(category::String, name::String, ctor::Function) + +注册一个组件构造函数到指定类别。 +如果已存在同名组件且不同,则发出警告。 +""" +function register(category::String, name::String, ctor::Function) + if !haskey(_REGISTRIES, category) + _REGISTRIES[category] = Dict{String, Function}() + end + + registry = _REGISTRIES[category] + if haskey(registry, name) + if registry[name] !== ctor && _VERBOSE[] + @warn "覆盖注册: $category.$name" + end + end + + registry[name] = ctor + if _VERBOSE[] + println("✅ 已注册: $category.$name -> $(nameof(ctor))") + end +end + +""" + create(category::String, name::String, args...; kwargs...) + +从注册表创建组件实例。 +""" +function create(category::String, name::String, args...; kwargs...) + if !haskey(_REGISTRIES, category) + error("❌ 未知类别: $category (可用: $(collect(keys(_REGISTRIES))))") + end + + registry = _REGISTRIES[category] + lname = lowercase(name) + if !haskey(registry, lname) + available = sort(collect(keys(registry))) + error("❌ 未找到: $category.$name (可用: $available)") + end + + return registry[lname](args...; kwargs...) +end + +""" + list_all() + +返回所有已注册组件(按类别)。 +""" +function list_all() + return Dict(cat => sort(collect(keys(reg))) for (cat, reg) in _REGISTRIES) +end + +""" + set_verbose(flag::Bool) + +开启/关闭注册提示。 +""" +function set_verbose(flag::Bool) + _VERBOSE[] = flag +end + +end # module ComponentRegistry + + +# ---------------------- 装饰器宏:@register_component ---------------------- +""" +@register_component(category, [name]) + +用法: + @register_component("boundary", "periodic") + struct PeriodicBoundary ... + +若省略 name,则使用类型名的小写形式。 +""" +macro register_component(category::String, name_expr) + error("@register_component 需在类型定义前使用,且必须在模块顶层") +end + +macro register_component(category::String) + error("@register_component(category, name) 需指定 name 或在类型后使用") +end + +# 重载:@register_component("category", "name") struct X ... end +macro register_component(category::String, name::String, ex) + if !Meta.isexpr(ex, :struct) + error("@register_component 必须作用于 struct 定义") + end + + struct_name = ex.args[2] + if Meta.isexpr(struct_name, :curly) + struct_name = struct_name.args[1] + end + + # 插入注册调用(在模块顶层) + quote + $(esc(ex)) + $(ComponentRegistry).register($(category), $(name), $(esc(struct_name))) + end +end + +# 重载:@register_component("category") struct X ... end → name = lowercase(nameof(X)) +macro register_component(category::String, ex) + if !Meta.isexpr(ex, :struct) + error("@register_component 必须作用于 struct 定义") + end + + struct_name = ex.args[2] + if Meta.isexpr(struct_name, :curly) + struct_name = struct_name.args[1] + end + + name_str = string(struct_name) |> lowercase + + quote + $(esc(ex)) + $(ComponentRegistry).register($(category), $(name_str), $(esc(struct_name))) + end +end + + +# ---------------------- 通用工厂 ---------------------- +module BaseFactory + +using ..ComponentRegistry + +""" + create_component(category::String, name::String, args...; kwargs...) + +通用工厂接口,与 Python BaseFactory.create_component 行为一致。 +""" +function create_component(category::String, name::String, args...; kwargs...) + return ComponentRegistry.create(category, name, args...; kwargs...) +end + +""" + get_available_components(category::String) + +列出某类别下所有可用组件。 +""" +function get_available_components(category::String) + all = ComponentRegistry.list_all() + return get(all, category, String[]) +end + +end # module BaseFactory + + +# 导出接口 +export ComponentRegistry, BaseFactory, @register_component \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03/src/residual.jl b/example/1d-linear-convection/weno3/julia/03/src/residual.jl new file mode 100644 index 000000000..11a885934 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03/src/residual.jl @@ -0,0 +1,69 @@ +# julia/residual.jl +""" +残差计算器(与 residual.py 完全同构) +- 封装重建→通量→散度完整流程 +- 依赖 cfd 的多个字段 +""" + +include("mesh.jl") + +mutable struct ResidualCalculator + cfd::Any + config::Any + domain::Any + solution::Any + mesh::Mesh + reconstructor::Any + flux_calculator::Any + + function ResidualCalculator(cfd::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + mesh = domain.mesh + reconstructor = cfd.reconstructor + + # ✅ 内部创建 flux_calculator(对标 Python) + flux_calculator = FluxCalculatorFactory.create(cfd) + + new(cfd, config, domain, solution, mesh, reconstructor, flux_calculator) + end +end + + +""" +计算完整残差(对外唯一接口) +""" +function compute!(calc::ResidualCalculator) + _reconstruct(calc) + _compute_inviscid_flux(calc) + _compute_flux_divergence(calc) +end + +""" +私有方法:界面值重建 +""" +function _reconstruct(calc::ResidualCalculator) + reconstruct(calc.reconstructor, calc.solution.u, calc.cfd) +end + +""" +私有方法:计算无粘通量 +""" +function _compute_inviscid_flux(calc::ResidualCalculator) + compute!(calc.flux_calculator, + calc.solution.q_face_left, + calc.solution.q_face_right, + calc.solution.flux) +end + +""" +私有方法:计算通量散度(残差 = -dF/dx) +""" +function _compute_flux_divergence(calc::ResidualCalculator) + solution = calc.solution + mesh = calc.mesh + for i in 1:mesh.ncells + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / mesh.dx + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03/src/solution.jl b/example/1d-linear-convection/weno3/julia/03/src/solution.jl new file mode 100644 index 000000000..7044c0014 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03/src/solution.jl @@ -0,0 +1,60 @@ +# julia/solution.jl +""" +Solution 结构体(与真实 solution.py 完全同构) +字段顺序、方法、逻辑 1:1 对齐 +""" + +include("mesh.jl") +include("domain.jl") +include("initial_condition.jl") + +mutable struct Solution + domain::Domain + q_face_left::Vector{Float64} + q_face_right::Vector{Float64} + flux::Vector{Float64} + res::Vector{Float64} + u::Vector{Float64} + un::Vector{Float64} + + function Solution(config::Any, domain::Domain) + mesh = domain.mesh + + q_face_left = zeros(Float64, mesh.nnodes) + q_face_right = zeros(Float64, mesh.nnodes) + flux = zeros(Float64, mesh.nnodes) + res = zeros(Float64, mesh.ncells) + u = zeros(Float64, domain.ntcells) + un = zeros(Float64, domain.ntcells) + + sol = new(domain, q_face_left, q_face_right, flux, res, u, un) + initialize_from_config(sol, config) + return sol + end +end + +""" +重置解数组为初始状态 +""" +function reset_solution(sol::Solution) + fill!(sol.u, 0.0) + fill!(sol.un, 0.0) +end + +""" +根据配置初始化场 +""" + +function initialize_from_config(sol::Solution, config::Any) + ic_type = getfield_safe(config, :ic_type, "step") + ic = InitialConditionFactory.create(ic_type, config) + apply(ic, sol) +end + +""" +更新旧场:un = u +""" +function update_old_field(sol::Solution) + sol.un .= sol.u # 等价于 Python 的 un[:] = u[:] +end + diff --git a/example/1d-linear-convection/weno3/julia/03/src/solver.jl b/example/1d-linear-convection/weno3/julia/03/src/solver.jl new file mode 100644 index 000000000..d8193f0e8 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03/src/solver.jl @@ -0,0 +1,130 @@ +# julia/solver.jl +""" +CFD 求解器主类(与 solver.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("initial_condition.jl") +include("boundary.jl") +include("flux.jl") +include("residual.jl") +include("time_integration.jl") +include("reconstructor/eno.jl") +include("reconstructor/weno3.jl") +include("reconstructor/factory.jl") + +# 导入工厂模块(必须在顶层!) +using .FluxCalculatorFactory +using .TimeIntegratorFactory +using .BoundaryConditionFactory +using .ReconstructorFactory + +# ---------------------- Cfd 求解器 ---------------------- +mutable struct Cfd + config::Any + domain::Domain + solution::Solution + reconstructor::Any + residual_calculator::ResidualCalculator + integrator::Any + boundary_condition::Any + result::Dict{String, Any} + + function Cfd(config::Any, mesh::Mesh) + domain = Domain(config, mesh) + solution = Solution(config, domain) + reconstructor = ReconstructorFactory.create(config, domain) + + # 1. 初始上下文(仅包含不依赖其他组件的字段) + full_cfd = ( + config = config, + domain = domain, + solution = solution, + reconstructor = reconstructor + # 注意:不预先占位 nothing! + ) + + # 2. 创建 boundary_condition(只依赖 config + domain) + boundary_condition = BoundaryConditionFactory.create(full_cfd) + full_cfd = merge(full_cfd, (boundary_condition = boundary_condition,)) + + # 3. 创建 residual_calculator(依赖上面所有字段) + residual_calculator = ResidualCalculator(full_cfd) + full_cfd = merge(full_cfd, (residual_calculator = residual_calculator,)) + + # 4. 创建 integrator(依赖 residual_calculator 等) + integrator = TimeIntegratorFactory.create(full_cfd) + full_cfd = merge(full_cfd, (integrator = integrator,)) + + # 5. 注入完整 self 到组件(确保它们能访问彼此) + residual_calculator.cfd = full_cfd + integrator.base.cfd = full_cfd + + result = Dict{String, Any}() + new(config, domain, solution, reconstructor, residual_calculator, integrator, boundary_condition, result) + end +end + +""" +通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界 +""" +function exact_solution(cfd::Cfd) + x = cfd.domain.mesh.xcc + T = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = @. (x - c * T + L) % L + + # ✅ 使用工厂创建初始条件(对标 Python) + ic = InitialConditionFactory.create(cfd.config.ic_type, cfd.config) + + return evaluate_at(ic, x_shifted) +end + +""" +主求解循环 +""" +function run!(cfd::Cfd) + # 应用初始边界条件并同步 old field + apply!(cfd.boundary_condition, cfd.solution.u) + update_old_field(cfd.solution) + + t = 0.0 + dt_old = cfd.config.dt + dt = dt_old + + while t < cfd.config.final_time + if t + dt > cfd.config.final_time + dt = cfd.config.final_time - t + end + #@show t, dt, maximum(cfd.solution.u), minimum(cfd.solution.u) + # 执行时间步 + step(cfd.integrator, dt) + t += dt + end + + # 恢复 dt + cfd.config.dt = dt_old + + # 整理结果 + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied-1] # Python: [ist:ied] + analytical = exact_solution(cfd) + + cfd.result = Dict( + "x" => cfd.domain.mesh.xcc, + "numerical" => u_numerical, + "analytical" => analytical, + "config" => Dict( + "scheme" => cfd.config.recon_scheme, + "order" => cfd.config.spatial_order, + "rk_order" => cfd.config.rk_order, + "final_time" => cfd.config.final_time + ) + ) + + return u_numerical +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03/src/time_integration.jl b/example/1d-linear-convection/weno3/julia/03/src/time_integration.jl new file mode 100644 index 000000000..add564ba5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03/src/time_integration.jl @@ -0,0 +1,169 @@ +# julia/time_integration.jl +""" +时间推进器模块(与 time_integration.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("residual.jl") +include("boundary.jl") + +# ---------------------- 抽象时间推进器基类 ---------------------- +abstract type TimeIntegrator end + +mutable struct TimeIntegratorBase <: TimeIntegrator + cfd::Any + config::Any + domain::Domain + solution::Solution + residual_calculator::Any # ResidualCalculator +end + +function TimeIntegratorBase(cfd::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + residual_calculator = cfd.residual_calculator + TimeIntegratorBase(cfd, config, domain, solution, residual_calculator) +end + +function compute_residual(integrator::TimeIntegratorBase) + compute!(integrator.residual_calculator) +end + +function apply_boundary(integrator::TimeIntegratorBase) + apply!(integrator.cfd.boundary_condition, integrator.solution.u) +end + +function map_idx(integrator::TimeIntegratorBase, i::Int) + return i - integrator.domain.ist + 1 # ← +1 转为 1-based +end + +# ---------------------- RK1Integrator ---------------------- +mutable struct RK1Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK1Integrator(cfd::Any) + RK1Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK1Integrator, dt::Float64) + compute_residual(integrator.base) + for i in integrator.base.domain.ist:(integrator.base.domain.ied - 1) + j = map_idx(integrator.base, i) + integrator.base.solution.u[i] += dt * integrator.base.solution.res[j] + end + apply_boundary(integrator.base) + update_old_field(integrator.base.solution) +end + +# ---------------------- RK2Integrator ---------------------- +mutable struct RK2Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK2Integrator(cfd::Any) + RK2Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK2Integrator, dt::Float64) + base = integrator.base + # 阶段1:预测步 + compute_residual(base) + u_pred = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u_pred[i] += dt * base.solution.res[j] + end + base.solution.u .= u_pred + apply_boundary(base) + # 阶段2:校正步 + compute_residual(base) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = 0.5 * base.solution.un[i] + 0.5 * base.solution.u[i] + 0.5 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end + +# ---------------------- RK3Integrator ---------------------- +mutable struct RK3Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK3Integrator(cfd::Any) + RK3Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK3Integrator, dt::Float64) + base = integrator.base + # 阶段1 + compute_residual(base) + u1 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u1[i] += dt * base.solution.res[j] + end + base.solution.u .= u1 + apply_boundary(base) + # 阶段2 + compute_residual(base) + u2 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u2[i] = 0.75 * base.solution.un[i] + 0.25 * base.solution.u[i] + 0.25 * dt * base.solution.res[j] + end + base.solution.u .= u2 + apply_boundary(base) + # 阶段3 + compute_residual(base) + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = c1 * base.solution.un[i] + c2 * base.solution.u[i] + c3 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end + +# ---------------------- TimeIntegratorFactory ---------------------- +module TimeIntegratorFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "时间积分器 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + rk_order = cfd.config.rk_order + if !(rk_order isa Integer) || rk_order < 1 + error("rk_order 必须为正整数,当前值: $rk_order") + end + + name = "rk$rk_order" + if !haskey(_REGISTRY, name) + available = sort(collect(keys(_REGISTRY))) + error("未注册的时间积分器: '$name'。可用选项: $available") + end + + return _REGISTRY[name](cfd) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("rk1", cfd -> Main.RK1Integrator(cfd)) +register("rk2", cfd -> Main.RK2Integrator(cfd)) +register("rk3", cfd -> Main.RK3Integrator(cfd)) + +end # module TimeIntegratorFactory + +export TimeIntegratorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03a/examples/run_eno_weno.jl b/example/1d-linear-convection/weno3/julia/03a/examples/run_eno_weno.jl new file mode 100644 index 000000000..da24e6cd7 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03a/examples/run_eno_weno.jl @@ -0,0 +1,50 @@ +# examples/run_eno_weno.jl +""" +1:1 复刻 run_eno_weno.py 的 Julia 版本 +""" + +include("../src/config.jl") +include("../src/mesh.jl") +include("../src/solver.jl") +include("../src/plotter.jl") + +function performEnoWenoAnalysis() + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO3 求解 + println("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + with_reconstruction(config_eno3, "eno", 3) # 显式指定 3 阶 + config_eno3.dt = 0.0025 # 覆盖默认值 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + run!(cfd_eno3) # 求解并生成 result 字典 + + # 3. 配置并运行 WENO3 求解 + println("Running WENO3 solver...") + config_weno3 = CfdConfig() + with_reconstruction(config_weno3, "weno", 3) # 显式指定 3 阶(WENO 默认 5 阶) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + run!(cfd_weno3) + + # 5. 绘制 ENO/WENO 对比图 + println("Plotting comparison results...") + plot_eno_weno_comparison( + cfd_eno3.result, + cfd_weno3.result; + save_path="eno_weno_comparison.png" + ) + + return cfd_eno3, cfd_weno3 +end + +# 主程序入口 +if abspath(PROGRAM_FILE) == @__FILE__ + performEnoWenoAnalysis() + println("Analysis completed!") +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03a/src/boundary.jl b/example/1d-linear-convection/weno3/julia/03a/src/boundary.jl new file mode 100644 index 000000000..82f58b71f --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03a/src/boundary.jl @@ -0,0 +1,121 @@ +# julia/boundary.jl +# 目标:与 Python boundary.py 行为完全一致(1:1 同构) +# 前提:ist/ied 在 Julia 中按 1-based 设置(ist = nghosts + 1) + +#using NPZ # 仅用于测试,实际模块可不依赖 + +# ---------------------- 抽象基类(Julia 风格:用函数接口) ---------------------- +# Julia 无 abstract class,用文档+约定 +# 所有子类型必须实现 apply!(bc, u) + +# ---------------------- PeriodicBoundary ---------------------- +mutable struct PeriodicBoundary + cfd::Any +end + +function apply!(self::PeriodicBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist # 1-based, e.g., 3 + ied = self.cfd.domain.ied # 1-based, e.g., 43 + + # 左 ghost: u[ist - 1 - ig] = u[ied - 1 - ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ied - 1 - ig] + end + # 右 ghost: u[ied + ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ied + ig] = u[ist + ig] + end +end + +# ---------------------- DirichletBoundary ---------------------- +mutable struct DirichletBoundary + cfd::Any +end + +function apply!(self::DirichletBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + left_value = getfield_safe(self.cfd.config, :left_boundary_value, 1.0) + right_value = getfield_safe(self.cfd.config, :right_boundary_value, 2.0) + + # 左边界 + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = left_value + end + # 右边界 + for ig in 0:(nghosts-1) + u[ied + ig] = right_value + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Dirichlet边界: 左值=$left_value, 右值=$right_value") + end +end + +# ---------------------- NeumannBoundary ---------------------- +mutable struct NeumannBoundary + cfd::Any +end + +function apply!(self::NeumannBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + # 左边界零梯度:u[ist - 1 - ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ist + ig] + end + # 右边界零梯度:u[ied + ig] = u[ied - 1 - ig] ← 注意是 ied - 1 - ig + for ig in 0:(nghosts-1) + u[ied + ig] = u[ied - 1 - ig] + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Neumann边界: 零梯度") + end +end + + +# ---------------------- BoundaryConditionFactory ---------------------- +module BoundaryConditionFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "边界条件 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + bc_type = cfd.config.boundary_type + if !(bc_type isa AbstractString) + error("boundary_type 必须为字符串,当前值: $bc_type") + end + + if !haskey(_REGISTRY, bc_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的边界条件: '$bc_type'。可用选项: $available") + end + + return _REGISTRY[bc_type](cfd) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("periodic", cfd -> Main.PeriodicBoundary(cfd)) +register("dirichlet", cfd -> Main.DirichletBoundary(cfd)) +register("neumann", cfd -> Main.NeumannBoundary(cfd)) + +end # module BoundaryConditionFactory + +export BoundaryConditionFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03a/src/config.jl b/example/1d-linear-convection/weno3/julia/03a/src/config.jl new file mode 100644 index 000000000..dd34fe7ab --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03a/src/config.jl @@ -0,0 +1,69 @@ +# julia/config.jl +""" +CfdConfig:与 Python config.py 完全同构 +""" +mutable struct CfdConfig + ic_type::String + recon_scheme::String + flux_type::String + rk_order::Int + wave_speed::Float64 + final_time::Float64 + dt::Float64 + boundary_type::String + left_boundary_value::Float64 + right_boundary_value::Float64 + spatial_order::Int + + function CfdConfig() + new( + "step", + "eno", + "rusanov", + 1, + 1.0, + 0.625, + 0.025, + "periodic", + 1.0, + 2.0, + 2 + ) + end +end + +""" +专用配置:重建方案(链式调用) +""" +function with_reconstruction(cfg::CfdConfig, scheme::String, order::Union{Int, Nothing}=nothing) + cfg.recon_scheme = lowercase(scheme) + + if order !== nothing + cfg.spatial_order = order + else + if startswith(cfg.recon_scheme, "weno") + cfg.spatial_order = 5 + elseif cfg.recon_scheme == "eno" + cfg.spatial_order = 3 + else + error("不支持的重建格式:$scheme(仅支持 eno/weno)") + end + end + + return cfg # 支持链式调用 +end + +""" +专用配置:边界条件(链式调用) +""" +function with_boundary(cfg::CfdConfig, bc_type::String; left_value=nothing, right_value=nothing) + cfg.boundary_type = bc_type + if left_value !== nothing + cfg.left_boundary_value = left_value + end + if right_value !== nothing + cfg.right_boundary_value = right_value + end + return cfg +end + diff --git a/example/1d-linear-convection/weno3/julia/03a/src/domain.jl b/example/1d-linear-convection/weno3/julia/03a/src/domain.jl new file mode 100644 index 000000000..eec261142 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03a/src/domain.jl @@ -0,0 +1,58 @@ +# julia/domain.jl +""" +Domain 结构体(与 domain.py 完全同构) +- 保存 config +- nghosts 计算逻辑 1:1 +- ist/ied 为 0-based(与 Python 一致) +""" + +include("mesh.jl") +include("utils.jl") + +mutable struct Domain + config::Any # 保存 config(与 Python 一致) + mesh::Mesh + nghosts::Int + ist::Int # 0-based + ied::Int # 0-based + ntcells::Int +end + +function Domain(config::Any, mesh::Mesh) + nghosts = _calc_nghosts(config) + ist = nghosts + 1 # ← 1-based 起始索引 + ied = ist + mesh.ncells # ← 1-based 结束索引(不包含) + ntcells = mesh.ncells + 2 * nghosts + Domain(config, mesh, nghosts, ist, ied, ntcells) +end + +function _calc_nghosts(config::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + order = getfield_safe(config, :spatial_order, 2) + + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + if scheme == "eno" + nghosts = order + elseif startswith(scheme, "weno") + nghosts = order ÷ 2 + 1 + else + error("未知重建格式 $(scheme),无法计算ghost层!") + end + + if nghosts <= 0 + error("计算得到的ghost层数量无效:$(nghosts)(阶数$(order),格式$(scheme))") + end + + return nghosts +end + +function is_physical_cell(domain::Domain, idx::Int) + return domain.ist <= idx < domain.ied +end + +function get_physical_indices(domain::Domain) + return domain.ist:(domain.ied - 1) +end diff --git a/example/1d-linear-convection/weno3/julia/03a/src/flux.jl b/example/1d-linear-convection/weno3/julia/03a/src/flux.jl new file mode 100644 index 000000000..cff235824 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03a/src/flux.jl @@ -0,0 +1,112 @@ +# julia/flux.jl +""" +通量计算器模块(与 flux.py 完全同构) +- 抽象基类 + 具体实现 +- 字段:cfd, config, mesh, wave_speed +""" + +include("mesh.jl") + +# ---------------------- 抽象基类 ---------------------- +""" +InviscidFluxCalculator 抽象类型 +Julia 无 ABC,用文档约定 +所有子类型必须实现 compute! +""" +abstract type InviscidFluxCalculator end + +# ---------------------- RusanovFluxCalculator ---------------------- +mutable struct RusanovFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function RusanovFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::RusanovFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = calc.wave_speed + c_R = calc.wave_speed + F_L = c_L * u_L + F_R = c_R * u_R + Smax = max(abs(c_L), abs(c_R)) + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + end +end + +# ---------------------- EngquistOsherFluxCalculator ---------------------- +mutable struct EngquistOsherFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function EngquistOsherFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::EngquistOsherFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + c = calc.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + end +end + +# ---------------------- FluxCalculatorFactory ---------------------- +module FluxCalculatorFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "通量计算器 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + config = cfd.config + if !hasproperty(config, :flux_type) + error("cfd.config 缺少 flux_type 字段") + end + + flux_type = config.flux_type + if !(flux_type isa AbstractString) + error("flux_type 必须为字符串,当前值: $flux_type") + end + + if !haskey(_REGISTRY, flux_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的通量计算器: '$flux_type'。可用选项: $available") + end + + return _REGISTRY[flux_type](cfd) +end + +# ✅ 修正:使用 Main. 前缀 +register("rusanov", cfd -> Main.RusanovFluxCalculator(cfd)) +register("engquist-osher", cfd -> Main.EngquistOsherFluxCalculator(cfd)) + +end # module FluxCalculatorFactory + +export FluxCalculatorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03a/src/initial_condition.jl b/example/1d-linear-convection/weno3/julia/03a/src/initial_condition.jl new file mode 100644 index 000000000..1b9993aa8 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03a/src/initial_condition.jl @@ -0,0 +1,112 @@ +# julia/initial_condition.jl +""" +初始条件模块(Julia 版) +目标:与 Python initial_condition.py 行为完全一致 +""" + +# ---------------------- 抽象接口(Julia 无继承,用函数约定) ---------------------- +# 所有初始条件必须实现: +# evaluate_at(ic, x::AbstractVector{Float64}) -> Vector{Float64} +# apply(ic, solution) + +# ---------------------- StepFunctionIC ---------------------- +struct StepFunctionIC + config::Any +end + +function evaluate_at(ic::StepFunctionIC, x::AbstractVector{Float64}) + u0 = ones(Float64, length(x)) + for i in eachindex(x) + if 0.5 <= x[i] <= 1.0 + u0[i] = 2.0 + end + end + return u0 +end + +function apply(ic::StepFunctionIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- SineWaveIC ---------------------- +struct SineWaveIC + config::Any +end + +function evaluate_at(ic::SineWaveIC, x::AbstractVector{Float64}) + L = getfield_safe(ic.config, :domain_length, 2.0) + return sin.(2π * x / L) +end + +function apply(ic::SineWaveIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- GaussianPulseIC ---------------------- +struct GaussianPulseIC + config::Any +end + +function evaluate_at(ic::GaussianPulseIC, x::AbstractVector{Float64}) + center = getfield_safe(ic.config, :pulse_center, 0.5) + width = getfield_safe(ic.config, :pulse_width, 0.1) + return exp.(-((x .- center) ./ width).^2) +end + +function apply(ic::GaussianPulseIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- 公共辅助函数 ---------------------- +""" +将初始场 values 应用到 solution.u 的物理区域(含 ghost 的数组) +""" +function _apply_to_interior(solution, values) + domain = solution.domain + # Python: for i in range(domain.ist, domain.ied) + # Julia: for i in domain.ist:domain.ied-1 (因为 ied 是结束索引,不包含) + for i in domain.ist:(domain.ied - 1) + j = i - domain.ist + 1 # values 是 1-based,i - ist 是 0-based → +1 + solution.u[i] = values[j] + end +end + +# ---------------------- InitialConditionFactory ---------------------- +module InitialConditionFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "初始条件 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(ic_type::String, config::Any) + if !(ic_type isa AbstractString) + error("ic_type 必须为字符串,当前值: $ic_type") + end + + if !haskey(_REGISTRY, ic_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的初始条件: '$ic_type'。可用选项: $available") + end + + return _REGISTRY[ic_type](config) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("step", config -> Main.StepFunctionIC(config)) +register("sin", config -> Main.SineWaveIC(config)) +register("gaussian", config -> Main.GaussianPulseIC(config)) + +end # module InitialConditionFactory + +export InitialConditionFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03a/src/mesh.jl b/example/1d-linear-convection/weno3/julia/03a/src/mesh.jl new file mode 100644 index 000000000..1b0dd4bf9 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03a/src/mesh.jl @@ -0,0 +1,45 @@ +# julia/mesh.jl +""" +Mesh 结构体(与提供的 mesh.py 完全同构) +- 字段名、初始化顺序、循环逻辑完全一致 +- 不做任何泛化或优化 +""" + +mutable struct Mesh + xmin::Float64 + xmax::Float64 + ncells::Int + nnodes::Int + nx::Int + x::Vector{Float64} + xcc::Vector{Float64} + L::Float64 # 在 init_mesh 中赋值 + dx::Float64 # 在 init_mesh 中赋值 + + function Mesh() + # 与 Python __init__ 完全一致 + xmin = 0.0 + xmax = 2.0 + ncells = 40 + nnodes = ncells + 1 + nx = ncells + x = zeros(Float64, nnodes) + xcc = zeros(Float64, ncells) + + # 调用 init_mesh(模拟 Python) + L = xmax - xmin + dx = L / ncells + + # Generate node coordinates: for i in range(self.nnodes) + for i in 1:nnodes + x[i] = xmin + (i - 1) * dx # Python i → Julia i-1 + end + + # Generate cell center coordinates + for i in 1:ncells + xcc[i] = 0.5 * (x[i] + x[i+1]) + end + + new(xmin, xmax, ncells, nnodes, nx, x, xcc, L, dx) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03a/src/plotter.jl b/example/1d-linear-convection/weno3/julia/03a/src/plotter.jl new file mode 100644 index 000000000..a77d8d688 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03a/src/plotter.jl @@ -0,0 +1,147 @@ +# julia/plotter.jl +""" +CFDPlotter 的 Julia 实现(通过 PythonCall.jl 调用 Matplotlib) +确保与 Python plotter.py 行为完全一致 +""" + +using PythonCall + +# 初始化 Python 环境(加载 matplotlib, inflect) +const plt = pyimport("matplotlib.pyplot") +const inflect = pyimport("inflect") + +mutable struct CFDPlotter + default_styles::Dict{String, Any} + p::Py +end + +function CFDPlotter() + default_styles = Dict{String, Any}( + "numerical" => Dict( + :color => "blue", + :linestyle => "-", + :marker => "o", + :markerfacecolor => "none" + ), + "analytical" => Dict( + :color => "red", + :linestyle => "--", + :marker => "", + :linewidth => 1.5 + ), + "comparison" => [ + Dict(:color => "black", :linestyle => "-", :marker => "o", :markerfacecolor => "none"), + Dict(:color => "blue", :linestyle => "--", :marker => "s", :markerfacecolor => "none"), + Dict(:color => "green", :linestyle => ":", :marker => "^", :markerfacecolor => "none") + ] + ) + p = inflect.engine() + CFDPlotter(default_styles, p) +end + +""" +轻量即时绘图(快速验证结果) +""" +function plot_quick(plotter::CFDPlotter, cfd_result::Dict; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + rk_order = cfd_result["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = cfd_result["config"]["final_time"] + order = cfd_result["config"]["order"] + scheme = uppercase(cfd_result["config"]["scheme"]) + actual_title = "1D Convection (t=$(final_time))\n$(order)th-order $(scheme) + $(rk_str)-order RK" + end + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"]; + label="Numerical ($(uppercase(cfd_result["config"]["scheme"])))", + plotter.default_styles["numerical"]..., + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + # 通用样式 + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +""" +多格式/多精度对比绘图 +""" +function plot_comparison(plotter::CFDPlotter, result_list::Vector{Dict{String, Any}}; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + schemes = [uppercase(r["config"]["scheme"]) * string(r["config"]["order"]) for r in result_list] + rk_order = result_list[1]["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = result_list[1]["config"]["final_time"] + actual_title = "1D Convection Comparison (t=$(final_time))\n$(join(schemes, ", ")) + $(rk_str)-order RK" + end + + # 绘制多个数值解 + for (i, res) in enumerate(result_list) + style = plotter.default_styles["comparison"][mod1(i, length(plotter.default_styles["comparison"]))] + label = "Numerical ($(uppercase(res["config"]["scheme"]))$(res["config"]["order"]))" + plt.plot( + res["x"], res["numerical"]; + label=label, + style..., + markersize=5, linewidth=0.5 + ) + end + + # 绘制解析解 + plt.plot( + result_list[1]["x"], result_list[1]["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +function _set_common_style(plotter::CFDPlotter, title::String) + plt.title(title, fontsize=12) + plt.xlabel("x", fontsize=10) + plt.ylabel("u", fontsize=10) + plt.legend(fontsize=9) + plt.grid(true, color="gray", linestyle="--", linewidth=0.5, alpha=0.7) + plt.tight_layout() +end + +""" +快捷函数:ENO/WENO对比绘图 +""" +function plot_eno_weno_comparison(eno_result::Dict, weno_result::Dict; save_path=nothing) + plotter = CFDPlotter() + plot_comparison(plotter, [eno_result, weno_result]; save_path=save_path) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03a/src/reconstructor/eno.jl b/example/1d-linear-convection/weno3/julia/03a/src/reconstructor/eno.jl new file mode 100644 index 000000000..e78a636f3 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03a/src/reconstructor/eno.jl @@ -0,0 +1,107 @@ +# julia/reconstructor/eno.jl +""" +ENO 重构器(与 reconstructor/eno.py 完全同构) +""" + +# ---------------------- ENO 系数初始化 ---------------------- +function _init_eno_coef!(spatial_order::Int, coef::Matrix{Float64}) + if spatial_order == 1 + coef[1, 1] = 1.0 + coef[2, 1] = 1.0 + elseif spatial_order == 2 + coef[1, 1:2] = [3.0/2.0, -1.0/2.0] + coef[2, 1:2] = [1.0/2.0, 1.0/2.0] + coef[3, 1:2] = [-1.0/2.0, 3.0/2.0] + elseif spatial_order == 3 + coef[1, 1:3] = [11.0/6.0, -7.0/6.0, 1.0/3.0] + coef[2, 1:3] = [1.0/3.0, 5.0/6.0, -1.0/6.0] + coef[3, 1:3] = [-1.0/6.0, 5.0/6.0, 1.0/3.0] + coef[4, 1:3] = [1.0/3.0, -7.0/6.0, 11.0/6.0] + elseif spatial_order == 4 + coef[1, 1:4] = [25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0] + coef[2, 1:4] = [1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0] + coef[3, 1:4] = [-1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0] + coef[4, 1:4] = [1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0] + coef[5, 1:4] = [-1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0] + elseif spatial_order == 5 + coef[1, 1:5] = [137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0] + coef[2, 1:5] = [1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0] + coef[3, 1:5] = [-1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0] + coef[4, 1:5] = [1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0] + coef[5, 1:5] = [-1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0] + coef[6, 1:5] = [1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0] + elseif spatial_order == 6 + coef[1, 1:6] = [49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0] + coef[2, 1:6] = [1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0] + coef[3, 1:6] = [-1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0] + coef[4, 1:6] = [1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0] + coef[5, 1:6] = [-1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0] + coef[6, 1:6] = [1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0] + coef[7, 1:6] = [-1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0] + elseif spatial_order == 7 + coef[1, 1:7] = [363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0] + coef[2, 1:7] = [1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0] + coef[3, 1:7] = [-1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0] + coef[4, 1:7] = [1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0] + coef[5, 1:7] = [-1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0] + coef[6, 1:7] = [1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0] + coef[7, 1:7] = [-1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0] + coef[8, 1:7] = [1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0] + else + error("ENO 系数未实现 order=$spatial_order") + end +end + +# ---------------------- ENO 重构器 ---------------------- +mutable struct EnoReconstructor + spatial_order::Int + ntcells::Int + lmc::Vector{Int} + coef::Matrix{Float64} + dd::Matrix{Float64} + + function EnoReconstructor(spatial_order::Int, ntcells::Int) + lmc = zeros(Int, ntcells) + coef = zeros(Float64, spatial_order + 1, spatial_order) + dd = zeros(Float64, spatial_order, ntcells) + _init_eno_coef!(spatial_order, coef) + new(spatial_order, ntcells, lmc, coef, dd) + end +end + +function reconstruct(rec::EnoReconstructor, q::Vector{Float64}, cfd::Any) + # 1. 差商计算 (dd[1,:] = q) + @views rec.dd[1, :] .= q + for m in 2:rec.spatial_order + for j in 1:(rec.ntcells - m + 1) + rec.dd[m, j] = rec.dd[m-1, j+1] - rec.dd[m-1, j] + end + end + + # 2. 选择 smoothest stencil + domain = cfd.domain + for i in (domain.ist - 1):(domain.ied) # Python: range(ist-1, ied+1) → ied+1-1 = ied + rec.lmc[i] = i + for m in 2:rec.spatial_order + if abs(rec.dd[m, rec.lmc[i] - 1]) < abs(rec.dd[m, rec.lmc[i]]) + rec.lmc[i] -= 1 + end + end + end + + # 3. 重构界面值 + solution = cfd.solution + for i in domain.ist:(domain.ied) # Python: range(ist, ied+1) → ied+1-1 = ied + j = i - domain.ist + 1 # Julia 1-based + k1 = rec.lmc[i - 1] + k2 = rec.lmc[i] + r1 = (i - 1) - k1 + 1 + r2 = i - k2 + 1 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in 1:rec.spatial_order + solution.q_face_left[j] += q[k1 + m - 1] * rec.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m - 1] * rec.coef[r2, m] + end + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03a/src/reconstructor/factory.jl b/example/1d-linear-convection/weno3/julia/03a/src/reconstructor/factory.jl new file mode 100644 index 000000000..70b0cf9ff --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03a/src/reconstructor/factory.jl @@ -0,0 +1,38 @@ +# src/reconstructor/factory.jl + +""" +ReconstructorFactory +对标 Python: ReconstructorFactory.create(config, domain) +""" +module ReconstructorFactory + +include("../utils.jl") + +# ✅ 不要用 using ..EnoReconstructor(它们不是模块) +# 直接通过 Main. 引用顶层定义的类型 + +function create(config::Any, domain::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + # 处理 WENO 默认命名(如 Python) + if scheme == "weno" + order = getfield_safe(config, :spatial_order, 5) + scheme = "weno$(order)" + end + + if scheme == "eno" + order = getfield_safe(config, :spatial_order, 3) + return Main.EnoReconstructor(order, domain.ntcells) + elseif scheme == "weno3" + return Main.Weno3Reconstructor() + else + error("不支持的重建格式: $scheme(仅支持 eno/weno3)") + end +end + +end # module ReconstructorFactory + +export ReconstructorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03a/src/reconstructor/weno3.jl b/example/1d-linear-convection/weno3/julia/03a/src/reconstructor/weno3.jl new file mode 100644 index 000000000..2b6fe1abb --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03a/src/reconstructor/weno3.jl @@ -0,0 +1,65 @@ +# julia/reconstructor/weno3.jl +""" +WENO3 重构器(与 reconstructor/weno3.py 完全同构) +""" + +mutable struct Weno3Reconstructor + # 无字段,与 Python 一致 +end + +function reconstruct(rec::Weno3Reconstructor, q::Vector{Float64}, cfd::Any) + domain = cfd.domain + solution = cfd.solution + _reconstruct_left_interfaces(domain, q, solution.q_face_left) + _reconstruct_right_interfaces(domain, q, solution.q_face_right) +end + +function _reconstruct_left_interfaces(domain, u, qL) + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in (domain.ist - 1):(domain.ied - 1) + j = i - (domain.ist - 1) + 1 # ← Julia 1-based: j = i - (ist-1) 对应 Python j = i - (ist-1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = _reconstruct_from_left_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_right_interfaces(domain, u, qR) + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in domain.ist:domain.ied + j = i - domain.ist + 1 # ← Julia 1-based: j = i - ist 对应 Python j = i - ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = _reconstruct_from_right_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_from_left_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -0.5*v1 + 1.5*v2 # r=1 stencil + q1 = 0.5*v2 + 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end + +function _reconstruct_from_right_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 0.5*v1 + 0.5*v2 # r=1 stencil + q1 = 1.5*v2 - 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03a/src/registry.jl b/example/1d-linear-convection/weno3/julia/03a/src/registry.jl new file mode 100644 index 000000000..6acd9aaaf --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03a/src/registry.jl @@ -0,0 +1,164 @@ +""" +统一注册系统 + 通用工厂 +- ComponentRegistry: 组件注册表 +- @register_component: 装饰器宏 +- BaseFactory: 通用工厂接口 +""" + +module ComponentRegistry + +# 注册表:Dict{category => Dict{name => constructor}} +const _REGISTRIES = Dict{String, Dict{String, Function}}() +const _VERBOSE = Ref(true) + +# ---------------------- 注册核心逻辑 ---------------------- +""" + register(category::String, name::String, ctor::Function) + +注册一个组件构造函数到指定类别。 +如果已存在同名组件且不同,则发出警告。 +""" +function register(category::String, name::String, ctor::Function) + if !haskey(_REGISTRIES, category) + _REGISTRIES[category] = Dict{String, Function}() + end + + registry = _REGISTRIES[category] + if haskey(registry, name) + if registry[name] !== ctor && _VERBOSE[] + @warn "覆盖注册: $category.$name" + end + end + + registry[name] = ctor + if _VERBOSE[] + println("✅ 已注册: $category.$name -> $(nameof(ctor))") + end +end + +""" + create(category::String, name::String, args...; kwargs...) + +从注册表创建组件实例。 +""" +function create(category::String, name::String, args...; kwargs...) + if !haskey(_REGISTRIES, category) + error("❌ 未知类别: $category (可用: $(collect(keys(_REGISTRIES))))") + end + + registry = _REGISTRIES[category] + lname = lowercase(name) + if !haskey(registry, lname) + available = sort(collect(keys(registry))) + error("❌ 未找到: $category.$name (可用: $available)") + end + + return registry[lname](args...; kwargs...) +end + +""" + list_all() + +返回所有已注册组件(按类别)。 +""" +function list_all() + return Dict(cat => sort(collect(keys(reg))) for (cat, reg) in _REGISTRIES) +end + +""" + set_verbose(flag::Bool) + +开启/关闭注册提示。 +""" +function set_verbose(flag::Bool) + _VERBOSE[] = flag +end + +end # module ComponentRegistry + + +# ---------------------- 装饰器宏:@register_component ---------------------- +""" +@register_component(category, [name]) + +用法: + @register_component("boundary", "periodic") + struct PeriodicBoundary ... + +若省略 name,则使用类型名的小写形式。 +""" +macro register_component(category::String, name_expr) + error("@register_component 需在类型定义前使用,且必须在模块顶层") +end + +macro register_component(category::String) + error("@register_component(category, name) 需指定 name 或在类型后使用") +end + +# 重载:@register_component("category", "name") struct X ... end +macro register_component(category::String, name::String, ex) + if !Meta.isexpr(ex, :struct) + error("@register_component 必须作用于 struct 定义") + end + + struct_name = ex.args[2] + if Meta.isexpr(struct_name, :curly) + struct_name = struct_name.args[1] + end + + # 插入注册调用(在模块顶层) + quote + $(esc(ex)) + $(ComponentRegistry).register($(category), $(name), $(esc(struct_name))) + end +end + +# 重载:@register_component("category") struct X ... end → name = lowercase(nameof(X)) +macro register_component(category::String, ex) + if !Meta.isexpr(ex, :struct) + error("@register_component 必须作用于 struct 定义") + end + + struct_name = ex.args[2] + if Meta.isexpr(struct_name, :curly) + struct_name = struct_name.args[1] + end + + name_str = string(struct_name) |> lowercase + + quote + $(esc(ex)) + $(ComponentRegistry).register($(category), $(name_str), $(esc(struct_name))) + end +end + + +# ---------------------- 通用工厂 ---------------------- +module BaseFactory + +using ..ComponentRegistry + +""" + create_component(category::String, name::String, args...; kwargs...) + +通用工厂接口,与 Python BaseFactory.create_component 行为一致。 +""" +function create_component(category::String, name::String, args...; kwargs...) + return ComponentRegistry.create(category, name, args...; kwargs...) +end + +""" + get_available_components(category::String) + +列出某类别下所有可用组件。 +""" +function get_available_components(category::String) + all = ComponentRegistry.list_all() + return get(all, category, String[]) +end + +end # module BaseFactory + + +# 导出接口 +export ComponentRegistry, BaseFactory, @register_component \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03a/src/residual.jl b/example/1d-linear-convection/weno3/julia/03a/src/residual.jl new file mode 100644 index 000000000..11a885934 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03a/src/residual.jl @@ -0,0 +1,69 @@ +# julia/residual.jl +""" +残差计算器(与 residual.py 完全同构) +- 封装重建→通量→散度完整流程 +- 依赖 cfd 的多个字段 +""" + +include("mesh.jl") + +mutable struct ResidualCalculator + cfd::Any + config::Any + domain::Any + solution::Any + mesh::Mesh + reconstructor::Any + flux_calculator::Any + + function ResidualCalculator(cfd::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + mesh = domain.mesh + reconstructor = cfd.reconstructor + + # ✅ 内部创建 flux_calculator(对标 Python) + flux_calculator = FluxCalculatorFactory.create(cfd) + + new(cfd, config, domain, solution, mesh, reconstructor, flux_calculator) + end +end + + +""" +计算完整残差(对外唯一接口) +""" +function compute!(calc::ResidualCalculator) + _reconstruct(calc) + _compute_inviscid_flux(calc) + _compute_flux_divergence(calc) +end + +""" +私有方法:界面值重建 +""" +function _reconstruct(calc::ResidualCalculator) + reconstruct(calc.reconstructor, calc.solution.u, calc.cfd) +end + +""" +私有方法:计算无粘通量 +""" +function _compute_inviscid_flux(calc::ResidualCalculator) + compute!(calc.flux_calculator, + calc.solution.q_face_left, + calc.solution.q_face_right, + calc.solution.flux) +end + +""" +私有方法:计算通量散度(残差 = -dF/dx) +""" +function _compute_flux_divergence(calc::ResidualCalculator) + solution = calc.solution + mesh = calc.mesh + for i in 1:mesh.ncells + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / mesh.dx + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03a/src/solution.jl b/example/1d-linear-convection/weno3/julia/03a/src/solution.jl new file mode 100644 index 000000000..7044c0014 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03a/src/solution.jl @@ -0,0 +1,60 @@ +# julia/solution.jl +""" +Solution 结构体(与真实 solution.py 完全同构) +字段顺序、方法、逻辑 1:1 对齐 +""" + +include("mesh.jl") +include("domain.jl") +include("initial_condition.jl") + +mutable struct Solution + domain::Domain + q_face_left::Vector{Float64} + q_face_right::Vector{Float64} + flux::Vector{Float64} + res::Vector{Float64} + u::Vector{Float64} + un::Vector{Float64} + + function Solution(config::Any, domain::Domain) + mesh = domain.mesh + + q_face_left = zeros(Float64, mesh.nnodes) + q_face_right = zeros(Float64, mesh.nnodes) + flux = zeros(Float64, mesh.nnodes) + res = zeros(Float64, mesh.ncells) + u = zeros(Float64, domain.ntcells) + un = zeros(Float64, domain.ntcells) + + sol = new(domain, q_face_left, q_face_right, flux, res, u, un) + initialize_from_config(sol, config) + return sol + end +end + +""" +重置解数组为初始状态 +""" +function reset_solution(sol::Solution) + fill!(sol.u, 0.0) + fill!(sol.un, 0.0) +end + +""" +根据配置初始化场 +""" + +function initialize_from_config(sol::Solution, config::Any) + ic_type = getfield_safe(config, :ic_type, "step") + ic = InitialConditionFactory.create(ic_type, config) + apply(ic, sol) +end + +""" +更新旧场:un = u +""" +function update_old_field(sol::Solution) + sol.un .= sol.u # 等价于 Python 的 un[:] = u[:] +end + diff --git a/example/1d-linear-convection/weno3/julia/03a/src/solver.jl b/example/1d-linear-convection/weno3/julia/03a/src/solver.jl new file mode 100644 index 000000000..d8193f0e8 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03a/src/solver.jl @@ -0,0 +1,130 @@ +# julia/solver.jl +""" +CFD 求解器主类(与 solver.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("initial_condition.jl") +include("boundary.jl") +include("flux.jl") +include("residual.jl") +include("time_integration.jl") +include("reconstructor/eno.jl") +include("reconstructor/weno3.jl") +include("reconstructor/factory.jl") + +# 导入工厂模块(必须在顶层!) +using .FluxCalculatorFactory +using .TimeIntegratorFactory +using .BoundaryConditionFactory +using .ReconstructorFactory + +# ---------------------- Cfd 求解器 ---------------------- +mutable struct Cfd + config::Any + domain::Domain + solution::Solution + reconstructor::Any + residual_calculator::ResidualCalculator + integrator::Any + boundary_condition::Any + result::Dict{String, Any} + + function Cfd(config::Any, mesh::Mesh) + domain = Domain(config, mesh) + solution = Solution(config, domain) + reconstructor = ReconstructorFactory.create(config, domain) + + # 1. 初始上下文(仅包含不依赖其他组件的字段) + full_cfd = ( + config = config, + domain = domain, + solution = solution, + reconstructor = reconstructor + # 注意:不预先占位 nothing! + ) + + # 2. 创建 boundary_condition(只依赖 config + domain) + boundary_condition = BoundaryConditionFactory.create(full_cfd) + full_cfd = merge(full_cfd, (boundary_condition = boundary_condition,)) + + # 3. 创建 residual_calculator(依赖上面所有字段) + residual_calculator = ResidualCalculator(full_cfd) + full_cfd = merge(full_cfd, (residual_calculator = residual_calculator,)) + + # 4. 创建 integrator(依赖 residual_calculator 等) + integrator = TimeIntegratorFactory.create(full_cfd) + full_cfd = merge(full_cfd, (integrator = integrator,)) + + # 5. 注入完整 self 到组件(确保它们能访问彼此) + residual_calculator.cfd = full_cfd + integrator.base.cfd = full_cfd + + result = Dict{String, Any}() + new(config, domain, solution, reconstructor, residual_calculator, integrator, boundary_condition, result) + end +end + +""" +通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界 +""" +function exact_solution(cfd::Cfd) + x = cfd.domain.mesh.xcc + T = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = @. (x - c * T + L) % L + + # ✅ 使用工厂创建初始条件(对标 Python) + ic = InitialConditionFactory.create(cfd.config.ic_type, cfd.config) + + return evaluate_at(ic, x_shifted) +end + +""" +主求解循环 +""" +function run!(cfd::Cfd) + # 应用初始边界条件并同步 old field + apply!(cfd.boundary_condition, cfd.solution.u) + update_old_field(cfd.solution) + + t = 0.0 + dt_old = cfd.config.dt + dt = dt_old + + while t < cfd.config.final_time + if t + dt > cfd.config.final_time + dt = cfd.config.final_time - t + end + #@show t, dt, maximum(cfd.solution.u), minimum(cfd.solution.u) + # 执行时间步 + step(cfd.integrator, dt) + t += dt + end + + # 恢复 dt + cfd.config.dt = dt_old + + # 整理结果 + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied-1] # Python: [ist:ied] + analytical = exact_solution(cfd) + + cfd.result = Dict( + "x" => cfd.domain.mesh.xcc, + "numerical" => u_numerical, + "analytical" => analytical, + "config" => Dict( + "scheme" => cfd.config.recon_scheme, + "order" => cfd.config.spatial_order, + "rk_order" => cfd.config.rk_order, + "final_time" => cfd.config.final_time + ) + ) + + return u_numerical +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03a/src/time_integration.jl b/example/1d-linear-convection/weno3/julia/03a/src/time_integration.jl new file mode 100644 index 000000000..add564ba5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03a/src/time_integration.jl @@ -0,0 +1,169 @@ +# julia/time_integration.jl +""" +时间推进器模块(与 time_integration.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("residual.jl") +include("boundary.jl") + +# ---------------------- 抽象时间推进器基类 ---------------------- +abstract type TimeIntegrator end + +mutable struct TimeIntegratorBase <: TimeIntegrator + cfd::Any + config::Any + domain::Domain + solution::Solution + residual_calculator::Any # ResidualCalculator +end + +function TimeIntegratorBase(cfd::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + residual_calculator = cfd.residual_calculator + TimeIntegratorBase(cfd, config, domain, solution, residual_calculator) +end + +function compute_residual(integrator::TimeIntegratorBase) + compute!(integrator.residual_calculator) +end + +function apply_boundary(integrator::TimeIntegratorBase) + apply!(integrator.cfd.boundary_condition, integrator.solution.u) +end + +function map_idx(integrator::TimeIntegratorBase, i::Int) + return i - integrator.domain.ist + 1 # ← +1 转为 1-based +end + +# ---------------------- RK1Integrator ---------------------- +mutable struct RK1Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK1Integrator(cfd::Any) + RK1Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK1Integrator, dt::Float64) + compute_residual(integrator.base) + for i in integrator.base.domain.ist:(integrator.base.domain.ied - 1) + j = map_idx(integrator.base, i) + integrator.base.solution.u[i] += dt * integrator.base.solution.res[j] + end + apply_boundary(integrator.base) + update_old_field(integrator.base.solution) +end + +# ---------------------- RK2Integrator ---------------------- +mutable struct RK2Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK2Integrator(cfd::Any) + RK2Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK2Integrator, dt::Float64) + base = integrator.base + # 阶段1:预测步 + compute_residual(base) + u_pred = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u_pred[i] += dt * base.solution.res[j] + end + base.solution.u .= u_pred + apply_boundary(base) + # 阶段2:校正步 + compute_residual(base) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = 0.5 * base.solution.un[i] + 0.5 * base.solution.u[i] + 0.5 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end + +# ---------------------- RK3Integrator ---------------------- +mutable struct RK3Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK3Integrator(cfd::Any) + RK3Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK3Integrator, dt::Float64) + base = integrator.base + # 阶段1 + compute_residual(base) + u1 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u1[i] += dt * base.solution.res[j] + end + base.solution.u .= u1 + apply_boundary(base) + # 阶段2 + compute_residual(base) + u2 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u2[i] = 0.75 * base.solution.un[i] + 0.25 * base.solution.u[i] + 0.25 * dt * base.solution.res[j] + end + base.solution.u .= u2 + apply_boundary(base) + # 阶段3 + compute_residual(base) + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = c1 * base.solution.un[i] + c2 * base.solution.u[i] + c3 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end + +# ---------------------- TimeIntegratorFactory ---------------------- +module TimeIntegratorFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "时间积分器 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + rk_order = cfd.config.rk_order + if !(rk_order isa Integer) || rk_order < 1 + error("rk_order 必须为正整数,当前值: $rk_order") + end + + name = "rk$rk_order" + if !haskey(_REGISTRY, name) + available = sort(collect(keys(_REGISTRY))) + error("未注册的时间积分器: '$name'。可用选项: $available") + end + + return _REGISTRY[name](cfd) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("rk1", cfd -> Main.RK1Integrator(cfd)) +register("rk2", cfd -> Main.RK2Integrator(cfd)) +register("rk3", cfd -> Main.RK3Integrator(cfd)) + +end # module TimeIntegratorFactory + +export TimeIntegratorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03a/src/utils.jl b/example/1d-linear-convection/weno3/julia/03a/src/utils.jl new file mode 100644 index 000000000..3786cd1a5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03a/src/utils.jl @@ -0,0 +1,9 @@ +# src/utils.jl + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end diff --git a/example/1d-linear-convection/weno3/julia/03b/examples/run_eno_weno.jl b/example/1d-linear-convection/weno3/julia/03b/examples/run_eno_weno.jl new file mode 100644 index 000000000..da24e6cd7 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03b/examples/run_eno_weno.jl @@ -0,0 +1,50 @@ +# examples/run_eno_weno.jl +""" +1:1 复刻 run_eno_weno.py 的 Julia 版本 +""" + +include("../src/config.jl") +include("../src/mesh.jl") +include("../src/solver.jl") +include("../src/plotter.jl") + +function performEnoWenoAnalysis() + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO3 求解 + println("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + with_reconstruction(config_eno3, "eno", 3) # 显式指定 3 阶 + config_eno3.dt = 0.0025 # 覆盖默认值 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + run!(cfd_eno3) # 求解并生成 result 字典 + + # 3. 配置并运行 WENO3 求解 + println("Running WENO3 solver...") + config_weno3 = CfdConfig() + with_reconstruction(config_weno3, "weno", 3) # 显式指定 3 阶(WENO 默认 5 阶) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + run!(cfd_weno3) + + # 5. 绘制 ENO/WENO 对比图 + println("Plotting comparison results...") + plot_eno_weno_comparison( + cfd_eno3.result, + cfd_weno3.result; + save_path="eno_weno_comparison.png" + ) + + return cfd_eno3, cfd_weno3 +end + +# 主程序入口 +if abspath(PROGRAM_FILE) == @__FILE__ + performEnoWenoAnalysis() + println("Analysis completed!") +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03b/src/boundary.jl b/example/1d-linear-convection/weno3/julia/03b/src/boundary.jl new file mode 100644 index 000000000..82f58b71f --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03b/src/boundary.jl @@ -0,0 +1,121 @@ +# julia/boundary.jl +# 目标:与 Python boundary.py 行为完全一致(1:1 同构) +# 前提:ist/ied 在 Julia 中按 1-based 设置(ist = nghosts + 1) + +#using NPZ # 仅用于测试,实际模块可不依赖 + +# ---------------------- 抽象基类(Julia 风格:用函数接口) ---------------------- +# Julia 无 abstract class,用文档+约定 +# 所有子类型必须实现 apply!(bc, u) + +# ---------------------- PeriodicBoundary ---------------------- +mutable struct PeriodicBoundary + cfd::Any +end + +function apply!(self::PeriodicBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist # 1-based, e.g., 3 + ied = self.cfd.domain.ied # 1-based, e.g., 43 + + # 左 ghost: u[ist - 1 - ig] = u[ied - 1 - ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ied - 1 - ig] + end + # 右 ghost: u[ied + ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ied + ig] = u[ist + ig] + end +end + +# ---------------------- DirichletBoundary ---------------------- +mutable struct DirichletBoundary + cfd::Any +end + +function apply!(self::DirichletBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + left_value = getfield_safe(self.cfd.config, :left_boundary_value, 1.0) + right_value = getfield_safe(self.cfd.config, :right_boundary_value, 2.0) + + # 左边界 + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = left_value + end + # 右边界 + for ig in 0:(nghosts-1) + u[ied + ig] = right_value + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Dirichlet边界: 左值=$left_value, 右值=$right_value") + end +end + +# ---------------------- NeumannBoundary ---------------------- +mutable struct NeumannBoundary + cfd::Any +end + +function apply!(self::NeumannBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + # 左边界零梯度:u[ist - 1 - ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ist + ig] + end + # 右边界零梯度:u[ied + ig] = u[ied - 1 - ig] ← 注意是 ied - 1 - ig + for ig in 0:(nghosts-1) + u[ied + ig] = u[ied - 1 - ig] + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Neumann边界: 零梯度") + end +end + + +# ---------------------- BoundaryConditionFactory ---------------------- +module BoundaryConditionFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "边界条件 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + bc_type = cfd.config.boundary_type + if !(bc_type isa AbstractString) + error("boundary_type 必须为字符串,当前值: $bc_type") + end + + if !haskey(_REGISTRY, bc_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的边界条件: '$bc_type'。可用选项: $available") + end + + return _REGISTRY[bc_type](cfd) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("periodic", cfd -> Main.PeriodicBoundary(cfd)) +register("dirichlet", cfd -> Main.DirichletBoundary(cfd)) +register("neumann", cfd -> Main.NeumannBoundary(cfd)) + +end # module BoundaryConditionFactory + +export BoundaryConditionFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03b/src/config.jl b/example/1d-linear-convection/weno3/julia/03b/src/config.jl new file mode 100644 index 000000000..dd34fe7ab --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03b/src/config.jl @@ -0,0 +1,69 @@ +# julia/config.jl +""" +CfdConfig:与 Python config.py 完全同构 +""" +mutable struct CfdConfig + ic_type::String + recon_scheme::String + flux_type::String + rk_order::Int + wave_speed::Float64 + final_time::Float64 + dt::Float64 + boundary_type::String + left_boundary_value::Float64 + right_boundary_value::Float64 + spatial_order::Int + + function CfdConfig() + new( + "step", + "eno", + "rusanov", + 1, + 1.0, + 0.625, + 0.025, + "periodic", + 1.0, + 2.0, + 2 + ) + end +end + +""" +专用配置:重建方案(链式调用) +""" +function with_reconstruction(cfg::CfdConfig, scheme::String, order::Union{Int, Nothing}=nothing) + cfg.recon_scheme = lowercase(scheme) + + if order !== nothing + cfg.spatial_order = order + else + if startswith(cfg.recon_scheme, "weno") + cfg.spatial_order = 5 + elseif cfg.recon_scheme == "eno" + cfg.spatial_order = 3 + else + error("不支持的重建格式:$scheme(仅支持 eno/weno)") + end + end + + return cfg # 支持链式调用 +end + +""" +专用配置:边界条件(链式调用) +""" +function with_boundary(cfg::CfdConfig, bc_type::String; left_value=nothing, right_value=nothing) + cfg.boundary_type = bc_type + if left_value !== nothing + cfg.left_boundary_value = left_value + end + if right_value !== nothing + cfg.right_boundary_value = right_value + end + return cfg +end + diff --git a/example/1d-linear-convection/weno3/julia/03b/src/domain.jl b/example/1d-linear-convection/weno3/julia/03b/src/domain.jl new file mode 100644 index 000000000..eec261142 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03b/src/domain.jl @@ -0,0 +1,58 @@ +# julia/domain.jl +""" +Domain 结构体(与 domain.py 完全同构) +- 保存 config +- nghosts 计算逻辑 1:1 +- ist/ied 为 0-based(与 Python 一致) +""" + +include("mesh.jl") +include("utils.jl") + +mutable struct Domain + config::Any # 保存 config(与 Python 一致) + mesh::Mesh + nghosts::Int + ist::Int # 0-based + ied::Int # 0-based + ntcells::Int +end + +function Domain(config::Any, mesh::Mesh) + nghosts = _calc_nghosts(config) + ist = nghosts + 1 # ← 1-based 起始索引 + ied = ist + mesh.ncells # ← 1-based 结束索引(不包含) + ntcells = mesh.ncells + 2 * nghosts + Domain(config, mesh, nghosts, ist, ied, ntcells) +end + +function _calc_nghosts(config::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + order = getfield_safe(config, :spatial_order, 2) + + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + if scheme == "eno" + nghosts = order + elseif startswith(scheme, "weno") + nghosts = order ÷ 2 + 1 + else + error("未知重建格式 $(scheme),无法计算ghost层!") + end + + if nghosts <= 0 + error("计算得到的ghost层数量无效:$(nghosts)(阶数$(order),格式$(scheme))") + end + + return nghosts +end + +function is_physical_cell(domain::Domain, idx::Int) + return domain.ist <= idx < domain.ied +end + +function get_physical_indices(domain::Domain) + return domain.ist:(domain.ied - 1) +end diff --git a/example/1d-linear-convection/weno3/julia/03b/src/flux/base.jl b/example/1d-linear-convection/weno3/julia/03b/src/flux/base.jl new file mode 100644 index 000000000..58dd6cc90 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03b/src/flux/base.jl @@ -0,0 +1,7 @@ +# src/flux/base.jl +""" +抽象通量计算器接口 +Julia 无 ABC,用文档约定: +- 所有子类型必须实现 `compute!(calc, qL, qR, flux)` +""" +abstract type InviscidFluxCalculator end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03b/src/flux/engquist_osher.jl b/example/1d-linear-convection/weno3/julia/03b/src/flux/engquist_osher.jl new file mode 100644 index 000000000..c0de183d5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03b/src/flux/engquist_osher.jl @@ -0,0 +1,25 @@ +# src/flux/engquist_osher.jl +mutable struct EngquistOsherFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function EngquistOsherFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::EngquistOsherFluxCalculator, qL::Vector{Float64}, qR::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + c = calc.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = qL[i] + u_R = qR[i] + flux[i] = cp * u_L + cm * u_R + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03b/src/flux/factory.jl b/example/1d-linear-convection/weno3/julia/03b/src/flux/factory.jl new file mode 100644 index 000000000..6ae4917db --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03b/src/flux/factory.jl @@ -0,0 +1,26 @@ +# src/flux/factory.jl + +module FluxCalculatorFactory + +# 全局注册表 +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "通量计算器 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + flux_type = cfd.config.flux_type + if !haskey(_REGISTRY, flux_type) + error("未注册的通量计算器: '$flux_type'。可用: $(keys(_REGISTRY))") + end + return _REGISTRY[flux_type](cfd) +end + +register("rusanov", cfd -> Main.RusanovFluxCalculator(cfd)) +register("engquist-osher", cfd -> Main.EngquistOsherFluxCalculator(cfd)) + +end # module FluxCalculatorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03b/src/flux/flux.jl b/example/1d-linear-convection/weno3/julia/03b/src/flux/flux.jl new file mode 100644 index 000000000..533f034c8 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03b/src/flux/flux.jl @@ -0,0 +1,10 @@ +# src/flux/flux.jl + +# 加载组件(顺序很重要!) +include("base.jl") +include("rusanov.jl") +include("engquist_osher.jl") +include("factory.jl") + +# 导出(如果你未来用模块) +# export InviscidFluxCalculator, RusanovFluxCalculator, EngquistOsherFluxCalculator, FluxCalculatorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03b/src/flux/rusanov.jl b/example/1d-linear-convection/weno3/julia/03b/src/flux/rusanov.jl new file mode 100644 index 000000000..70c20fc3c --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03b/src/flux/rusanov.jl @@ -0,0 +1,28 @@ +# src/flux/rusanov.jl + +mutable struct RusanovFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function RusanovFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::RusanovFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = calc.wave_speed + c_R = calc.wave_speed + F_L = c_L * u_L + F_R = c_R * u_R + Smax = max(abs(c_L), abs(c_R)) + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03b/src/initial_condition.jl b/example/1d-linear-convection/weno3/julia/03b/src/initial_condition.jl new file mode 100644 index 000000000..1b9993aa8 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03b/src/initial_condition.jl @@ -0,0 +1,112 @@ +# julia/initial_condition.jl +""" +初始条件模块(Julia 版) +目标:与 Python initial_condition.py 行为完全一致 +""" + +# ---------------------- 抽象接口(Julia 无继承,用函数约定) ---------------------- +# 所有初始条件必须实现: +# evaluate_at(ic, x::AbstractVector{Float64}) -> Vector{Float64} +# apply(ic, solution) + +# ---------------------- StepFunctionIC ---------------------- +struct StepFunctionIC + config::Any +end + +function evaluate_at(ic::StepFunctionIC, x::AbstractVector{Float64}) + u0 = ones(Float64, length(x)) + for i in eachindex(x) + if 0.5 <= x[i] <= 1.0 + u0[i] = 2.0 + end + end + return u0 +end + +function apply(ic::StepFunctionIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- SineWaveIC ---------------------- +struct SineWaveIC + config::Any +end + +function evaluate_at(ic::SineWaveIC, x::AbstractVector{Float64}) + L = getfield_safe(ic.config, :domain_length, 2.0) + return sin.(2π * x / L) +end + +function apply(ic::SineWaveIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- GaussianPulseIC ---------------------- +struct GaussianPulseIC + config::Any +end + +function evaluate_at(ic::GaussianPulseIC, x::AbstractVector{Float64}) + center = getfield_safe(ic.config, :pulse_center, 0.5) + width = getfield_safe(ic.config, :pulse_width, 0.1) + return exp.(-((x .- center) ./ width).^2) +end + +function apply(ic::GaussianPulseIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- 公共辅助函数 ---------------------- +""" +将初始场 values 应用到 solution.u 的物理区域(含 ghost 的数组) +""" +function _apply_to_interior(solution, values) + domain = solution.domain + # Python: for i in range(domain.ist, domain.ied) + # Julia: for i in domain.ist:domain.ied-1 (因为 ied 是结束索引,不包含) + for i in domain.ist:(domain.ied - 1) + j = i - domain.ist + 1 # values 是 1-based,i - ist 是 0-based → +1 + solution.u[i] = values[j] + end +end + +# ---------------------- InitialConditionFactory ---------------------- +module InitialConditionFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "初始条件 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(ic_type::String, config::Any) + if !(ic_type isa AbstractString) + error("ic_type 必须为字符串,当前值: $ic_type") + end + + if !haskey(_REGISTRY, ic_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的初始条件: '$ic_type'。可用选项: $available") + end + + return _REGISTRY[ic_type](config) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("step", config -> Main.StepFunctionIC(config)) +register("sin", config -> Main.SineWaveIC(config)) +register("gaussian", config -> Main.GaussianPulseIC(config)) + +end # module InitialConditionFactory + +export InitialConditionFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03b/src/mesh.jl b/example/1d-linear-convection/weno3/julia/03b/src/mesh.jl new file mode 100644 index 000000000..1b0dd4bf9 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03b/src/mesh.jl @@ -0,0 +1,45 @@ +# julia/mesh.jl +""" +Mesh 结构体(与提供的 mesh.py 完全同构) +- 字段名、初始化顺序、循环逻辑完全一致 +- 不做任何泛化或优化 +""" + +mutable struct Mesh + xmin::Float64 + xmax::Float64 + ncells::Int + nnodes::Int + nx::Int + x::Vector{Float64} + xcc::Vector{Float64} + L::Float64 # 在 init_mesh 中赋值 + dx::Float64 # 在 init_mesh 中赋值 + + function Mesh() + # 与 Python __init__ 完全一致 + xmin = 0.0 + xmax = 2.0 + ncells = 40 + nnodes = ncells + 1 + nx = ncells + x = zeros(Float64, nnodes) + xcc = zeros(Float64, ncells) + + # 调用 init_mesh(模拟 Python) + L = xmax - xmin + dx = L / ncells + + # Generate node coordinates: for i in range(self.nnodes) + for i in 1:nnodes + x[i] = xmin + (i - 1) * dx # Python i → Julia i-1 + end + + # Generate cell center coordinates + for i in 1:ncells + xcc[i] = 0.5 * (x[i] + x[i+1]) + end + + new(xmin, xmax, ncells, nnodes, nx, x, xcc, L, dx) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03b/src/plotter.jl b/example/1d-linear-convection/weno3/julia/03b/src/plotter.jl new file mode 100644 index 000000000..a77d8d688 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03b/src/plotter.jl @@ -0,0 +1,147 @@ +# julia/plotter.jl +""" +CFDPlotter 的 Julia 实现(通过 PythonCall.jl 调用 Matplotlib) +确保与 Python plotter.py 行为完全一致 +""" + +using PythonCall + +# 初始化 Python 环境(加载 matplotlib, inflect) +const plt = pyimport("matplotlib.pyplot") +const inflect = pyimport("inflect") + +mutable struct CFDPlotter + default_styles::Dict{String, Any} + p::Py +end + +function CFDPlotter() + default_styles = Dict{String, Any}( + "numerical" => Dict( + :color => "blue", + :linestyle => "-", + :marker => "o", + :markerfacecolor => "none" + ), + "analytical" => Dict( + :color => "red", + :linestyle => "--", + :marker => "", + :linewidth => 1.5 + ), + "comparison" => [ + Dict(:color => "black", :linestyle => "-", :marker => "o", :markerfacecolor => "none"), + Dict(:color => "blue", :linestyle => "--", :marker => "s", :markerfacecolor => "none"), + Dict(:color => "green", :linestyle => ":", :marker => "^", :markerfacecolor => "none") + ] + ) + p = inflect.engine() + CFDPlotter(default_styles, p) +end + +""" +轻量即时绘图(快速验证结果) +""" +function plot_quick(plotter::CFDPlotter, cfd_result::Dict; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + rk_order = cfd_result["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = cfd_result["config"]["final_time"] + order = cfd_result["config"]["order"] + scheme = uppercase(cfd_result["config"]["scheme"]) + actual_title = "1D Convection (t=$(final_time))\n$(order)th-order $(scheme) + $(rk_str)-order RK" + end + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"]; + label="Numerical ($(uppercase(cfd_result["config"]["scheme"])))", + plotter.default_styles["numerical"]..., + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + # 通用样式 + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +""" +多格式/多精度对比绘图 +""" +function plot_comparison(plotter::CFDPlotter, result_list::Vector{Dict{String, Any}}; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + schemes = [uppercase(r["config"]["scheme"]) * string(r["config"]["order"]) for r in result_list] + rk_order = result_list[1]["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = result_list[1]["config"]["final_time"] + actual_title = "1D Convection Comparison (t=$(final_time))\n$(join(schemes, ", ")) + $(rk_str)-order RK" + end + + # 绘制多个数值解 + for (i, res) in enumerate(result_list) + style = plotter.default_styles["comparison"][mod1(i, length(plotter.default_styles["comparison"]))] + label = "Numerical ($(uppercase(res["config"]["scheme"]))$(res["config"]["order"]))" + plt.plot( + res["x"], res["numerical"]; + label=label, + style..., + markersize=5, linewidth=0.5 + ) + end + + # 绘制解析解 + plt.plot( + result_list[1]["x"], result_list[1]["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +function _set_common_style(plotter::CFDPlotter, title::String) + plt.title(title, fontsize=12) + plt.xlabel("x", fontsize=10) + plt.ylabel("u", fontsize=10) + plt.legend(fontsize=9) + plt.grid(true, color="gray", linestyle="--", linewidth=0.5, alpha=0.7) + plt.tight_layout() +end + +""" +快捷函数:ENO/WENO对比绘图 +""" +function plot_eno_weno_comparison(eno_result::Dict, weno_result::Dict; save_path=nothing) + plotter = CFDPlotter() + plot_comparison(plotter, [eno_result, weno_result]; save_path=save_path) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03b/src/reconstructor/eno.jl b/example/1d-linear-convection/weno3/julia/03b/src/reconstructor/eno.jl new file mode 100644 index 000000000..e78a636f3 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03b/src/reconstructor/eno.jl @@ -0,0 +1,107 @@ +# julia/reconstructor/eno.jl +""" +ENO 重构器(与 reconstructor/eno.py 完全同构) +""" + +# ---------------------- ENO 系数初始化 ---------------------- +function _init_eno_coef!(spatial_order::Int, coef::Matrix{Float64}) + if spatial_order == 1 + coef[1, 1] = 1.0 + coef[2, 1] = 1.0 + elseif spatial_order == 2 + coef[1, 1:2] = [3.0/2.0, -1.0/2.0] + coef[2, 1:2] = [1.0/2.0, 1.0/2.0] + coef[3, 1:2] = [-1.0/2.0, 3.0/2.0] + elseif spatial_order == 3 + coef[1, 1:3] = [11.0/6.0, -7.0/6.0, 1.0/3.0] + coef[2, 1:3] = [1.0/3.0, 5.0/6.0, -1.0/6.0] + coef[3, 1:3] = [-1.0/6.0, 5.0/6.0, 1.0/3.0] + coef[4, 1:3] = [1.0/3.0, -7.0/6.0, 11.0/6.0] + elseif spatial_order == 4 + coef[1, 1:4] = [25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0] + coef[2, 1:4] = [1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0] + coef[3, 1:4] = [-1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0] + coef[4, 1:4] = [1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0] + coef[5, 1:4] = [-1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0] + elseif spatial_order == 5 + coef[1, 1:5] = [137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0] + coef[2, 1:5] = [1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0] + coef[3, 1:5] = [-1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0] + coef[4, 1:5] = [1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0] + coef[5, 1:5] = [-1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0] + coef[6, 1:5] = [1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0] + elseif spatial_order == 6 + coef[1, 1:6] = [49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0] + coef[2, 1:6] = [1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0] + coef[3, 1:6] = [-1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0] + coef[4, 1:6] = [1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0] + coef[5, 1:6] = [-1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0] + coef[6, 1:6] = [1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0] + coef[7, 1:6] = [-1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0] + elseif spatial_order == 7 + coef[1, 1:7] = [363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0] + coef[2, 1:7] = [1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0] + coef[3, 1:7] = [-1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0] + coef[4, 1:7] = [1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0] + coef[5, 1:7] = [-1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0] + coef[6, 1:7] = [1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0] + coef[7, 1:7] = [-1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0] + coef[8, 1:7] = [1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0] + else + error("ENO 系数未实现 order=$spatial_order") + end +end + +# ---------------------- ENO 重构器 ---------------------- +mutable struct EnoReconstructor + spatial_order::Int + ntcells::Int + lmc::Vector{Int} + coef::Matrix{Float64} + dd::Matrix{Float64} + + function EnoReconstructor(spatial_order::Int, ntcells::Int) + lmc = zeros(Int, ntcells) + coef = zeros(Float64, spatial_order + 1, spatial_order) + dd = zeros(Float64, spatial_order, ntcells) + _init_eno_coef!(spatial_order, coef) + new(spatial_order, ntcells, lmc, coef, dd) + end +end + +function reconstruct(rec::EnoReconstructor, q::Vector{Float64}, cfd::Any) + # 1. 差商计算 (dd[1,:] = q) + @views rec.dd[1, :] .= q + for m in 2:rec.spatial_order + for j in 1:(rec.ntcells - m + 1) + rec.dd[m, j] = rec.dd[m-1, j+1] - rec.dd[m-1, j] + end + end + + # 2. 选择 smoothest stencil + domain = cfd.domain + for i in (domain.ist - 1):(domain.ied) # Python: range(ist-1, ied+1) → ied+1-1 = ied + rec.lmc[i] = i + for m in 2:rec.spatial_order + if abs(rec.dd[m, rec.lmc[i] - 1]) < abs(rec.dd[m, rec.lmc[i]]) + rec.lmc[i] -= 1 + end + end + end + + # 3. 重构界面值 + solution = cfd.solution + for i in domain.ist:(domain.ied) # Python: range(ist, ied+1) → ied+1-1 = ied + j = i - domain.ist + 1 # Julia 1-based + k1 = rec.lmc[i - 1] + k2 = rec.lmc[i] + r1 = (i - 1) - k1 + 1 + r2 = i - k2 + 1 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in 1:rec.spatial_order + solution.q_face_left[j] += q[k1 + m - 1] * rec.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m - 1] * rec.coef[r2, m] + end + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03b/src/reconstructor/factory.jl b/example/1d-linear-convection/weno3/julia/03b/src/reconstructor/factory.jl new file mode 100644 index 000000000..70b0cf9ff --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03b/src/reconstructor/factory.jl @@ -0,0 +1,38 @@ +# src/reconstructor/factory.jl + +""" +ReconstructorFactory +对标 Python: ReconstructorFactory.create(config, domain) +""" +module ReconstructorFactory + +include("../utils.jl") + +# ✅ 不要用 using ..EnoReconstructor(它们不是模块) +# 直接通过 Main. 引用顶层定义的类型 + +function create(config::Any, domain::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + # 处理 WENO 默认命名(如 Python) + if scheme == "weno" + order = getfield_safe(config, :spatial_order, 5) + scheme = "weno$(order)" + end + + if scheme == "eno" + order = getfield_safe(config, :spatial_order, 3) + return Main.EnoReconstructor(order, domain.ntcells) + elseif scheme == "weno3" + return Main.Weno3Reconstructor() + else + error("不支持的重建格式: $scheme(仅支持 eno/weno3)") + end +end + +end # module ReconstructorFactory + +export ReconstructorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03b/src/reconstructor/weno3.jl b/example/1d-linear-convection/weno3/julia/03b/src/reconstructor/weno3.jl new file mode 100644 index 000000000..2b6fe1abb --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03b/src/reconstructor/weno3.jl @@ -0,0 +1,65 @@ +# julia/reconstructor/weno3.jl +""" +WENO3 重构器(与 reconstructor/weno3.py 完全同构) +""" + +mutable struct Weno3Reconstructor + # 无字段,与 Python 一致 +end + +function reconstruct(rec::Weno3Reconstructor, q::Vector{Float64}, cfd::Any) + domain = cfd.domain + solution = cfd.solution + _reconstruct_left_interfaces(domain, q, solution.q_face_left) + _reconstruct_right_interfaces(domain, q, solution.q_face_right) +end + +function _reconstruct_left_interfaces(domain, u, qL) + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in (domain.ist - 1):(domain.ied - 1) + j = i - (domain.ist - 1) + 1 # ← Julia 1-based: j = i - (ist-1) 对应 Python j = i - (ist-1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = _reconstruct_from_left_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_right_interfaces(domain, u, qR) + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in domain.ist:domain.ied + j = i - domain.ist + 1 # ← Julia 1-based: j = i - ist 对应 Python j = i - ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = _reconstruct_from_right_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_from_left_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -0.5*v1 + 1.5*v2 # r=1 stencil + q1 = 0.5*v2 + 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end + +function _reconstruct_from_right_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 0.5*v1 + 0.5*v2 # r=1 stencil + q1 = 1.5*v2 - 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03b/src/registry.jl b/example/1d-linear-convection/weno3/julia/03b/src/registry.jl new file mode 100644 index 000000000..6acd9aaaf --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03b/src/registry.jl @@ -0,0 +1,164 @@ +""" +统一注册系统 + 通用工厂 +- ComponentRegistry: 组件注册表 +- @register_component: 装饰器宏 +- BaseFactory: 通用工厂接口 +""" + +module ComponentRegistry + +# 注册表:Dict{category => Dict{name => constructor}} +const _REGISTRIES = Dict{String, Dict{String, Function}}() +const _VERBOSE = Ref(true) + +# ---------------------- 注册核心逻辑 ---------------------- +""" + register(category::String, name::String, ctor::Function) + +注册一个组件构造函数到指定类别。 +如果已存在同名组件且不同,则发出警告。 +""" +function register(category::String, name::String, ctor::Function) + if !haskey(_REGISTRIES, category) + _REGISTRIES[category] = Dict{String, Function}() + end + + registry = _REGISTRIES[category] + if haskey(registry, name) + if registry[name] !== ctor && _VERBOSE[] + @warn "覆盖注册: $category.$name" + end + end + + registry[name] = ctor + if _VERBOSE[] + println("✅ 已注册: $category.$name -> $(nameof(ctor))") + end +end + +""" + create(category::String, name::String, args...; kwargs...) + +从注册表创建组件实例。 +""" +function create(category::String, name::String, args...; kwargs...) + if !haskey(_REGISTRIES, category) + error("❌ 未知类别: $category (可用: $(collect(keys(_REGISTRIES))))") + end + + registry = _REGISTRIES[category] + lname = lowercase(name) + if !haskey(registry, lname) + available = sort(collect(keys(registry))) + error("❌ 未找到: $category.$name (可用: $available)") + end + + return registry[lname](args...; kwargs...) +end + +""" + list_all() + +返回所有已注册组件(按类别)。 +""" +function list_all() + return Dict(cat => sort(collect(keys(reg))) for (cat, reg) in _REGISTRIES) +end + +""" + set_verbose(flag::Bool) + +开启/关闭注册提示。 +""" +function set_verbose(flag::Bool) + _VERBOSE[] = flag +end + +end # module ComponentRegistry + + +# ---------------------- 装饰器宏:@register_component ---------------------- +""" +@register_component(category, [name]) + +用法: + @register_component("boundary", "periodic") + struct PeriodicBoundary ... + +若省略 name,则使用类型名的小写形式。 +""" +macro register_component(category::String, name_expr) + error("@register_component 需在类型定义前使用,且必须在模块顶层") +end + +macro register_component(category::String) + error("@register_component(category, name) 需指定 name 或在类型后使用") +end + +# 重载:@register_component("category", "name") struct X ... end +macro register_component(category::String, name::String, ex) + if !Meta.isexpr(ex, :struct) + error("@register_component 必须作用于 struct 定义") + end + + struct_name = ex.args[2] + if Meta.isexpr(struct_name, :curly) + struct_name = struct_name.args[1] + end + + # 插入注册调用(在模块顶层) + quote + $(esc(ex)) + $(ComponentRegistry).register($(category), $(name), $(esc(struct_name))) + end +end + +# 重载:@register_component("category") struct X ... end → name = lowercase(nameof(X)) +macro register_component(category::String, ex) + if !Meta.isexpr(ex, :struct) + error("@register_component 必须作用于 struct 定义") + end + + struct_name = ex.args[2] + if Meta.isexpr(struct_name, :curly) + struct_name = struct_name.args[1] + end + + name_str = string(struct_name) |> lowercase + + quote + $(esc(ex)) + $(ComponentRegistry).register($(category), $(name_str), $(esc(struct_name))) + end +end + + +# ---------------------- 通用工厂 ---------------------- +module BaseFactory + +using ..ComponentRegistry + +""" + create_component(category::String, name::String, args...; kwargs...) + +通用工厂接口,与 Python BaseFactory.create_component 行为一致。 +""" +function create_component(category::String, name::String, args...; kwargs...) + return ComponentRegistry.create(category, name, args...; kwargs...) +end + +""" + get_available_components(category::String) + +列出某类别下所有可用组件。 +""" +function get_available_components(category::String) + all = ComponentRegistry.list_all() + return get(all, category, String[]) +end + +end # module BaseFactory + + +# 导出接口 +export ComponentRegistry, BaseFactory, @register_component \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03b/src/residual.jl b/example/1d-linear-convection/weno3/julia/03b/src/residual.jl new file mode 100644 index 000000000..11a885934 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03b/src/residual.jl @@ -0,0 +1,69 @@ +# julia/residual.jl +""" +残差计算器(与 residual.py 完全同构) +- 封装重建→通量→散度完整流程 +- 依赖 cfd 的多个字段 +""" + +include("mesh.jl") + +mutable struct ResidualCalculator + cfd::Any + config::Any + domain::Any + solution::Any + mesh::Mesh + reconstructor::Any + flux_calculator::Any + + function ResidualCalculator(cfd::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + mesh = domain.mesh + reconstructor = cfd.reconstructor + + # ✅ 内部创建 flux_calculator(对标 Python) + flux_calculator = FluxCalculatorFactory.create(cfd) + + new(cfd, config, domain, solution, mesh, reconstructor, flux_calculator) + end +end + + +""" +计算完整残差(对外唯一接口) +""" +function compute!(calc::ResidualCalculator) + _reconstruct(calc) + _compute_inviscid_flux(calc) + _compute_flux_divergence(calc) +end + +""" +私有方法:界面值重建 +""" +function _reconstruct(calc::ResidualCalculator) + reconstruct(calc.reconstructor, calc.solution.u, calc.cfd) +end + +""" +私有方法:计算无粘通量 +""" +function _compute_inviscid_flux(calc::ResidualCalculator) + compute!(calc.flux_calculator, + calc.solution.q_face_left, + calc.solution.q_face_right, + calc.solution.flux) +end + +""" +私有方法:计算通量散度(残差 = -dF/dx) +""" +function _compute_flux_divergence(calc::ResidualCalculator) + solution = calc.solution + mesh = calc.mesh + for i in 1:mesh.ncells + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / mesh.dx + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03b/src/solution.jl b/example/1d-linear-convection/weno3/julia/03b/src/solution.jl new file mode 100644 index 000000000..7044c0014 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03b/src/solution.jl @@ -0,0 +1,60 @@ +# julia/solution.jl +""" +Solution 结构体(与真实 solution.py 完全同构) +字段顺序、方法、逻辑 1:1 对齐 +""" + +include("mesh.jl") +include("domain.jl") +include("initial_condition.jl") + +mutable struct Solution + domain::Domain + q_face_left::Vector{Float64} + q_face_right::Vector{Float64} + flux::Vector{Float64} + res::Vector{Float64} + u::Vector{Float64} + un::Vector{Float64} + + function Solution(config::Any, domain::Domain) + mesh = domain.mesh + + q_face_left = zeros(Float64, mesh.nnodes) + q_face_right = zeros(Float64, mesh.nnodes) + flux = zeros(Float64, mesh.nnodes) + res = zeros(Float64, mesh.ncells) + u = zeros(Float64, domain.ntcells) + un = zeros(Float64, domain.ntcells) + + sol = new(domain, q_face_left, q_face_right, flux, res, u, un) + initialize_from_config(sol, config) + return sol + end +end + +""" +重置解数组为初始状态 +""" +function reset_solution(sol::Solution) + fill!(sol.u, 0.0) + fill!(sol.un, 0.0) +end + +""" +根据配置初始化场 +""" + +function initialize_from_config(sol::Solution, config::Any) + ic_type = getfield_safe(config, :ic_type, "step") + ic = InitialConditionFactory.create(ic_type, config) + apply(ic, sol) +end + +""" +更新旧场:un = u +""" +function update_old_field(sol::Solution) + sol.un .= sol.u # 等价于 Python 的 un[:] = u[:] +end + diff --git a/example/1d-linear-convection/weno3/julia/03b/src/solver.jl b/example/1d-linear-convection/weno3/julia/03b/src/solver.jl new file mode 100644 index 000000000..50ae55db1 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03b/src/solver.jl @@ -0,0 +1,130 @@ +# julia/solver.jl +""" +CFD 求解器主类(与 solver.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("initial_condition.jl") +include("boundary.jl") +include("flux/flux.jl") +include("residual.jl") +include("time_integration.jl") +include("reconstructor/eno.jl") +include("reconstructor/weno3.jl") +include("reconstructor/factory.jl") + +# 导入工厂模块(必须在顶层!) +using .FluxCalculatorFactory +using .TimeIntegratorFactory +using .BoundaryConditionFactory +using .ReconstructorFactory + +# ---------------------- Cfd 求解器 ---------------------- +mutable struct Cfd + config::Any + domain::Domain + solution::Solution + reconstructor::Any + residual_calculator::ResidualCalculator + integrator::Any + boundary_condition::Any + result::Dict{String, Any} + + function Cfd(config::Any, mesh::Mesh) + domain = Domain(config, mesh) + solution = Solution(config, domain) + reconstructor = ReconstructorFactory.create(config, domain) + + # 1. 初始上下文(仅包含不依赖其他组件的字段) + full_cfd = ( + config = config, + domain = domain, + solution = solution, + reconstructor = reconstructor + # 注意:不预先占位 nothing! + ) + + # 2. 创建 boundary_condition(只依赖 config + domain) + boundary_condition = BoundaryConditionFactory.create(full_cfd) + full_cfd = merge(full_cfd, (boundary_condition = boundary_condition,)) + + # 3. 创建 residual_calculator(依赖上面所有字段) + residual_calculator = ResidualCalculator(full_cfd) + full_cfd = merge(full_cfd, (residual_calculator = residual_calculator,)) + + # 4. 创建 integrator(依赖 residual_calculator 等) + integrator = TimeIntegratorFactory.create(full_cfd) + full_cfd = merge(full_cfd, (integrator = integrator,)) + + # 5. 注入完整 self 到组件(确保它们能访问彼此) + residual_calculator.cfd = full_cfd + integrator.base.cfd = full_cfd + + result = Dict{String, Any}() + new(config, domain, solution, reconstructor, residual_calculator, integrator, boundary_condition, result) + end +end + +""" +通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界 +""" +function exact_solution(cfd::Cfd) + x = cfd.domain.mesh.xcc + T = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = @. (x - c * T + L) % L + + # ✅ 使用工厂创建初始条件(对标 Python) + ic = InitialConditionFactory.create(cfd.config.ic_type, cfd.config) + + return evaluate_at(ic, x_shifted) +end + +""" +主求解循环 +""" +function run!(cfd::Cfd) + # 应用初始边界条件并同步 old field + apply!(cfd.boundary_condition, cfd.solution.u) + update_old_field(cfd.solution) + + t = 0.0 + dt_old = cfd.config.dt + dt = dt_old + + while t < cfd.config.final_time + if t + dt > cfd.config.final_time + dt = cfd.config.final_time - t + end + #@show t, dt, maximum(cfd.solution.u), minimum(cfd.solution.u) + # 执行时间步 + step(cfd.integrator, dt) + t += dt + end + + # 恢复 dt + cfd.config.dt = dt_old + + # 整理结果 + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied-1] # Python: [ist:ied] + analytical = exact_solution(cfd) + + cfd.result = Dict( + "x" => cfd.domain.mesh.xcc, + "numerical" => u_numerical, + "analytical" => analytical, + "config" => Dict( + "scheme" => cfd.config.recon_scheme, + "order" => cfd.config.spatial_order, + "rk_order" => cfd.config.rk_order, + "final_time" => cfd.config.final_time + ) + ) + + return u_numerical +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03b/src/time_integration.jl b/example/1d-linear-convection/weno3/julia/03b/src/time_integration.jl new file mode 100644 index 000000000..add564ba5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03b/src/time_integration.jl @@ -0,0 +1,169 @@ +# julia/time_integration.jl +""" +时间推进器模块(与 time_integration.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("residual.jl") +include("boundary.jl") + +# ---------------------- 抽象时间推进器基类 ---------------------- +abstract type TimeIntegrator end + +mutable struct TimeIntegratorBase <: TimeIntegrator + cfd::Any + config::Any + domain::Domain + solution::Solution + residual_calculator::Any # ResidualCalculator +end + +function TimeIntegratorBase(cfd::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + residual_calculator = cfd.residual_calculator + TimeIntegratorBase(cfd, config, domain, solution, residual_calculator) +end + +function compute_residual(integrator::TimeIntegratorBase) + compute!(integrator.residual_calculator) +end + +function apply_boundary(integrator::TimeIntegratorBase) + apply!(integrator.cfd.boundary_condition, integrator.solution.u) +end + +function map_idx(integrator::TimeIntegratorBase, i::Int) + return i - integrator.domain.ist + 1 # ← +1 转为 1-based +end + +# ---------------------- RK1Integrator ---------------------- +mutable struct RK1Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK1Integrator(cfd::Any) + RK1Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK1Integrator, dt::Float64) + compute_residual(integrator.base) + for i in integrator.base.domain.ist:(integrator.base.domain.ied - 1) + j = map_idx(integrator.base, i) + integrator.base.solution.u[i] += dt * integrator.base.solution.res[j] + end + apply_boundary(integrator.base) + update_old_field(integrator.base.solution) +end + +# ---------------------- RK2Integrator ---------------------- +mutable struct RK2Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK2Integrator(cfd::Any) + RK2Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK2Integrator, dt::Float64) + base = integrator.base + # 阶段1:预测步 + compute_residual(base) + u_pred = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u_pred[i] += dt * base.solution.res[j] + end + base.solution.u .= u_pred + apply_boundary(base) + # 阶段2:校正步 + compute_residual(base) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = 0.5 * base.solution.un[i] + 0.5 * base.solution.u[i] + 0.5 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end + +# ---------------------- RK3Integrator ---------------------- +mutable struct RK3Integrator <: TimeIntegrator + base::TimeIntegratorBase +end + +function RK3Integrator(cfd::Any) + RK3Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK3Integrator, dt::Float64) + base = integrator.base + # 阶段1 + compute_residual(base) + u1 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u1[i] += dt * base.solution.res[j] + end + base.solution.u .= u1 + apply_boundary(base) + # 阶段2 + compute_residual(base) + u2 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u2[i] = 0.75 * base.solution.un[i] + 0.25 * base.solution.u[i] + 0.25 * dt * base.solution.res[j] + end + base.solution.u .= u2 + apply_boundary(base) + # 阶段3 + compute_residual(base) + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = c1 * base.solution.un[i] + c2 * base.solution.u[i] + c3 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end + +# ---------------------- TimeIntegratorFactory ---------------------- +module TimeIntegratorFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "时间积分器 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + rk_order = cfd.config.rk_order + if !(rk_order isa Integer) || rk_order < 1 + error("rk_order 必须为正整数,当前值: $rk_order") + end + + name = "rk$rk_order" + if !haskey(_REGISTRY, name) + available = sort(collect(keys(_REGISTRY))) + error("未注册的时间积分器: '$name'。可用选项: $available") + end + + return _REGISTRY[name](cfd) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("rk1", cfd -> Main.RK1Integrator(cfd)) +register("rk2", cfd -> Main.RK2Integrator(cfd)) +register("rk3", cfd -> Main.RK3Integrator(cfd)) + +end # module TimeIntegratorFactory + +export TimeIntegratorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03b/src/utils.jl b/example/1d-linear-convection/weno3/julia/03b/src/utils.jl new file mode 100644 index 000000000..3786cd1a5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03b/src/utils.jl @@ -0,0 +1,9 @@ +# src/utils.jl + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end diff --git a/example/1d-linear-convection/weno3/julia/03c/examples/run_eno_weno.jl b/example/1d-linear-convection/weno3/julia/03c/examples/run_eno_weno.jl new file mode 100644 index 000000000..da24e6cd7 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03c/examples/run_eno_weno.jl @@ -0,0 +1,50 @@ +# examples/run_eno_weno.jl +""" +1:1 复刻 run_eno_weno.py 的 Julia 版本 +""" + +include("../src/config.jl") +include("../src/mesh.jl") +include("../src/solver.jl") +include("../src/plotter.jl") + +function performEnoWenoAnalysis() + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO3 求解 + println("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + with_reconstruction(config_eno3, "eno", 3) # 显式指定 3 阶 + config_eno3.dt = 0.0025 # 覆盖默认值 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + run!(cfd_eno3) # 求解并生成 result 字典 + + # 3. 配置并运行 WENO3 求解 + println("Running WENO3 solver...") + config_weno3 = CfdConfig() + with_reconstruction(config_weno3, "weno", 3) # 显式指定 3 阶(WENO 默认 5 阶) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + run!(cfd_weno3) + + # 5. 绘制 ENO/WENO 对比图 + println("Plotting comparison results...") + plot_eno_weno_comparison( + cfd_eno3.result, + cfd_weno3.result; + save_path="eno_weno_comparison.png" + ) + + return cfd_eno3, cfd_weno3 +end + +# 主程序入口 +if abspath(PROGRAM_FILE) == @__FILE__ + performEnoWenoAnalysis() + println("Analysis completed!") +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03c/src/boundary.jl b/example/1d-linear-convection/weno3/julia/03c/src/boundary.jl new file mode 100644 index 000000000..82f58b71f --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03c/src/boundary.jl @@ -0,0 +1,121 @@ +# julia/boundary.jl +# 目标:与 Python boundary.py 行为完全一致(1:1 同构) +# 前提:ist/ied 在 Julia 中按 1-based 设置(ist = nghosts + 1) + +#using NPZ # 仅用于测试,实际模块可不依赖 + +# ---------------------- 抽象基类(Julia 风格:用函数接口) ---------------------- +# Julia 无 abstract class,用文档+约定 +# 所有子类型必须实现 apply!(bc, u) + +# ---------------------- PeriodicBoundary ---------------------- +mutable struct PeriodicBoundary + cfd::Any +end + +function apply!(self::PeriodicBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist # 1-based, e.g., 3 + ied = self.cfd.domain.ied # 1-based, e.g., 43 + + # 左 ghost: u[ist - 1 - ig] = u[ied - 1 - ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ied - 1 - ig] + end + # 右 ghost: u[ied + ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ied + ig] = u[ist + ig] + end +end + +# ---------------------- DirichletBoundary ---------------------- +mutable struct DirichletBoundary + cfd::Any +end + +function apply!(self::DirichletBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + left_value = getfield_safe(self.cfd.config, :left_boundary_value, 1.0) + right_value = getfield_safe(self.cfd.config, :right_boundary_value, 2.0) + + # 左边界 + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = left_value + end + # 右边界 + for ig in 0:(nghosts-1) + u[ied + ig] = right_value + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Dirichlet边界: 左值=$left_value, 右值=$right_value") + end +end + +# ---------------------- NeumannBoundary ---------------------- +mutable struct NeumannBoundary + cfd::Any +end + +function apply!(self::NeumannBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + # 左边界零梯度:u[ist - 1 - ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ist + ig] + end + # 右边界零梯度:u[ied + ig] = u[ied - 1 - ig] ← 注意是 ied - 1 - ig + for ig in 0:(nghosts-1) + u[ied + ig] = u[ied - 1 - ig] + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Neumann边界: 零梯度") + end +end + + +# ---------------------- BoundaryConditionFactory ---------------------- +module BoundaryConditionFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "边界条件 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + bc_type = cfd.config.boundary_type + if !(bc_type isa AbstractString) + error("boundary_type 必须为字符串,当前值: $bc_type") + end + + if !haskey(_REGISTRY, bc_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的边界条件: '$bc_type'。可用选项: $available") + end + + return _REGISTRY[bc_type](cfd) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("periodic", cfd -> Main.PeriodicBoundary(cfd)) +register("dirichlet", cfd -> Main.DirichletBoundary(cfd)) +register("neumann", cfd -> Main.NeumannBoundary(cfd)) + +end # module BoundaryConditionFactory + +export BoundaryConditionFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03c/src/config.jl b/example/1d-linear-convection/weno3/julia/03c/src/config.jl new file mode 100644 index 000000000..dd34fe7ab --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03c/src/config.jl @@ -0,0 +1,69 @@ +# julia/config.jl +""" +CfdConfig:与 Python config.py 完全同构 +""" +mutable struct CfdConfig + ic_type::String + recon_scheme::String + flux_type::String + rk_order::Int + wave_speed::Float64 + final_time::Float64 + dt::Float64 + boundary_type::String + left_boundary_value::Float64 + right_boundary_value::Float64 + spatial_order::Int + + function CfdConfig() + new( + "step", + "eno", + "rusanov", + 1, + 1.0, + 0.625, + 0.025, + "periodic", + 1.0, + 2.0, + 2 + ) + end +end + +""" +专用配置:重建方案(链式调用) +""" +function with_reconstruction(cfg::CfdConfig, scheme::String, order::Union{Int, Nothing}=nothing) + cfg.recon_scheme = lowercase(scheme) + + if order !== nothing + cfg.spatial_order = order + else + if startswith(cfg.recon_scheme, "weno") + cfg.spatial_order = 5 + elseif cfg.recon_scheme == "eno" + cfg.spatial_order = 3 + else + error("不支持的重建格式:$scheme(仅支持 eno/weno)") + end + end + + return cfg # 支持链式调用 +end + +""" +专用配置:边界条件(链式调用) +""" +function with_boundary(cfg::CfdConfig, bc_type::String; left_value=nothing, right_value=nothing) + cfg.boundary_type = bc_type + if left_value !== nothing + cfg.left_boundary_value = left_value + end + if right_value !== nothing + cfg.right_boundary_value = right_value + end + return cfg +end + diff --git a/example/1d-linear-convection/weno3/julia/03c/src/domain.jl b/example/1d-linear-convection/weno3/julia/03c/src/domain.jl new file mode 100644 index 000000000..eec261142 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03c/src/domain.jl @@ -0,0 +1,58 @@ +# julia/domain.jl +""" +Domain 结构体(与 domain.py 完全同构) +- 保存 config +- nghosts 计算逻辑 1:1 +- ist/ied 为 0-based(与 Python 一致) +""" + +include("mesh.jl") +include("utils.jl") + +mutable struct Domain + config::Any # 保存 config(与 Python 一致) + mesh::Mesh + nghosts::Int + ist::Int # 0-based + ied::Int # 0-based + ntcells::Int +end + +function Domain(config::Any, mesh::Mesh) + nghosts = _calc_nghosts(config) + ist = nghosts + 1 # ← 1-based 起始索引 + ied = ist + mesh.ncells # ← 1-based 结束索引(不包含) + ntcells = mesh.ncells + 2 * nghosts + Domain(config, mesh, nghosts, ist, ied, ntcells) +end + +function _calc_nghosts(config::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + order = getfield_safe(config, :spatial_order, 2) + + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + if scheme == "eno" + nghosts = order + elseif startswith(scheme, "weno") + nghosts = order ÷ 2 + 1 + else + error("未知重建格式 $(scheme),无法计算ghost层!") + end + + if nghosts <= 0 + error("计算得到的ghost层数量无效:$(nghosts)(阶数$(order),格式$(scheme))") + end + + return nghosts +end + +function is_physical_cell(domain::Domain, idx::Int) + return domain.ist <= idx < domain.ied +end + +function get_physical_indices(domain::Domain) + return domain.ist:(domain.ied - 1) +end diff --git a/example/1d-linear-convection/weno3/julia/03c/src/flux/base.jl b/example/1d-linear-convection/weno3/julia/03c/src/flux/base.jl new file mode 100644 index 000000000..58dd6cc90 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03c/src/flux/base.jl @@ -0,0 +1,7 @@ +# src/flux/base.jl +""" +抽象通量计算器接口 +Julia 无 ABC,用文档约定: +- 所有子类型必须实现 `compute!(calc, qL, qR, flux)` +""" +abstract type InviscidFluxCalculator end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03c/src/flux/engquist_osher.jl b/example/1d-linear-convection/weno3/julia/03c/src/flux/engquist_osher.jl new file mode 100644 index 000000000..c0de183d5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03c/src/flux/engquist_osher.jl @@ -0,0 +1,25 @@ +# src/flux/engquist_osher.jl +mutable struct EngquistOsherFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function EngquistOsherFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::EngquistOsherFluxCalculator, qL::Vector{Float64}, qR::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + c = calc.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = qL[i] + u_R = qR[i] + flux[i] = cp * u_L + cm * u_R + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03c/src/flux/factory.jl b/example/1d-linear-convection/weno3/julia/03c/src/flux/factory.jl new file mode 100644 index 000000000..6ae4917db --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03c/src/flux/factory.jl @@ -0,0 +1,26 @@ +# src/flux/factory.jl + +module FluxCalculatorFactory + +# 全局注册表 +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "通量计算器 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + flux_type = cfd.config.flux_type + if !haskey(_REGISTRY, flux_type) + error("未注册的通量计算器: '$flux_type'。可用: $(keys(_REGISTRY))") + end + return _REGISTRY[flux_type](cfd) +end + +register("rusanov", cfd -> Main.RusanovFluxCalculator(cfd)) +register("engquist-osher", cfd -> Main.EngquistOsherFluxCalculator(cfd)) + +end # module FluxCalculatorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03c/src/flux/flux.jl b/example/1d-linear-convection/weno3/julia/03c/src/flux/flux.jl new file mode 100644 index 000000000..533f034c8 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03c/src/flux/flux.jl @@ -0,0 +1,10 @@ +# src/flux/flux.jl + +# 加载组件(顺序很重要!) +include("base.jl") +include("rusanov.jl") +include("engquist_osher.jl") +include("factory.jl") + +# 导出(如果你未来用模块) +# export InviscidFluxCalculator, RusanovFluxCalculator, EngquistOsherFluxCalculator, FluxCalculatorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03c/src/flux/rusanov.jl b/example/1d-linear-convection/weno3/julia/03c/src/flux/rusanov.jl new file mode 100644 index 000000000..70c20fc3c --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03c/src/flux/rusanov.jl @@ -0,0 +1,28 @@ +# src/flux/rusanov.jl + +mutable struct RusanovFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function RusanovFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::RusanovFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = calc.wave_speed + c_R = calc.wave_speed + F_L = c_L * u_L + F_R = c_R * u_R + Smax = max(abs(c_L), abs(c_R)) + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03c/src/initial_condition.jl b/example/1d-linear-convection/weno3/julia/03c/src/initial_condition.jl new file mode 100644 index 000000000..1b9993aa8 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03c/src/initial_condition.jl @@ -0,0 +1,112 @@ +# julia/initial_condition.jl +""" +初始条件模块(Julia 版) +目标:与 Python initial_condition.py 行为完全一致 +""" + +# ---------------------- 抽象接口(Julia 无继承,用函数约定) ---------------------- +# 所有初始条件必须实现: +# evaluate_at(ic, x::AbstractVector{Float64}) -> Vector{Float64} +# apply(ic, solution) + +# ---------------------- StepFunctionIC ---------------------- +struct StepFunctionIC + config::Any +end + +function evaluate_at(ic::StepFunctionIC, x::AbstractVector{Float64}) + u0 = ones(Float64, length(x)) + for i in eachindex(x) + if 0.5 <= x[i] <= 1.0 + u0[i] = 2.0 + end + end + return u0 +end + +function apply(ic::StepFunctionIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- SineWaveIC ---------------------- +struct SineWaveIC + config::Any +end + +function evaluate_at(ic::SineWaveIC, x::AbstractVector{Float64}) + L = getfield_safe(ic.config, :domain_length, 2.0) + return sin.(2π * x / L) +end + +function apply(ic::SineWaveIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- GaussianPulseIC ---------------------- +struct GaussianPulseIC + config::Any +end + +function evaluate_at(ic::GaussianPulseIC, x::AbstractVector{Float64}) + center = getfield_safe(ic.config, :pulse_center, 0.5) + width = getfield_safe(ic.config, :pulse_width, 0.1) + return exp.(-((x .- center) ./ width).^2) +end + +function apply(ic::GaussianPulseIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- 公共辅助函数 ---------------------- +""" +将初始场 values 应用到 solution.u 的物理区域(含 ghost 的数组) +""" +function _apply_to_interior(solution, values) + domain = solution.domain + # Python: for i in range(domain.ist, domain.ied) + # Julia: for i in domain.ist:domain.ied-1 (因为 ied 是结束索引,不包含) + for i in domain.ist:(domain.ied - 1) + j = i - domain.ist + 1 # values 是 1-based,i - ist 是 0-based → +1 + solution.u[i] = values[j] + end +end + +# ---------------------- InitialConditionFactory ---------------------- +module InitialConditionFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "初始条件 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(ic_type::String, config::Any) + if !(ic_type isa AbstractString) + error("ic_type 必须为字符串,当前值: $ic_type") + end + + if !haskey(_REGISTRY, ic_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的初始条件: '$ic_type'。可用选项: $available") + end + + return _REGISTRY[ic_type](config) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("step", config -> Main.StepFunctionIC(config)) +register("sin", config -> Main.SineWaveIC(config)) +register("gaussian", config -> Main.GaussianPulseIC(config)) + +end # module InitialConditionFactory + +export InitialConditionFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03c/src/mesh.jl b/example/1d-linear-convection/weno3/julia/03c/src/mesh.jl new file mode 100644 index 000000000..1b0dd4bf9 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03c/src/mesh.jl @@ -0,0 +1,45 @@ +# julia/mesh.jl +""" +Mesh 结构体(与提供的 mesh.py 完全同构) +- 字段名、初始化顺序、循环逻辑完全一致 +- 不做任何泛化或优化 +""" + +mutable struct Mesh + xmin::Float64 + xmax::Float64 + ncells::Int + nnodes::Int + nx::Int + x::Vector{Float64} + xcc::Vector{Float64} + L::Float64 # 在 init_mesh 中赋值 + dx::Float64 # 在 init_mesh 中赋值 + + function Mesh() + # 与 Python __init__ 完全一致 + xmin = 0.0 + xmax = 2.0 + ncells = 40 + nnodes = ncells + 1 + nx = ncells + x = zeros(Float64, nnodes) + xcc = zeros(Float64, ncells) + + # 调用 init_mesh(模拟 Python) + L = xmax - xmin + dx = L / ncells + + # Generate node coordinates: for i in range(self.nnodes) + for i in 1:nnodes + x[i] = xmin + (i - 1) * dx # Python i → Julia i-1 + end + + # Generate cell center coordinates + for i in 1:ncells + xcc[i] = 0.5 * (x[i] + x[i+1]) + end + + new(xmin, xmax, ncells, nnodes, nx, x, xcc, L, dx) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03c/src/plotter.jl b/example/1d-linear-convection/weno3/julia/03c/src/plotter.jl new file mode 100644 index 000000000..a77d8d688 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03c/src/plotter.jl @@ -0,0 +1,147 @@ +# julia/plotter.jl +""" +CFDPlotter 的 Julia 实现(通过 PythonCall.jl 调用 Matplotlib) +确保与 Python plotter.py 行为完全一致 +""" + +using PythonCall + +# 初始化 Python 环境(加载 matplotlib, inflect) +const plt = pyimport("matplotlib.pyplot") +const inflect = pyimport("inflect") + +mutable struct CFDPlotter + default_styles::Dict{String, Any} + p::Py +end + +function CFDPlotter() + default_styles = Dict{String, Any}( + "numerical" => Dict( + :color => "blue", + :linestyle => "-", + :marker => "o", + :markerfacecolor => "none" + ), + "analytical" => Dict( + :color => "red", + :linestyle => "--", + :marker => "", + :linewidth => 1.5 + ), + "comparison" => [ + Dict(:color => "black", :linestyle => "-", :marker => "o", :markerfacecolor => "none"), + Dict(:color => "blue", :linestyle => "--", :marker => "s", :markerfacecolor => "none"), + Dict(:color => "green", :linestyle => ":", :marker => "^", :markerfacecolor => "none") + ] + ) + p = inflect.engine() + CFDPlotter(default_styles, p) +end + +""" +轻量即时绘图(快速验证结果) +""" +function plot_quick(plotter::CFDPlotter, cfd_result::Dict; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + rk_order = cfd_result["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = cfd_result["config"]["final_time"] + order = cfd_result["config"]["order"] + scheme = uppercase(cfd_result["config"]["scheme"]) + actual_title = "1D Convection (t=$(final_time))\n$(order)th-order $(scheme) + $(rk_str)-order RK" + end + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"]; + label="Numerical ($(uppercase(cfd_result["config"]["scheme"])))", + plotter.default_styles["numerical"]..., + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + # 通用样式 + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +""" +多格式/多精度对比绘图 +""" +function plot_comparison(plotter::CFDPlotter, result_list::Vector{Dict{String, Any}}; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + schemes = [uppercase(r["config"]["scheme"]) * string(r["config"]["order"]) for r in result_list] + rk_order = result_list[1]["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = result_list[1]["config"]["final_time"] + actual_title = "1D Convection Comparison (t=$(final_time))\n$(join(schemes, ", ")) + $(rk_str)-order RK" + end + + # 绘制多个数值解 + for (i, res) in enumerate(result_list) + style = plotter.default_styles["comparison"][mod1(i, length(plotter.default_styles["comparison"]))] + label = "Numerical ($(uppercase(res["config"]["scheme"]))$(res["config"]["order"]))" + plt.plot( + res["x"], res["numerical"]; + label=label, + style..., + markersize=5, linewidth=0.5 + ) + end + + # 绘制解析解 + plt.plot( + result_list[1]["x"], result_list[1]["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +function _set_common_style(plotter::CFDPlotter, title::String) + plt.title(title, fontsize=12) + plt.xlabel("x", fontsize=10) + plt.ylabel("u", fontsize=10) + plt.legend(fontsize=9) + plt.grid(true, color="gray", linestyle="--", linewidth=0.5, alpha=0.7) + plt.tight_layout() +end + +""" +快捷函数:ENO/WENO对比绘图 +""" +function plot_eno_weno_comparison(eno_result::Dict, weno_result::Dict; save_path=nothing) + plotter = CFDPlotter() + plot_comparison(plotter, [eno_result, weno_result]; save_path=save_path) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03c/src/reconstructor/eno.jl b/example/1d-linear-convection/weno3/julia/03c/src/reconstructor/eno.jl new file mode 100644 index 000000000..e78a636f3 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03c/src/reconstructor/eno.jl @@ -0,0 +1,107 @@ +# julia/reconstructor/eno.jl +""" +ENO 重构器(与 reconstructor/eno.py 完全同构) +""" + +# ---------------------- ENO 系数初始化 ---------------------- +function _init_eno_coef!(spatial_order::Int, coef::Matrix{Float64}) + if spatial_order == 1 + coef[1, 1] = 1.0 + coef[2, 1] = 1.0 + elseif spatial_order == 2 + coef[1, 1:2] = [3.0/2.0, -1.0/2.0] + coef[2, 1:2] = [1.0/2.0, 1.0/2.0] + coef[3, 1:2] = [-1.0/2.0, 3.0/2.0] + elseif spatial_order == 3 + coef[1, 1:3] = [11.0/6.0, -7.0/6.0, 1.0/3.0] + coef[2, 1:3] = [1.0/3.0, 5.0/6.0, -1.0/6.0] + coef[3, 1:3] = [-1.0/6.0, 5.0/6.0, 1.0/3.0] + coef[4, 1:3] = [1.0/3.0, -7.0/6.0, 11.0/6.0] + elseif spatial_order == 4 + coef[1, 1:4] = [25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0] + coef[2, 1:4] = [1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0] + coef[3, 1:4] = [-1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0] + coef[4, 1:4] = [1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0] + coef[5, 1:4] = [-1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0] + elseif spatial_order == 5 + coef[1, 1:5] = [137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0] + coef[2, 1:5] = [1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0] + coef[3, 1:5] = [-1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0] + coef[4, 1:5] = [1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0] + coef[5, 1:5] = [-1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0] + coef[6, 1:5] = [1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0] + elseif spatial_order == 6 + coef[1, 1:6] = [49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0] + coef[2, 1:6] = [1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0] + coef[3, 1:6] = [-1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0] + coef[4, 1:6] = [1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0] + coef[5, 1:6] = [-1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0] + coef[6, 1:6] = [1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0] + coef[7, 1:6] = [-1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0] + elseif spatial_order == 7 + coef[1, 1:7] = [363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0] + coef[2, 1:7] = [1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0] + coef[3, 1:7] = [-1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0] + coef[4, 1:7] = [1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0] + coef[5, 1:7] = [-1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0] + coef[6, 1:7] = [1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0] + coef[7, 1:7] = [-1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0] + coef[8, 1:7] = [1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0] + else + error("ENO 系数未实现 order=$spatial_order") + end +end + +# ---------------------- ENO 重构器 ---------------------- +mutable struct EnoReconstructor + spatial_order::Int + ntcells::Int + lmc::Vector{Int} + coef::Matrix{Float64} + dd::Matrix{Float64} + + function EnoReconstructor(spatial_order::Int, ntcells::Int) + lmc = zeros(Int, ntcells) + coef = zeros(Float64, spatial_order + 1, spatial_order) + dd = zeros(Float64, spatial_order, ntcells) + _init_eno_coef!(spatial_order, coef) + new(spatial_order, ntcells, lmc, coef, dd) + end +end + +function reconstruct(rec::EnoReconstructor, q::Vector{Float64}, cfd::Any) + # 1. 差商计算 (dd[1,:] = q) + @views rec.dd[1, :] .= q + for m in 2:rec.spatial_order + for j in 1:(rec.ntcells - m + 1) + rec.dd[m, j] = rec.dd[m-1, j+1] - rec.dd[m-1, j] + end + end + + # 2. 选择 smoothest stencil + domain = cfd.domain + for i in (domain.ist - 1):(domain.ied) # Python: range(ist-1, ied+1) → ied+1-1 = ied + rec.lmc[i] = i + for m in 2:rec.spatial_order + if abs(rec.dd[m, rec.lmc[i] - 1]) < abs(rec.dd[m, rec.lmc[i]]) + rec.lmc[i] -= 1 + end + end + end + + # 3. 重构界面值 + solution = cfd.solution + for i in domain.ist:(domain.ied) # Python: range(ist, ied+1) → ied+1-1 = ied + j = i - domain.ist + 1 # Julia 1-based + k1 = rec.lmc[i - 1] + k2 = rec.lmc[i] + r1 = (i - 1) - k1 + 1 + r2 = i - k2 + 1 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in 1:rec.spatial_order + solution.q_face_left[j] += q[k1 + m - 1] * rec.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m - 1] * rec.coef[r2, m] + end + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03c/src/reconstructor/factory.jl b/example/1d-linear-convection/weno3/julia/03c/src/reconstructor/factory.jl new file mode 100644 index 000000000..70b0cf9ff --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03c/src/reconstructor/factory.jl @@ -0,0 +1,38 @@ +# src/reconstructor/factory.jl + +""" +ReconstructorFactory +对标 Python: ReconstructorFactory.create(config, domain) +""" +module ReconstructorFactory + +include("../utils.jl") + +# ✅ 不要用 using ..EnoReconstructor(它们不是模块) +# 直接通过 Main. 引用顶层定义的类型 + +function create(config::Any, domain::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + # 处理 WENO 默认命名(如 Python) + if scheme == "weno" + order = getfield_safe(config, :spatial_order, 5) + scheme = "weno$(order)" + end + + if scheme == "eno" + order = getfield_safe(config, :spatial_order, 3) + return Main.EnoReconstructor(order, domain.ntcells) + elseif scheme == "weno3" + return Main.Weno3Reconstructor() + else + error("不支持的重建格式: $scheme(仅支持 eno/weno3)") + end +end + +end # module ReconstructorFactory + +export ReconstructorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03c/src/reconstructor/weno3.jl b/example/1d-linear-convection/weno3/julia/03c/src/reconstructor/weno3.jl new file mode 100644 index 000000000..2b6fe1abb --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03c/src/reconstructor/weno3.jl @@ -0,0 +1,65 @@ +# julia/reconstructor/weno3.jl +""" +WENO3 重构器(与 reconstructor/weno3.py 完全同构) +""" + +mutable struct Weno3Reconstructor + # 无字段,与 Python 一致 +end + +function reconstruct(rec::Weno3Reconstructor, q::Vector{Float64}, cfd::Any) + domain = cfd.domain + solution = cfd.solution + _reconstruct_left_interfaces(domain, q, solution.q_face_left) + _reconstruct_right_interfaces(domain, q, solution.q_face_right) +end + +function _reconstruct_left_interfaces(domain, u, qL) + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in (domain.ist - 1):(domain.ied - 1) + j = i - (domain.ist - 1) + 1 # ← Julia 1-based: j = i - (ist-1) 对应 Python j = i - (ist-1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = _reconstruct_from_left_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_right_interfaces(domain, u, qR) + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in domain.ist:domain.ied + j = i - domain.ist + 1 # ← Julia 1-based: j = i - ist 对应 Python j = i - ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = _reconstruct_from_right_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_from_left_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -0.5*v1 + 1.5*v2 # r=1 stencil + q1 = 0.5*v2 + 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end + +function _reconstruct_from_right_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 0.5*v1 + 0.5*v2 # r=1 stencil + q1 = 1.5*v2 - 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03c/src/registry.jl b/example/1d-linear-convection/weno3/julia/03c/src/registry.jl new file mode 100644 index 000000000..6acd9aaaf --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03c/src/registry.jl @@ -0,0 +1,164 @@ +""" +统一注册系统 + 通用工厂 +- ComponentRegistry: 组件注册表 +- @register_component: 装饰器宏 +- BaseFactory: 通用工厂接口 +""" + +module ComponentRegistry + +# 注册表:Dict{category => Dict{name => constructor}} +const _REGISTRIES = Dict{String, Dict{String, Function}}() +const _VERBOSE = Ref(true) + +# ---------------------- 注册核心逻辑 ---------------------- +""" + register(category::String, name::String, ctor::Function) + +注册一个组件构造函数到指定类别。 +如果已存在同名组件且不同,则发出警告。 +""" +function register(category::String, name::String, ctor::Function) + if !haskey(_REGISTRIES, category) + _REGISTRIES[category] = Dict{String, Function}() + end + + registry = _REGISTRIES[category] + if haskey(registry, name) + if registry[name] !== ctor && _VERBOSE[] + @warn "覆盖注册: $category.$name" + end + end + + registry[name] = ctor + if _VERBOSE[] + println("✅ 已注册: $category.$name -> $(nameof(ctor))") + end +end + +""" + create(category::String, name::String, args...; kwargs...) + +从注册表创建组件实例。 +""" +function create(category::String, name::String, args...; kwargs...) + if !haskey(_REGISTRIES, category) + error("❌ 未知类别: $category (可用: $(collect(keys(_REGISTRIES))))") + end + + registry = _REGISTRIES[category] + lname = lowercase(name) + if !haskey(registry, lname) + available = sort(collect(keys(registry))) + error("❌ 未找到: $category.$name (可用: $available)") + end + + return registry[lname](args...; kwargs...) +end + +""" + list_all() + +返回所有已注册组件(按类别)。 +""" +function list_all() + return Dict(cat => sort(collect(keys(reg))) for (cat, reg) in _REGISTRIES) +end + +""" + set_verbose(flag::Bool) + +开启/关闭注册提示。 +""" +function set_verbose(flag::Bool) + _VERBOSE[] = flag +end + +end # module ComponentRegistry + + +# ---------------------- 装饰器宏:@register_component ---------------------- +""" +@register_component(category, [name]) + +用法: + @register_component("boundary", "periodic") + struct PeriodicBoundary ... + +若省略 name,则使用类型名的小写形式。 +""" +macro register_component(category::String, name_expr) + error("@register_component 需在类型定义前使用,且必须在模块顶层") +end + +macro register_component(category::String) + error("@register_component(category, name) 需指定 name 或在类型后使用") +end + +# 重载:@register_component("category", "name") struct X ... end +macro register_component(category::String, name::String, ex) + if !Meta.isexpr(ex, :struct) + error("@register_component 必须作用于 struct 定义") + end + + struct_name = ex.args[2] + if Meta.isexpr(struct_name, :curly) + struct_name = struct_name.args[1] + end + + # 插入注册调用(在模块顶层) + quote + $(esc(ex)) + $(ComponentRegistry).register($(category), $(name), $(esc(struct_name))) + end +end + +# 重载:@register_component("category") struct X ... end → name = lowercase(nameof(X)) +macro register_component(category::String, ex) + if !Meta.isexpr(ex, :struct) + error("@register_component 必须作用于 struct 定义") + end + + struct_name = ex.args[2] + if Meta.isexpr(struct_name, :curly) + struct_name = struct_name.args[1] + end + + name_str = string(struct_name) |> lowercase + + quote + $(esc(ex)) + $(ComponentRegistry).register($(category), $(name_str), $(esc(struct_name))) + end +end + + +# ---------------------- 通用工厂 ---------------------- +module BaseFactory + +using ..ComponentRegistry + +""" + create_component(category::String, name::String, args...; kwargs...) + +通用工厂接口,与 Python BaseFactory.create_component 行为一致。 +""" +function create_component(category::String, name::String, args...; kwargs...) + return ComponentRegistry.create(category, name, args...; kwargs...) +end + +""" + get_available_components(category::String) + +列出某类别下所有可用组件。 +""" +function get_available_components(category::String) + all = ComponentRegistry.list_all() + return get(all, category, String[]) +end + +end # module BaseFactory + + +# 导出接口 +export ComponentRegistry, BaseFactory, @register_component \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03c/src/residual.jl b/example/1d-linear-convection/weno3/julia/03c/src/residual.jl new file mode 100644 index 000000000..11a885934 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03c/src/residual.jl @@ -0,0 +1,69 @@ +# julia/residual.jl +""" +残差计算器(与 residual.py 完全同构) +- 封装重建→通量→散度完整流程 +- 依赖 cfd 的多个字段 +""" + +include("mesh.jl") + +mutable struct ResidualCalculator + cfd::Any + config::Any + domain::Any + solution::Any + mesh::Mesh + reconstructor::Any + flux_calculator::Any + + function ResidualCalculator(cfd::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + mesh = domain.mesh + reconstructor = cfd.reconstructor + + # ✅ 内部创建 flux_calculator(对标 Python) + flux_calculator = FluxCalculatorFactory.create(cfd) + + new(cfd, config, domain, solution, mesh, reconstructor, flux_calculator) + end +end + + +""" +计算完整残差(对外唯一接口) +""" +function compute!(calc::ResidualCalculator) + _reconstruct(calc) + _compute_inviscid_flux(calc) + _compute_flux_divergence(calc) +end + +""" +私有方法:界面值重建 +""" +function _reconstruct(calc::ResidualCalculator) + reconstruct(calc.reconstructor, calc.solution.u, calc.cfd) +end + +""" +私有方法:计算无粘通量 +""" +function _compute_inviscid_flux(calc::ResidualCalculator) + compute!(calc.flux_calculator, + calc.solution.q_face_left, + calc.solution.q_face_right, + calc.solution.flux) +end + +""" +私有方法:计算通量散度(残差 = -dF/dx) +""" +function _compute_flux_divergence(calc::ResidualCalculator) + solution = calc.solution + mesh = calc.mesh + for i in 1:mesh.ncells + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / mesh.dx + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03c/src/solution.jl b/example/1d-linear-convection/weno3/julia/03c/src/solution.jl new file mode 100644 index 000000000..7044c0014 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03c/src/solution.jl @@ -0,0 +1,60 @@ +# julia/solution.jl +""" +Solution 结构体(与真实 solution.py 完全同构) +字段顺序、方法、逻辑 1:1 对齐 +""" + +include("mesh.jl") +include("domain.jl") +include("initial_condition.jl") + +mutable struct Solution + domain::Domain + q_face_left::Vector{Float64} + q_face_right::Vector{Float64} + flux::Vector{Float64} + res::Vector{Float64} + u::Vector{Float64} + un::Vector{Float64} + + function Solution(config::Any, domain::Domain) + mesh = domain.mesh + + q_face_left = zeros(Float64, mesh.nnodes) + q_face_right = zeros(Float64, mesh.nnodes) + flux = zeros(Float64, mesh.nnodes) + res = zeros(Float64, mesh.ncells) + u = zeros(Float64, domain.ntcells) + un = zeros(Float64, domain.ntcells) + + sol = new(domain, q_face_left, q_face_right, flux, res, u, un) + initialize_from_config(sol, config) + return sol + end +end + +""" +重置解数组为初始状态 +""" +function reset_solution(sol::Solution) + fill!(sol.u, 0.0) + fill!(sol.un, 0.0) +end + +""" +根据配置初始化场 +""" + +function initialize_from_config(sol::Solution, config::Any) + ic_type = getfield_safe(config, :ic_type, "step") + ic = InitialConditionFactory.create(ic_type, config) + apply(ic, sol) +end + +""" +更新旧场:un = u +""" +function update_old_field(sol::Solution) + sol.un .= sol.u # 等价于 Python 的 un[:] = u[:] +end + diff --git a/example/1d-linear-convection/weno3/julia/03c/src/solver.jl b/example/1d-linear-convection/weno3/julia/03c/src/solver.jl new file mode 100644 index 000000000..aeca9515d --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03c/src/solver.jl @@ -0,0 +1,129 @@ +# julia/solver.jl +""" +CFD 求解器主类(与 solver.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("initial_condition.jl") +include("boundary.jl") +include("flux/flux.jl") +include("residual.jl") +include("time_integration/time_integration.jl") +include("reconstructor/eno.jl") +include("reconstructor/weno3.jl") +include("reconstructor/factory.jl") + +# 导入工厂模块(必须在顶层!) +using .FluxCalculatorFactory +using .BoundaryConditionFactory +using .ReconstructorFactory + +# ---------------------- Cfd 求解器 ---------------------- +mutable struct Cfd + config::Any + domain::Domain + solution::Solution + reconstructor::Any + residual_calculator::ResidualCalculator + integrator::Any + boundary_condition::Any + result::Dict{String, Any} + + function Cfd(config::Any, mesh::Mesh) + domain = Domain(config, mesh) + solution = Solution(config, domain) + reconstructor = ReconstructorFactory.create(config, domain) + + # 1. 初始上下文(仅包含不依赖其他组件的字段) + full_cfd = ( + config = config, + domain = domain, + solution = solution, + reconstructor = reconstructor + # 注意:不预先占位 nothing! + ) + + # 2. 创建 boundary_condition(只依赖 config + domain) + boundary_condition = BoundaryConditionFactory.create(full_cfd) + full_cfd = merge(full_cfd, (boundary_condition = boundary_condition,)) + + # 3. 创建 residual_calculator(依赖上面所有字段) + residual_calculator = ResidualCalculator(full_cfd) + full_cfd = merge(full_cfd, (residual_calculator = residual_calculator,)) + + # 4. 创建 integrator(依赖 residual_calculator 等) + integrator = TimeIntegratorFactory.create(full_cfd) + full_cfd = merge(full_cfd, (integrator = integrator,)) + + # 5. 注入完整 self 到组件(确保它们能访问彼此) + residual_calculator.cfd = full_cfd + integrator.base.cfd = full_cfd + + result = Dict{String, Any}() + new(config, domain, solution, reconstructor, residual_calculator, integrator, boundary_condition, result) + end +end + +""" +通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界 +""" +function exact_solution(cfd::Cfd) + x = cfd.domain.mesh.xcc + T = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = @. (x - c * T + L) % L + + # ✅ 使用工厂创建初始条件(对标 Python) + ic = InitialConditionFactory.create(cfd.config.ic_type, cfd.config) + + return evaluate_at(ic, x_shifted) +end + +""" +主求解循环 +""" +function run!(cfd::Cfd) + # 应用初始边界条件并同步 old field + apply!(cfd.boundary_condition, cfd.solution.u) + update_old_field(cfd.solution) + + t = 0.0 + dt_old = cfd.config.dt + dt = dt_old + + while t < cfd.config.final_time + if t + dt > cfd.config.final_time + dt = cfd.config.final_time - t + end + #@show t, dt, maximum(cfd.solution.u), minimum(cfd.solution.u) + # 执行时间步 + step(cfd.integrator, dt) + t += dt + end + + # 恢复 dt + cfd.config.dt = dt_old + + # 整理结果 + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied-1] # Python: [ist:ied] + analytical = exact_solution(cfd) + + cfd.result = Dict( + "x" => cfd.domain.mesh.xcc, + "numerical" => u_numerical, + "analytical" => analytical, + "config" => Dict( + "scheme" => cfd.config.recon_scheme, + "order" => cfd.config.spatial_order, + "rk_order" => cfd.config.rk_order, + "final_time" => cfd.config.final_time + ) + ) + + return u_numerical +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03c/src/time_integration/base.jl b/example/1d-linear-convection/weno3/julia/03c/src/time_integration/base.jl new file mode 100644 index 000000000..a5b93bc19 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03c/src/time_integration/base.jl @@ -0,0 +1,33 @@ +# src/time_integration/base.jl + +""" +公共基类逻辑(对应 Python TimeIntegratorBase) +""" + +mutable struct TimeIntegratorBase + cfd::Any + config::Any + domain::Domain + solution::Solution + residual_calculator::Any +end + +function TimeIntegratorBase(cfd::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + residual_calculator = cfd.residual_calculator + TimeIntegratorBase(cfd, config, domain, solution, residual_calculator) +end + +function compute_residual(integrator::TimeIntegratorBase) + compute!(integrator.residual_calculator) +end + +function apply_boundary(integrator::TimeIntegratorBase) + apply!(integrator.cfd.boundary_condition, integrator.solution.u) +end + +function map_idx(integrator::TimeIntegratorBase, i::Int) + return i - integrator.domain.ist + 1 +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03c/src/time_integration/factory.jl b/example/1d-linear-convection/weno3/julia/03c/src/time_integration/factory.jl new file mode 100644 index 000000000..271fcde5c --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03c/src/time_integration/factory.jl @@ -0,0 +1,29 @@ +# src/time_integration/factory.jl + +module TimeIntegratorFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "时间积分器 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + rk_order = cfd.config.rk_order + name = "rk$rk_order" + if !haskey(_REGISTRY, name) + available = sort(collect(keys(_REGISTRY))) + error("未注册的时间积分器: '$name'。可用选项: $available") + end + return _REGISTRY[name](cfd) +end + +# ✅ 关键:用 Main. 引用在 Main 模块中定义的类型 +register("rk1", cfd -> Main.RK1Integrator(cfd)) +register("rk2", cfd -> Main.RK2Integrator(cfd)) +register("rk3", cfd -> Main.RK3Integrator(cfd)) + +end # module TimeIntegratorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03c/src/time_integration/rk1.jl b/example/1d-linear-convection/weno3/julia/03c/src/time_integration/rk1.jl new file mode 100644 index 000000000..580db3599 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03c/src/time_integration/rk1.jl @@ -0,0 +1,19 @@ +# src/time_integration/rk1.jl + +mutable struct RK1Integrator + base::TimeIntegratorBase +end + +function RK1Integrator(cfd::Any) + RK1Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK1Integrator, dt::Float64) + compute_residual(integrator.base) + for i in integrator.base.domain.ist:(integrator.base.domain.ied - 1) + j = map_idx(integrator.base, i) + integrator.base.solution.u[i] += dt * integrator.base.solution.res[j] + end + apply_boundary(integrator.base) + update_old_field(integrator.base.solution) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03c/src/time_integration/rk2.jl b/example/1d-linear-convection/weno3/julia/03c/src/time_integration/rk2.jl new file mode 100644 index 000000000..ca2b7ab6a --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03c/src/time_integration/rk2.jl @@ -0,0 +1,30 @@ +# src/time_integration/rk2.jl + +mutable struct RK2Integrator + base::TimeIntegratorBase +end + +function RK2Integrator(cfd::Any) + RK2Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK2Integrator, dt::Float64) + base = integrator.base + compute_residual(base) + u_pred = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u_pred[i] += dt * base.solution.res[j] + end + base.solution.u .= u_pred + apply_boundary(base) + compute_residual(base) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = 0.5 * base.solution.un[i] + + 0.5 * base.solution.u[i] + + 0.5 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03c/src/time_integration/rk3.jl b/example/1d-linear-convection/weno3/julia/03c/src/time_integration/rk3.jl new file mode 100644 index 000000000..a8e07736d --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03c/src/time_integration/rk3.jl @@ -0,0 +1,44 @@ +# src/time_integration/rk3.jl + +mutable struct RK3Integrator + base::TimeIntegratorBase +end + +function RK3Integrator(cfd::Any) + RK3Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK3Integrator, dt::Float64) + base = integrator.base + # Stage 1 + compute_residual(base) + u1 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u1[i] += dt * base.solution.res[j] + end + base.solution.u .= u1 + apply_boundary(base) + # Stage 2 + compute_residual(base) + u2 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u2[i] = 0.75 * base.solution.un[i] + + 0.25 * base.solution.u[i] + + 0.25 * dt * base.solution.res[j] + end + base.solution.u .= u2 + apply_boundary(base) + # Stage 3 + compute_residual(base) + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = c1 * base.solution.un[i] + + c2 * base.solution.u[i] + + c3 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03c/src/time_integration/time_integration.jl b/example/1d-linear-convection/weno3/julia/03c/src/time_integration/time_integration.jl new file mode 100644 index 000000000..4e60bfc2b --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03c/src/time_integration/time_integration.jl @@ -0,0 +1,7 @@ +# src/time_integration/time_integration.jl + +include("base.jl") +include("rk1.jl") +include("rk2.jl") +include("rk3.jl") +include("factory.jl") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03c/src/utils.jl b/example/1d-linear-convection/weno3/julia/03c/src/utils.jl new file mode 100644 index 000000000..3786cd1a5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03c/src/utils.jl @@ -0,0 +1,9 @@ +# src/utils.jl + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end diff --git a/example/1d-linear-convection/weno3/julia/03d/examples/run_eno_weno.jl b/example/1d-linear-convection/weno3/julia/03d/examples/run_eno_weno.jl new file mode 100644 index 000000000..da24e6cd7 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/examples/run_eno_weno.jl @@ -0,0 +1,50 @@ +# examples/run_eno_weno.jl +""" +1:1 复刻 run_eno_weno.py 的 Julia 版本 +""" + +include("../src/config.jl") +include("../src/mesh.jl") +include("../src/solver.jl") +include("../src/plotter.jl") + +function performEnoWenoAnalysis() + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO3 求解 + println("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + with_reconstruction(config_eno3, "eno", 3) # 显式指定 3 阶 + config_eno3.dt = 0.0025 # 覆盖默认值 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + run!(cfd_eno3) # 求解并生成 result 字典 + + # 3. 配置并运行 WENO3 求解 + println("Running WENO3 solver...") + config_weno3 = CfdConfig() + with_reconstruction(config_weno3, "weno", 3) # 显式指定 3 阶(WENO 默认 5 阶) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + run!(cfd_weno3) + + # 5. 绘制 ENO/WENO 对比图 + println("Plotting comparison results...") + plot_eno_weno_comparison( + cfd_eno3.result, + cfd_weno3.result; + save_path="eno_weno_comparison.png" + ) + + return cfd_eno3, cfd_weno3 +end + +# 主程序入口 +if abspath(PROGRAM_FILE) == @__FILE__ + performEnoWenoAnalysis() + println("Analysis completed!") +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03d/src/boundary.jl b/example/1d-linear-convection/weno3/julia/03d/src/boundary.jl new file mode 100644 index 000000000..82f58b71f --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/boundary.jl @@ -0,0 +1,121 @@ +# julia/boundary.jl +# 目标:与 Python boundary.py 行为完全一致(1:1 同构) +# 前提:ist/ied 在 Julia 中按 1-based 设置(ist = nghosts + 1) + +#using NPZ # 仅用于测试,实际模块可不依赖 + +# ---------------------- 抽象基类(Julia 风格:用函数接口) ---------------------- +# Julia 无 abstract class,用文档+约定 +# 所有子类型必须实现 apply!(bc, u) + +# ---------------------- PeriodicBoundary ---------------------- +mutable struct PeriodicBoundary + cfd::Any +end + +function apply!(self::PeriodicBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist # 1-based, e.g., 3 + ied = self.cfd.domain.ied # 1-based, e.g., 43 + + # 左 ghost: u[ist - 1 - ig] = u[ied - 1 - ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ied - 1 - ig] + end + # 右 ghost: u[ied + ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ied + ig] = u[ist + ig] + end +end + +# ---------------------- DirichletBoundary ---------------------- +mutable struct DirichletBoundary + cfd::Any +end + +function apply!(self::DirichletBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + left_value = getfield_safe(self.cfd.config, :left_boundary_value, 1.0) + right_value = getfield_safe(self.cfd.config, :right_boundary_value, 2.0) + + # 左边界 + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = left_value + end + # 右边界 + for ig in 0:(nghosts-1) + u[ied + ig] = right_value + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Dirichlet边界: 左值=$left_value, 右值=$right_value") + end +end + +# ---------------------- NeumannBoundary ---------------------- +mutable struct NeumannBoundary + cfd::Any +end + +function apply!(self::NeumannBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + # 左边界零梯度:u[ist - 1 - ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ist + ig] + end + # 右边界零梯度:u[ied + ig] = u[ied - 1 - ig] ← 注意是 ied - 1 - ig + for ig in 0:(nghosts-1) + u[ied + ig] = u[ied - 1 - ig] + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Neumann边界: 零梯度") + end +end + + +# ---------------------- BoundaryConditionFactory ---------------------- +module BoundaryConditionFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "边界条件 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + bc_type = cfd.config.boundary_type + if !(bc_type isa AbstractString) + error("boundary_type 必须为字符串,当前值: $bc_type") + end + + if !haskey(_REGISTRY, bc_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的边界条件: '$bc_type'。可用选项: $available") + end + + return _REGISTRY[bc_type](cfd) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("periodic", cfd -> Main.PeriodicBoundary(cfd)) +register("dirichlet", cfd -> Main.DirichletBoundary(cfd)) +register("neumann", cfd -> Main.NeumannBoundary(cfd)) + +end # module BoundaryConditionFactory + +export BoundaryConditionFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03d/src/config.jl b/example/1d-linear-convection/weno3/julia/03d/src/config.jl new file mode 100644 index 000000000..1507d6a09 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/config.jl @@ -0,0 +1,73 @@ +# julia/config.jl +""" +CfdConfig:与 Python config.py 完全同构 +""" +mutable struct CfdConfig + ic_type::String + recon_scheme::String + flux_type::String + rk_order::Int + wave_speed::Float64 + final_time::Float64 + dt::Float64 + boundary_type::String + left_boundary_value::Float64 + right_boundary_value::Float64 + spatial_order::Int + equation_type::String # ← 新增 + problem_type::String # ← 新增 + + function CfdConfig() + new( + "step", + "eno", + "rusanov", + 1, + 1.0, + 0.625, + 0.025, + "periodic", + 1.0, + 2.0, + 2, + "linear_advection", # equation_type + "linear_advection" # problem_type + ) + end +end + +""" +专用配置:重建方案(链式调用) +""" +function with_reconstruction(cfg::CfdConfig, scheme::String, order::Union{Int, Nothing}=nothing) + cfg.recon_scheme = lowercase(scheme) + + if order !== nothing + cfg.spatial_order = order + else + if startswith(cfg.recon_scheme, "weno") + cfg.spatial_order = 5 + elseif cfg.recon_scheme == "eno" + cfg.spatial_order = 3 + else + error("不支持的重建格式:$scheme(仅支持 eno/weno)") + end + end + + return cfg # 支持链式调用 +end + +""" +专用配置:边界条件(链式调用) +""" +function with_boundary(cfg::CfdConfig, bc_type::String; left_value=nothing, right_value=nothing) + cfg.boundary_type = bc_type + if left_value !== nothing + cfg.left_boundary_value = left_value + end + if right_value !== nothing + cfg.right_boundary_value = right_value + end + return cfg +end + diff --git a/example/1d-linear-convection/weno3/julia/03d/src/domain.jl b/example/1d-linear-convection/weno3/julia/03d/src/domain.jl new file mode 100644 index 000000000..eec261142 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/domain.jl @@ -0,0 +1,58 @@ +# julia/domain.jl +""" +Domain 结构体(与 domain.py 完全同构) +- 保存 config +- nghosts 计算逻辑 1:1 +- ist/ied 为 0-based(与 Python 一致) +""" + +include("mesh.jl") +include("utils.jl") + +mutable struct Domain + config::Any # 保存 config(与 Python 一致) + mesh::Mesh + nghosts::Int + ist::Int # 0-based + ied::Int # 0-based + ntcells::Int +end + +function Domain(config::Any, mesh::Mesh) + nghosts = _calc_nghosts(config) + ist = nghosts + 1 # ← 1-based 起始索引 + ied = ist + mesh.ncells # ← 1-based 结束索引(不包含) + ntcells = mesh.ncells + 2 * nghosts + Domain(config, mesh, nghosts, ist, ied, ntcells) +end + +function _calc_nghosts(config::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + order = getfield_safe(config, :spatial_order, 2) + + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + if scheme == "eno" + nghosts = order + elseif startswith(scheme, "weno") + nghosts = order ÷ 2 + 1 + else + error("未知重建格式 $(scheme),无法计算ghost层!") + end + + if nghosts <= 0 + error("计算得到的ghost层数量无效:$(nghosts)(阶数$(order),格式$(scheme))") + end + + return nghosts +end + +function is_physical_cell(domain::Domain, idx::Int) + return domain.ist <= idx < domain.ied +end + +function get_physical_indices(domain::Domain) + return domain.ist:(domain.ied - 1) +end diff --git a/example/1d-linear-convection/weno3/julia/03d/src/equations/base.jl b/example/1d-linear-convection/weno3/julia/03d/src/equations/base.jl new file mode 100644 index 000000000..1915d7e32 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/equations/base.jl @@ -0,0 +1,28 @@ +# src/equations/base.jl + +""" +抽象方程基类 +所有具体方程必须实现: +- eq_flux(eq, u) +- max_wave_speed(eq, u) +""" +abstract type Equation end + +""" +通量函数 F(u) +""" +function eq_flux(eq::Equation, u::Float64)::Float64 + error("Not implemented for $(typeof(eq))") +end + +""" +最大波速(用于 Rusanov 通量和 CFL) +""" +function max_wave_speed(eq::Equation, u::Float64)::Float64 + error("Not implemented for $(typeof(eq))") +end + +""" +方程变量数(标量方程返回 1) +""" +num_equations(eq::Equation) = 1 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03d/src/equations/equations.jl b/example/1d-linear-convection/weno3/julia/03d/src/equations/equations.jl new file mode 100644 index 000000000..de1195d45 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/equations/equations.jl @@ -0,0 +1,4 @@ +# src/equations/equations.jl + +include("base.jl") +include("linear_advection.jl") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03d/src/equations/linear_advection.jl b/example/1d-linear-convection/weno3/julia/03d/src/equations/linear_advection.jl new file mode 100644 index 000000000..3086c1ce0 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/equations/linear_advection.jl @@ -0,0 +1,16 @@ +# src/equations/linear_advection.jl + +""" +线性对流方程: ∂u/∂t + c ∂u/∂x = 0 +""" +mutable struct LinearAdvectionEquation <: Equation + wave_speed::Float64 + function LinearAdvectionEquation(config::Any) + c = getfield_safe(config, :wave_speed, 1.0) + new(c) + end +end + +eq_flux(eq::LinearAdvectionEquation, u::Float64) = eq.wave_speed * u + +max_wave_speed(eq::LinearAdvectionEquation, u::Float64) = abs(eq.wave_speed) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03d/src/flux/base.jl b/example/1d-linear-convection/weno3/julia/03d/src/flux/base.jl new file mode 100644 index 000000000..58dd6cc90 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/flux/base.jl @@ -0,0 +1,7 @@ +# src/flux/base.jl +""" +抽象通量计算器接口 +Julia 无 ABC,用文档约定: +- 所有子类型必须实现 `compute!(calc, qL, qR, flux)` +""" +abstract type InviscidFluxCalculator end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03d/src/flux/engquist_osher.jl b/example/1d-linear-convection/weno3/julia/03d/src/flux/engquist_osher.jl new file mode 100644 index 000000000..c0de183d5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/flux/engquist_osher.jl @@ -0,0 +1,25 @@ +# src/flux/engquist_osher.jl +mutable struct EngquistOsherFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function EngquistOsherFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::EngquistOsherFluxCalculator, qL::Vector{Float64}, qR::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + c = calc.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = qL[i] + u_R = qR[i] + flux[i] = cp * u_L + cm * u_R + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03d/src/flux/factory.jl b/example/1d-linear-convection/weno3/julia/03d/src/flux/factory.jl new file mode 100644 index 000000000..6ae4917db --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/flux/factory.jl @@ -0,0 +1,26 @@ +# src/flux/factory.jl + +module FluxCalculatorFactory + +# 全局注册表 +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "通量计算器 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + flux_type = cfd.config.flux_type + if !haskey(_REGISTRY, flux_type) + error("未注册的通量计算器: '$flux_type'。可用: $(keys(_REGISTRY))") + end + return _REGISTRY[flux_type](cfd) +end + +register("rusanov", cfd -> Main.RusanovFluxCalculator(cfd)) +register("engquist-osher", cfd -> Main.EngquistOsherFluxCalculator(cfd)) + +end # module FluxCalculatorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03d/src/flux/flux.jl b/example/1d-linear-convection/weno3/julia/03d/src/flux/flux.jl new file mode 100644 index 000000000..533f034c8 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/flux/flux.jl @@ -0,0 +1,10 @@ +# src/flux/flux.jl + +# 加载组件(顺序很重要!) +include("base.jl") +include("rusanov.jl") +include("engquist_osher.jl") +include("factory.jl") + +# 导出(如果你未来用模块) +# export InviscidFluxCalculator, RusanovFluxCalculator, EngquistOsherFluxCalculator, FluxCalculatorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03d/src/flux/rusanov.jl b/example/1d-linear-convection/weno3/julia/03d/src/flux/rusanov.jl new file mode 100644 index 000000000..70c20fc3c --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/flux/rusanov.jl @@ -0,0 +1,28 @@ +# src/flux/rusanov.jl + +mutable struct RusanovFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function RusanovFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::RusanovFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + for i in 1:calc.mesh.nnodes + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = calc.wave_speed + c_R = calc.wave_speed + F_L = c_L * u_L + F_R = c_R * u_R + Smax = max(abs(c_L), abs(c_R)) + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03d/src/initial_condition.jl b/example/1d-linear-convection/weno3/julia/03d/src/initial_condition.jl new file mode 100644 index 000000000..1b9993aa8 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/initial_condition.jl @@ -0,0 +1,112 @@ +# julia/initial_condition.jl +""" +初始条件模块(Julia 版) +目标:与 Python initial_condition.py 行为完全一致 +""" + +# ---------------------- 抽象接口(Julia 无继承,用函数约定) ---------------------- +# 所有初始条件必须实现: +# evaluate_at(ic, x::AbstractVector{Float64}) -> Vector{Float64} +# apply(ic, solution) + +# ---------------------- StepFunctionIC ---------------------- +struct StepFunctionIC + config::Any +end + +function evaluate_at(ic::StepFunctionIC, x::AbstractVector{Float64}) + u0 = ones(Float64, length(x)) + for i in eachindex(x) + if 0.5 <= x[i] <= 1.0 + u0[i] = 2.0 + end + end + return u0 +end + +function apply(ic::StepFunctionIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- SineWaveIC ---------------------- +struct SineWaveIC + config::Any +end + +function evaluate_at(ic::SineWaveIC, x::AbstractVector{Float64}) + L = getfield_safe(ic.config, :domain_length, 2.0) + return sin.(2π * x / L) +end + +function apply(ic::SineWaveIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- GaussianPulseIC ---------------------- +struct GaussianPulseIC + config::Any +end + +function evaluate_at(ic::GaussianPulseIC, x::AbstractVector{Float64}) + center = getfield_safe(ic.config, :pulse_center, 0.5) + width = getfield_safe(ic.config, :pulse_width, 0.1) + return exp.(-((x .- center) ./ width).^2) +end + +function apply(ic::GaussianPulseIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- 公共辅助函数 ---------------------- +""" +将初始场 values 应用到 solution.u 的物理区域(含 ghost 的数组) +""" +function _apply_to_interior(solution, values) + domain = solution.domain + # Python: for i in range(domain.ist, domain.ied) + # Julia: for i in domain.ist:domain.ied-1 (因为 ied 是结束索引,不包含) + for i in domain.ist:(domain.ied - 1) + j = i - domain.ist + 1 # values 是 1-based,i - ist 是 0-based → +1 + solution.u[i] = values[j] + end +end + +# ---------------------- InitialConditionFactory ---------------------- +module InitialConditionFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "初始条件 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(ic_type::String, config::Any) + if !(ic_type isa AbstractString) + error("ic_type 必须为字符串,当前值: $ic_type") + end + + if !haskey(_REGISTRY, ic_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的初始条件: '$ic_type'。可用选项: $available") + end + + return _REGISTRY[ic_type](config) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("step", config -> Main.StepFunctionIC(config)) +register("sin", config -> Main.SineWaveIC(config)) +register("gaussian", config -> Main.GaussianPulseIC(config)) + +end # module InitialConditionFactory + +export InitialConditionFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03d/src/mesh.jl b/example/1d-linear-convection/weno3/julia/03d/src/mesh.jl new file mode 100644 index 000000000..1b0dd4bf9 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/mesh.jl @@ -0,0 +1,45 @@ +# julia/mesh.jl +""" +Mesh 结构体(与提供的 mesh.py 完全同构) +- 字段名、初始化顺序、循环逻辑完全一致 +- 不做任何泛化或优化 +""" + +mutable struct Mesh + xmin::Float64 + xmax::Float64 + ncells::Int + nnodes::Int + nx::Int + x::Vector{Float64} + xcc::Vector{Float64} + L::Float64 # 在 init_mesh 中赋值 + dx::Float64 # 在 init_mesh 中赋值 + + function Mesh() + # 与 Python __init__ 完全一致 + xmin = 0.0 + xmax = 2.0 + ncells = 40 + nnodes = ncells + 1 + nx = ncells + x = zeros(Float64, nnodes) + xcc = zeros(Float64, ncells) + + # 调用 init_mesh(模拟 Python) + L = xmax - xmin + dx = L / ncells + + # Generate node coordinates: for i in range(self.nnodes) + for i in 1:nnodes + x[i] = xmin + (i - 1) * dx # Python i → Julia i-1 + end + + # Generate cell center coordinates + for i in 1:ncells + xcc[i] = 0.5 * (x[i] + x[i+1]) + end + + new(xmin, xmax, ncells, nnodes, nx, x, xcc, L, dx) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03d/src/plotter.jl b/example/1d-linear-convection/weno3/julia/03d/src/plotter.jl new file mode 100644 index 000000000..a77d8d688 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/plotter.jl @@ -0,0 +1,147 @@ +# julia/plotter.jl +""" +CFDPlotter 的 Julia 实现(通过 PythonCall.jl 调用 Matplotlib) +确保与 Python plotter.py 行为完全一致 +""" + +using PythonCall + +# 初始化 Python 环境(加载 matplotlib, inflect) +const plt = pyimport("matplotlib.pyplot") +const inflect = pyimport("inflect") + +mutable struct CFDPlotter + default_styles::Dict{String, Any} + p::Py +end + +function CFDPlotter() + default_styles = Dict{String, Any}( + "numerical" => Dict( + :color => "blue", + :linestyle => "-", + :marker => "o", + :markerfacecolor => "none" + ), + "analytical" => Dict( + :color => "red", + :linestyle => "--", + :marker => "", + :linewidth => 1.5 + ), + "comparison" => [ + Dict(:color => "black", :linestyle => "-", :marker => "o", :markerfacecolor => "none"), + Dict(:color => "blue", :linestyle => "--", :marker => "s", :markerfacecolor => "none"), + Dict(:color => "green", :linestyle => ":", :marker => "^", :markerfacecolor => "none") + ] + ) + p = inflect.engine() + CFDPlotter(default_styles, p) +end + +""" +轻量即时绘图(快速验证结果) +""" +function plot_quick(plotter::CFDPlotter, cfd_result::Dict; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + rk_order = cfd_result["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = cfd_result["config"]["final_time"] + order = cfd_result["config"]["order"] + scheme = uppercase(cfd_result["config"]["scheme"]) + actual_title = "1D Convection (t=$(final_time))\n$(order)th-order $(scheme) + $(rk_str)-order RK" + end + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"]; + label="Numerical ($(uppercase(cfd_result["config"]["scheme"])))", + plotter.default_styles["numerical"]..., + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + # 通用样式 + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +""" +多格式/多精度对比绘图 +""" +function plot_comparison(plotter::CFDPlotter, result_list::Vector{Dict{String, Any}}; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + schemes = [uppercase(r["config"]["scheme"]) * string(r["config"]["order"]) for r in result_list] + rk_order = result_list[1]["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = result_list[1]["config"]["final_time"] + actual_title = "1D Convection Comparison (t=$(final_time))\n$(join(schemes, ", ")) + $(rk_str)-order RK" + end + + # 绘制多个数值解 + for (i, res) in enumerate(result_list) + style = plotter.default_styles["comparison"][mod1(i, length(plotter.default_styles["comparison"]))] + label = "Numerical ($(uppercase(res["config"]["scheme"]))$(res["config"]["order"]))" + plt.plot( + res["x"], res["numerical"]; + label=label, + style..., + markersize=5, linewidth=0.5 + ) + end + + # 绘制解析解 + plt.plot( + result_list[1]["x"], result_list[1]["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +function _set_common_style(plotter::CFDPlotter, title::String) + plt.title(title, fontsize=12) + plt.xlabel("x", fontsize=10) + plt.ylabel("u", fontsize=10) + plt.legend(fontsize=9) + plt.grid(true, color="gray", linestyle="--", linewidth=0.5, alpha=0.7) + plt.tight_layout() +end + +""" +快捷函数:ENO/WENO对比绘图 +""" +function plot_eno_weno_comparison(eno_result::Dict, weno_result::Dict; save_path=nothing) + plotter = CFDPlotter() + plot_comparison(plotter, [eno_result, weno_result]; save_path=save_path) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03d/src/problems/base.jl b/example/1d-linear-convection/weno3/julia/03d/src/problems/base.jl new file mode 100644 index 000000000..e29d0fbbe --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/problems/base.jl @@ -0,0 +1,22 @@ +# src/problems/base.jl + +""" +抽象问题基类 +所有具体问题必须实现: +- create_initial_condition(prob, config) +- create_boundary_condition(prob, cfd) +- exact_solution(prob, x, t) +""" +abstract type Problem end + +function create_initial_condition(prob::Problem, config::Any) + error("Not implemented for $(typeof(prob))") +end + +function create_boundary_condition(prob::Problem, cfd::Any) + error("Not implemented for $(typeof(prob))") +end + +function exact_solution(prob::Problem, x::Vector{Float64}, t::Float64)::Vector{Float64} + error("Not implemented for $(typeof(prob))") +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03d/src/problems/linear_advection.jl b/example/1d-linear-convection/weno3/julia/03d/src/problems/linear_advection.jl new file mode 100644 index 000000000..8b37d9f85 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/problems/linear_advection.jl @@ -0,0 +1,43 @@ +# src/problems/linear_advection.jl + +""" +线性对流问题:定义初始条件、边界条件、解析解 +""" +mutable struct LinearAdvectionProblem <: Problem + ic_type::String + boundary_type::String + left_value::Float64 + right_value::Float64 + domain_length::Float64 + wave_speed::Float64 # 从 config 获取,未来可从 equation 获取 + + function LinearAdvectionProblem(config::Any) + new( + getfield_safe(config, :ic_type, "step"), + getfield_safe(config, :boundary_type, "periodic"), + getfield_safe(config, :left_boundary_value, 1.0), + getfield_safe(config, :right_boundary_value, 2.0), + getfield_safe(config, :domain_length, 2.0), + getfield_safe(config, :wave_speed, 1.0) + ) + end +end + +function create_initial_condition(prob::LinearAdvectionProblem, config::Any) + return Main.InitialConditionFactory.create(prob.ic_type, config) +end + +function create_boundary_condition(prob::LinearAdvectionProblem, cfd::Any) + # 复用原有 BC 工厂(cfd 需包含 config + domain) + return Main.BoundaryConditionFactory.create(cfd) +end + +function exact_solution(prob::LinearAdvectionProblem, x::Vector{Float64}, t::Float64)::Vector{Float64} + L = prob.domain_length + c = prob.wave_speed + # 周期性平移 + x_shifted = @. (x - c * t + L) % L + # 重用 IC 的 evaluate_at + ic = Main.InitialConditionFactory.create(prob.ic_type, (ic_type=prob.ic_type,)) + return Main.evaluate_at(ic, x_shifted) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03d/src/problems/problems.jl b/example/1d-linear-convection/weno3/julia/03d/src/problems/problems.jl new file mode 100644 index 000000000..983113978 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/problems/problems.jl @@ -0,0 +1,4 @@ +# src/problems/problems.jl + +include("base.jl") +include("linear_advection.jl") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03d/src/reconstructor/eno.jl b/example/1d-linear-convection/weno3/julia/03d/src/reconstructor/eno.jl new file mode 100644 index 000000000..e78a636f3 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/reconstructor/eno.jl @@ -0,0 +1,107 @@ +# julia/reconstructor/eno.jl +""" +ENO 重构器(与 reconstructor/eno.py 完全同构) +""" + +# ---------------------- ENO 系数初始化 ---------------------- +function _init_eno_coef!(spatial_order::Int, coef::Matrix{Float64}) + if spatial_order == 1 + coef[1, 1] = 1.0 + coef[2, 1] = 1.0 + elseif spatial_order == 2 + coef[1, 1:2] = [3.0/2.0, -1.0/2.0] + coef[2, 1:2] = [1.0/2.0, 1.0/2.0] + coef[3, 1:2] = [-1.0/2.0, 3.0/2.0] + elseif spatial_order == 3 + coef[1, 1:3] = [11.0/6.0, -7.0/6.0, 1.0/3.0] + coef[2, 1:3] = [1.0/3.0, 5.0/6.0, -1.0/6.0] + coef[3, 1:3] = [-1.0/6.0, 5.0/6.0, 1.0/3.0] + coef[4, 1:3] = [1.0/3.0, -7.0/6.0, 11.0/6.0] + elseif spatial_order == 4 + coef[1, 1:4] = [25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0] + coef[2, 1:4] = [1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0] + coef[3, 1:4] = [-1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0] + coef[4, 1:4] = [1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0] + coef[5, 1:4] = [-1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0] + elseif spatial_order == 5 + coef[1, 1:5] = [137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0] + coef[2, 1:5] = [1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0] + coef[3, 1:5] = [-1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0] + coef[4, 1:5] = [1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0] + coef[5, 1:5] = [-1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0] + coef[6, 1:5] = [1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0] + elseif spatial_order == 6 + coef[1, 1:6] = [49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0] + coef[2, 1:6] = [1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0] + coef[3, 1:6] = [-1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0] + coef[4, 1:6] = [1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0] + coef[5, 1:6] = [-1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0] + coef[6, 1:6] = [1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0] + coef[7, 1:6] = [-1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0] + elseif spatial_order == 7 + coef[1, 1:7] = [363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0] + coef[2, 1:7] = [1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0] + coef[3, 1:7] = [-1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0] + coef[4, 1:7] = [1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0] + coef[5, 1:7] = [-1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0] + coef[6, 1:7] = [1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0] + coef[7, 1:7] = [-1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0] + coef[8, 1:7] = [1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0] + else + error("ENO 系数未实现 order=$spatial_order") + end +end + +# ---------------------- ENO 重构器 ---------------------- +mutable struct EnoReconstructor + spatial_order::Int + ntcells::Int + lmc::Vector{Int} + coef::Matrix{Float64} + dd::Matrix{Float64} + + function EnoReconstructor(spatial_order::Int, ntcells::Int) + lmc = zeros(Int, ntcells) + coef = zeros(Float64, spatial_order + 1, spatial_order) + dd = zeros(Float64, spatial_order, ntcells) + _init_eno_coef!(spatial_order, coef) + new(spatial_order, ntcells, lmc, coef, dd) + end +end + +function reconstruct(rec::EnoReconstructor, q::Vector{Float64}, cfd::Any) + # 1. 差商计算 (dd[1,:] = q) + @views rec.dd[1, :] .= q + for m in 2:rec.spatial_order + for j in 1:(rec.ntcells - m + 1) + rec.dd[m, j] = rec.dd[m-1, j+1] - rec.dd[m-1, j] + end + end + + # 2. 选择 smoothest stencil + domain = cfd.domain + for i in (domain.ist - 1):(domain.ied) # Python: range(ist-1, ied+1) → ied+1-1 = ied + rec.lmc[i] = i + for m in 2:rec.spatial_order + if abs(rec.dd[m, rec.lmc[i] - 1]) < abs(rec.dd[m, rec.lmc[i]]) + rec.lmc[i] -= 1 + end + end + end + + # 3. 重构界面值 + solution = cfd.solution + for i in domain.ist:(domain.ied) # Python: range(ist, ied+1) → ied+1-1 = ied + j = i - domain.ist + 1 # Julia 1-based + k1 = rec.lmc[i - 1] + k2 = rec.lmc[i] + r1 = (i - 1) - k1 + 1 + r2 = i - k2 + 1 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in 1:rec.spatial_order + solution.q_face_left[j] += q[k1 + m - 1] * rec.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m - 1] * rec.coef[r2, m] + end + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03d/src/reconstructor/factory.jl b/example/1d-linear-convection/weno3/julia/03d/src/reconstructor/factory.jl new file mode 100644 index 000000000..70b0cf9ff --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/reconstructor/factory.jl @@ -0,0 +1,38 @@ +# src/reconstructor/factory.jl + +""" +ReconstructorFactory +对标 Python: ReconstructorFactory.create(config, domain) +""" +module ReconstructorFactory + +include("../utils.jl") + +# ✅ 不要用 using ..EnoReconstructor(它们不是模块) +# 直接通过 Main. 引用顶层定义的类型 + +function create(config::Any, domain::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + # 处理 WENO 默认命名(如 Python) + if scheme == "weno" + order = getfield_safe(config, :spatial_order, 5) + scheme = "weno$(order)" + end + + if scheme == "eno" + order = getfield_safe(config, :spatial_order, 3) + return Main.EnoReconstructor(order, domain.ntcells) + elseif scheme == "weno3" + return Main.Weno3Reconstructor() + else + error("不支持的重建格式: $scheme(仅支持 eno/weno3)") + end +end + +end # module ReconstructorFactory + +export ReconstructorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03d/src/reconstructor/weno3.jl b/example/1d-linear-convection/weno3/julia/03d/src/reconstructor/weno3.jl new file mode 100644 index 000000000..2b6fe1abb --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/reconstructor/weno3.jl @@ -0,0 +1,65 @@ +# julia/reconstructor/weno3.jl +""" +WENO3 重构器(与 reconstructor/weno3.py 完全同构) +""" + +mutable struct Weno3Reconstructor + # 无字段,与 Python 一致 +end + +function reconstruct(rec::Weno3Reconstructor, q::Vector{Float64}, cfd::Any) + domain = cfd.domain + solution = cfd.solution + _reconstruct_left_interfaces(domain, q, solution.q_face_left) + _reconstruct_right_interfaces(domain, q, solution.q_face_right) +end + +function _reconstruct_left_interfaces(domain, u, qL) + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in (domain.ist - 1):(domain.ied - 1) + j = i - (domain.ist - 1) + 1 # ← Julia 1-based: j = i - (ist-1) 对应 Python j = i - (ist-1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = _reconstruct_from_left_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_right_interfaces(domain, u, qR) + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in domain.ist:domain.ied + j = i - domain.ist + 1 # ← Julia 1-based: j = i - ist 对应 Python j = i - ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = _reconstruct_from_right_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_from_left_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -0.5*v1 + 1.5*v2 # r=1 stencil + q1 = 0.5*v2 + 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end + +function _reconstruct_from_right_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 0.5*v1 + 0.5*v2 # r=1 stencil + q1 = 1.5*v2 - 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03d/src/registry.jl b/example/1d-linear-convection/weno3/julia/03d/src/registry.jl new file mode 100644 index 000000000..6acd9aaaf --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/registry.jl @@ -0,0 +1,164 @@ +""" +统一注册系统 + 通用工厂 +- ComponentRegistry: 组件注册表 +- @register_component: 装饰器宏 +- BaseFactory: 通用工厂接口 +""" + +module ComponentRegistry + +# 注册表:Dict{category => Dict{name => constructor}} +const _REGISTRIES = Dict{String, Dict{String, Function}}() +const _VERBOSE = Ref(true) + +# ---------------------- 注册核心逻辑 ---------------------- +""" + register(category::String, name::String, ctor::Function) + +注册一个组件构造函数到指定类别。 +如果已存在同名组件且不同,则发出警告。 +""" +function register(category::String, name::String, ctor::Function) + if !haskey(_REGISTRIES, category) + _REGISTRIES[category] = Dict{String, Function}() + end + + registry = _REGISTRIES[category] + if haskey(registry, name) + if registry[name] !== ctor && _VERBOSE[] + @warn "覆盖注册: $category.$name" + end + end + + registry[name] = ctor + if _VERBOSE[] + println("✅ 已注册: $category.$name -> $(nameof(ctor))") + end +end + +""" + create(category::String, name::String, args...; kwargs...) + +从注册表创建组件实例。 +""" +function create(category::String, name::String, args...; kwargs...) + if !haskey(_REGISTRIES, category) + error("❌ 未知类别: $category (可用: $(collect(keys(_REGISTRIES))))") + end + + registry = _REGISTRIES[category] + lname = lowercase(name) + if !haskey(registry, lname) + available = sort(collect(keys(registry))) + error("❌ 未找到: $category.$name (可用: $available)") + end + + return registry[lname](args...; kwargs...) +end + +""" + list_all() + +返回所有已注册组件(按类别)。 +""" +function list_all() + return Dict(cat => sort(collect(keys(reg))) for (cat, reg) in _REGISTRIES) +end + +""" + set_verbose(flag::Bool) + +开启/关闭注册提示。 +""" +function set_verbose(flag::Bool) + _VERBOSE[] = flag +end + +end # module ComponentRegistry + + +# ---------------------- 装饰器宏:@register_component ---------------------- +""" +@register_component(category, [name]) + +用法: + @register_component("boundary", "periodic") + struct PeriodicBoundary ... + +若省略 name,则使用类型名的小写形式。 +""" +macro register_component(category::String, name_expr) + error("@register_component 需在类型定义前使用,且必须在模块顶层") +end + +macro register_component(category::String) + error("@register_component(category, name) 需指定 name 或在类型后使用") +end + +# 重载:@register_component("category", "name") struct X ... end +macro register_component(category::String, name::String, ex) + if !Meta.isexpr(ex, :struct) + error("@register_component 必须作用于 struct 定义") + end + + struct_name = ex.args[2] + if Meta.isexpr(struct_name, :curly) + struct_name = struct_name.args[1] + end + + # 插入注册调用(在模块顶层) + quote + $(esc(ex)) + $(ComponentRegistry).register($(category), $(name), $(esc(struct_name))) + end +end + +# 重载:@register_component("category") struct X ... end → name = lowercase(nameof(X)) +macro register_component(category::String, ex) + if !Meta.isexpr(ex, :struct) + error("@register_component 必须作用于 struct 定义") + end + + struct_name = ex.args[2] + if Meta.isexpr(struct_name, :curly) + struct_name = struct_name.args[1] + end + + name_str = string(struct_name) |> lowercase + + quote + $(esc(ex)) + $(ComponentRegistry).register($(category), $(name_str), $(esc(struct_name))) + end +end + + +# ---------------------- 通用工厂 ---------------------- +module BaseFactory + +using ..ComponentRegistry + +""" + create_component(category::String, name::String, args...; kwargs...) + +通用工厂接口,与 Python BaseFactory.create_component 行为一致。 +""" +function create_component(category::String, name::String, args...; kwargs...) + return ComponentRegistry.create(category, name, args...; kwargs...) +end + +""" + get_available_components(category::String) + +列出某类别下所有可用组件。 +""" +function get_available_components(category::String) + all = ComponentRegistry.list_all() + return get(all, category, String[]) +end + +end # module BaseFactory + + +# 导出接口 +export ComponentRegistry, BaseFactory, @register_component \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03d/src/residual.jl b/example/1d-linear-convection/weno3/julia/03d/src/residual.jl new file mode 100644 index 000000000..11a885934 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/residual.jl @@ -0,0 +1,69 @@ +# julia/residual.jl +""" +残差计算器(与 residual.py 完全同构) +- 封装重建→通量→散度完整流程 +- 依赖 cfd 的多个字段 +""" + +include("mesh.jl") + +mutable struct ResidualCalculator + cfd::Any + config::Any + domain::Any + solution::Any + mesh::Mesh + reconstructor::Any + flux_calculator::Any + + function ResidualCalculator(cfd::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + mesh = domain.mesh + reconstructor = cfd.reconstructor + + # ✅ 内部创建 flux_calculator(对标 Python) + flux_calculator = FluxCalculatorFactory.create(cfd) + + new(cfd, config, domain, solution, mesh, reconstructor, flux_calculator) + end +end + + +""" +计算完整残差(对外唯一接口) +""" +function compute!(calc::ResidualCalculator) + _reconstruct(calc) + _compute_inviscid_flux(calc) + _compute_flux_divergence(calc) +end + +""" +私有方法:界面值重建 +""" +function _reconstruct(calc::ResidualCalculator) + reconstruct(calc.reconstructor, calc.solution.u, calc.cfd) +end + +""" +私有方法:计算无粘通量 +""" +function _compute_inviscid_flux(calc::ResidualCalculator) + compute!(calc.flux_calculator, + calc.solution.q_face_left, + calc.solution.q_face_right, + calc.solution.flux) +end + +""" +私有方法:计算通量散度(残差 = -dF/dx) +""" +function _compute_flux_divergence(calc::ResidualCalculator) + solution = calc.solution + mesh = calc.mesh + for i in 1:mesh.ncells + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / mesh.dx + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03d/src/solution.jl b/example/1d-linear-convection/weno3/julia/03d/src/solution.jl new file mode 100644 index 000000000..7044c0014 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/solution.jl @@ -0,0 +1,60 @@ +# julia/solution.jl +""" +Solution 结构体(与真实 solution.py 完全同构) +字段顺序、方法、逻辑 1:1 对齐 +""" + +include("mesh.jl") +include("domain.jl") +include("initial_condition.jl") + +mutable struct Solution + domain::Domain + q_face_left::Vector{Float64} + q_face_right::Vector{Float64} + flux::Vector{Float64} + res::Vector{Float64} + u::Vector{Float64} + un::Vector{Float64} + + function Solution(config::Any, domain::Domain) + mesh = domain.mesh + + q_face_left = zeros(Float64, mesh.nnodes) + q_face_right = zeros(Float64, mesh.nnodes) + flux = zeros(Float64, mesh.nnodes) + res = zeros(Float64, mesh.ncells) + u = zeros(Float64, domain.ntcells) + un = zeros(Float64, domain.ntcells) + + sol = new(domain, q_face_left, q_face_right, flux, res, u, un) + initialize_from_config(sol, config) + return sol + end +end + +""" +重置解数组为初始状态 +""" +function reset_solution(sol::Solution) + fill!(sol.u, 0.0) + fill!(sol.un, 0.0) +end + +""" +根据配置初始化场 +""" + +function initialize_from_config(sol::Solution, config::Any) + ic_type = getfield_safe(config, :ic_type, "step") + ic = InitialConditionFactory.create(ic_type, config) + apply(ic, sol) +end + +""" +更新旧场:un = u +""" +function update_old_field(sol::Solution) + sol.un .= sol.u # 等价于 Python 的 un[:] = u[:] +end + diff --git a/example/1d-linear-convection/weno3/julia/03d/src/solver.jl b/example/1d-linear-convection/weno3/julia/03d/src/solver.jl new file mode 100644 index 000000000..b2e975aac --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/solver.jl @@ -0,0 +1,162 @@ +# julia/solver.jl +""" +CFD 求解器主类(与 solver.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("initial_condition.jl") +include("boundary.jl") +include("flux/flux.jl") +include("residual.jl") +include("time_integration/time_integration.jl") +include("reconstructor/eno.jl") +include("reconstructor/weno3.jl") +include("reconstructor/factory.jl") + +include("equations/equations.jl") +include("problems/problems.jl") + +# 导入工厂模块(必须在顶层!) +using .FluxCalculatorFactory +using .BoundaryConditionFactory +using .ReconstructorFactory + +# ---------------------- Cfd 求解器 ---------------------- +mutable struct Cfd + config::Any + domain::Domain + solution::Solution + reconstructor::Any + residual_calculator::ResidualCalculator + integrator::Any + boundary_condition::Any + result::Dict{String, Any} + + function Cfd(config::Any, mesh::Mesh) + domain = Domain(config, mesh) + solution = Solution(config, domain) + reconstructor = ReconstructorFactory.create(config, domain) + + # 1. 创建 Equation 和 Problem + equation = create_equation(config) + problem = create_problem(config) + + # 2. 初始上下文(仅包含不依赖其他组件的字段) + full_cfd = ( + config = config, + domain = domain, + solution = solution, + reconstructor = reconstructor, + equation = equation, # ← 新增 + problem = problem # ← 新增 + ) + + # 3. 创建边界条件(通过 problem) + boundary_condition = create_boundary_condition(problem, full_cfd) + full_cfd = merge(full_cfd, (boundary_condition = boundary_condition,)) + + + # 4. 创建 residual_calculator(现在可访问 equation) + residual_calculator = ResidualCalculator(full_cfd) + full_cfd = merge(full_cfd, (residual_calculator = residual_calculator,)) + + # 5. 创建 integrator + integrator = TimeIntegratorFactory.create(full_cfd) + full_cfd = merge(full_cfd, (integrator = integrator,)) + + # 6. 注入完整 self + residual_calculator.cfd = full_cfd + integrator.base.cfd = full_cfd + + # 7. 初始化解(通过 problem) + ic = create_initial_condition(problem, config) + Main.apply(ic, solution) # 注意:apply 在 Main + + result = Dict{String, Any}() + new(config, domain, solution, reconstructor, residual_calculator, integrator, boundary_condition, result) + + end +end + +""" +通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界 +""" +function exact_solution(cfd::Cfd) + x = cfd.domain.mesh.xcc + T = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = @. (x - c * T + L) % L + + # ✅ 使用工厂创建初始条件(对标 Python) + ic = InitialConditionFactory.create(cfd.config.ic_type, cfd.config) + + return evaluate_at(ic, x_shifted) +end + +""" +主求解循环 +""" +function run!(cfd::Cfd) + # 应用初始边界条件并同步 old field + apply!(cfd.boundary_condition, cfd.solution.u) + update_old_field(cfd.solution) + + t = 0.0 + dt_old = cfd.config.dt + dt = dt_old + + while t < cfd.config.final_time + if t + dt > cfd.config.final_time + dt = cfd.config.final_time - t + end + #@show t, dt, maximum(cfd.solution.u), minimum(cfd.solution.u) + # 执行时间步 + step(cfd.integrator, dt) + t += dt + end + + # 恢复 dt + cfd.config.dt = dt_old + + # 整理结果 + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied-1] # Python: [ist:ied] + analytical = exact_solution(cfd) + + cfd.result = Dict( + "x" => cfd.domain.mesh.xcc, + "numerical" => u_numerical, + "analytical" => analytical, + "config" => Dict( + "scheme" => cfd.config.recon_scheme, + "order" => cfd.config.spatial_order, + "rk_order" => cfd.config.rk_order, + "final_time" => cfd.config.final_time + ) + ) + + return u_numerical +end + +# --- Equation / Problem 创建函数 --- +function create_equation(config::Any) + eq_type = config.equation_type + if eq_type == "linear_advection" + return LinearAdvectionEquation(config) + else + error("Unknown equation type: $eq_type") + end +end + +function create_problem(config::Any) + prob_type = config.problem_type + if prob_type == "linear_advection" + return LinearAdvectionProblem(config) + else + error("Unknown problem type: $prob_type") + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03d/src/time_integration/base.jl b/example/1d-linear-convection/weno3/julia/03d/src/time_integration/base.jl new file mode 100644 index 000000000..a5b93bc19 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/time_integration/base.jl @@ -0,0 +1,33 @@ +# src/time_integration/base.jl + +""" +公共基类逻辑(对应 Python TimeIntegratorBase) +""" + +mutable struct TimeIntegratorBase + cfd::Any + config::Any + domain::Domain + solution::Solution + residual_calculator::Any +end + +function TimeIntegratorBase(cfd::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + residual_calculator = cfd.residual_calculator + TimeIntegratorBase(cfd, config, domain, solution, residual_calculator) +end + +function compute_residual(integrator::TimeIntegratorBase) + compute!(integrator.residual_calculator) +end + +function apply_boundary(integrator::TimeIntegratorBase) + apply!(integrator.cfd.boundary_condition, integrator.solution.u) +end + +function map_idx(integrator::TimeIntegratorBase, i::Int) + return i - integrator.domain.ist + 1 +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03d/src/time_integration/factory.jl b/example/1d-linear-convection/weno3/julia/03d/src/time_integration/factory.jl new file mode 100644 index 000000000..271fcde5c --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/time_integration/factory.jl @@ -0,0 +1,29 @@ +# src/time_integration/factory.jl + +module TimeIntegratorFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "时间积分器 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + rk_order = cfd.config.rk_order + name = "rk$rk_order" + if !haskey(_REGISTRY, name) + available = sort(collect(keys(_REGISTRY))) + error("未注册的时间积分器: '$name'。可用选项: $available") + end + return _REGISTRY[name](cfd) +end + +# ✅ 关键:用 Main. 引用在 Main 模块中定义的类型 +register("rk1", cfd -> Main.RK1Integrator(cfd)) +register("rk2", cfd -> Main.RK2Integrator(cfd)) +register("rk3", cfd -> Main.RK3Integrator(cfd)) + +end # module TimeIntegratorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03d/src/time_integration/rk1.jl b/example/1d-linear-convection/weno3/julia/03d/src/time_integration/rk1.jl new file mode 100644 index 000000000..580db3599 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/time_integration/rk1.jl @@ -0,0 +1,19 @@ +# src/time_integration/rk1.jl + +mutable struct RK1Integrator + base::TimeIntegratorBase +end + +function RK1Integrator(cfd::Any) + RK1Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK1Integrator, dt::Float64) + compute_residual(integrator.base) + for i in integrator.base.domain.ist:(integrator.base.domain.ied - 1) + j = map_idx(integrator.base, i) + integrator.base.solution.u[i] += dt * integrator.base.solution.res[j] + end + apply_boundary(integrator.base) + update_old_field(integrator.base.solution) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03d/src/time_integration/rk2.jl b/example/1d-linear-convection/weno3/julia/03d/src/time_integration/rk2.jl new file mode 100644 index 000000000..ca2b7ab6a --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/time_integration/rk2.jl @@ -0,0 +1,30 @@ +# src/time_integration/rk2.jl + +mutable struct RK2Integrator + base::TimeIntegratorBase +end + +function RK2Integrator(cfd::Any) + RK2Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK2Integrator, dt::Float64) + base = integrator.base + compute_residual(base) + u_pred = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u_pred[i] += dt * base.solution.res[j] + end + base.solution.u .= u_pred + apply_boundary(base) + compute_residual(base) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = 0.5 * base.solution.un[i] + + 0.5 * base.solution.u[i] + + 0.5 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03d/src/time_integration/rk3.jl b/example/1d-linear-convection/weno3/julia/03d/src/time_integration/rk3.jl new file mode 100644 index 000000000..a8e07736d --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/time_integration/rk3.jl @@ -0,0 +1,44 @@ +# src/time_integration/rk3.jl + +mutable struct RK3Integrator + base::TimeIntegratorBase +end + +function RK3Integrator(cfd::Any) + RK3Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK3Integrator, dt::Float64) + base = integrator.base + # Stage 1 + compute_residual(base) + u1 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u1[i] += dt * base.solution.res[j] + end + base.solution.u .= u1 + apply_boundary(base) + # Stage 2 + compute_residual(base) + u2 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u2[i] = 0.75 * base.solution.un[i] + + 0.25 * base.solution.u[i] + + 0.25 * dt * base.solution.res[j] + end + base.solution.u .= u2 + apply_boundary(base) + # Stage 3 + compute_residual(base) + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = c1 * base.solution.un[i] + + c2 * base.solution.u[i] + + c3 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03d/src/time_integration/time_integration.jl b/example/1d-linear-convection/weno3/julia/03d/src/time_integration/time_integration.jl new file mode 100644 index 000000000..4e60bfc2b --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/time_integration/time_integration.jl @@ -0,0 +1,7 @@ +# src/time_integration/time_integration.jl + +include("base.jl") +include("rk1.jl") +include("rk2.jl") +include("rk3.jl") +include("factory.jl") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03d/src/utils.jl b/example/1d-linear-convection/weno3/julia/03d/src/utils.jl new file mode 100644 index 000000000..3786cd1a5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03d/src/utils.jl @@ -0,0 +1,9 @@ +# src/utils.jl + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end diff --git a/example/1d-linear-convection/weno3/julia/03f/examples/run_eno_weno.jl b/example/1d-linear-convection/weno3/julia/03f/examples/run_eno_weno.jl new file mode 100644 index 000000000..f02110d9e --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/examples/run_eno_weno.jl @@ -0,0 +1,99 @@ +# examples/run_eno_weno.jl +""" +1:1 复刻 run_eno_weno.py 的 Julia 版本 +""" + +include("../src/config.jl") +include("../src/mesh.jl") +include("../src/solver.jl") +include("../src/plotter.jl") + +function performEnoWenoAnalysisBAK() + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO3 求解 + println("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + with_reconstruction(config_eno3, "eno", 3) # 显式指定 3 阶 + config_eno3.dt = 0.0025 # 覆盖默认值 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + run!(cfd_eno3) # 求解并生成 result 字典 + + # 3. 配置并运行 WENO3 求解 + println("Running WENO3 solver...") + config_weno3 = CfdConfig() + with_reconstruction(config_weno3, "weno", 3) # 显式指定 3 阶(WENO 默认 5 阶) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + run!(cfd_weno3) + + # 5. 绘制 ENO/WENO 对比图 + println("Plotting comparison results...") + plot_eno_weno_comparison( + cfd_eno3.result, + cfd_weno3.result; + save_path="eno_weno_comparison.png" + ) + + return cfd_eno3, cfd_weno3 +end + +function performEnoWenoAnalysis() + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO3 求解 + println("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + with_reconstruction(config_eno3, "eno", 3) # 显式指定 3 阶 + config_eno3.dt = 0.0025 # 覆盖默认值 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + run!(cfd_eno3) # 求解并生成 result 字典 + + # 3. 配置并运行 WENO3 求解 + println("Running WENO3 solver...") + config_weno3 = CfdConfig() + with_reconstruction(config_weno3, "weno", 3) # 显式指定 3 阶(WENO 默认 5 阶) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + run!(cfd_weno3) + + # 新增 WENO5 + println("Running WENO5 solver...") + + config_weno5 = CfdConfig() + with_reconstruction(config_weno5, "weno", 5) + config_weno5.dt = 0.0025 + config_weno5.rk_order = 2 + + cfd_weno5 = Cfd(config_weno5, mesh) + run!(cfd_weno5) + + println("Plotting comparison results...") + + plotter = CFDPlotter() + + # 绘图时加入 weno5 + plot_comparison( + plotter, + [cfd_eno3.result, cfd_weno3.result, cfd_weno5.result], + save_path="eno_weno_comparison.png" + ) + + return cfd_eno3, cfd_weno3, cfd_weno5 +end + +# 主程序入口 +if abspath(PROGRAM_FILE) == @__FILE__ + performEnoWenoAnalysis() + println("Analysis completed!") +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/boundary.jl b/example/1d-linear-convection/weno3/julia/03f/src/boundary.jl new file mode 100644 index 000000000..82f58b71f --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/boundary.jl @@ -0,0 +1,121 @@ +# julia/boundary.jl +# 目标:与 Python boundary.py 行为完全一致(1:1 同构) +# 前提:ist/ied 在 Julia 中按 1-based 设置(ist = nghosts + 1) + +#using NPZ # 仅用于测试,实际模块可不依赖 + +# ---------------------- 抽象基类(Julia 风格:用函数接口) ---------------------- +# Julia 无 abstract class,用文档+约定 +# 所有子类型必须实现 apply!(bc, u) + +# ---------------------- PeriodicBoundary ---------------------- +mutable struct PeriodicBoundary + cfd::Any +end + +function apply!(self::PeriodicBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist # 1-based, e.g., 3 + ied = self.cfd.domain.ied # 1-based, e.g., 43 + + # 左 ghost: u[ist - 1 - ig] = u[ied - 1 - ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ied - 1 - ig] + end + # 右 ghost: u[ied + ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ied + ig] = u[ist + ig] + end +end + +# ---------------------- DirichletBoundary ---------------------- +mutable struct DirichletBoundary + cfd::Any +end + +function apply!(self::DirichletBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + left_value = getfield_safe(self.cfd.config, :left_boundary_value, 1.0) + right_value = getfield_safe(self.cfd.config, :right_boundary_value, 2.0) + + # 左边界 + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = left_value + end + # 右边界 + for ig in 0:(nghosts-1) + u[ied + ig] = right_value + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Dirichlet边界: 左值=$left_value, 右值=$right_value") + end +end + +# ---------------------- NeumannBoundary ---------------------- +mutable struct NeumannBoundary + cfd::Any +end + +function apply!(self::NeumannBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + # 左边界零梯度:u[ist - 1 - ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ist + ig] + end + # 右边界零梯度:u[ied + ig] = u[ied - 1 - ig] ← 注意是 ied - 1 - ig + for ig in 0:(nghosts-1) + u[ied + ig] = u[ied - 1 - ig] + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Neumann边界: 零梯度") + end +end + + +# ---------------------- BoundaryConditionFactory ---------------------- +module BoundaryConditionFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "边界条件 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + bc_type = cfd.config.boundary_type + if !(bc_type isa AbstractString) + error("boundary_type 必须为字符串,当前值: $bc_type") + end + + if !haskey(_REGISTRY, bc_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的边界条件: '$bc_type'。可用选项: $available") + end + + return _REGISTRY[bc_type](cfd) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("periodic", cfd -> Main.PeriodicBoundary(cfd)) +register("dirichlet", cfd -> Main.DirichletBoundary(cfd)) +register("neumann", cfd -> Main.NeumannBoundary(cfd)) + +end # module BoundaryConditionFactory + +export BoundaryConditionFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/config.jl b/example/1d-linear-convection/weno3/julia/03f/src/config.jl new file mode 100644 index 000000000..aaa4bdb52 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/config.jl @@ -0,0 +1,74 @@ +# julia/config.jl +""" +CfdConfig:与 Python config.py 完全同构 +""" +mutable struct CfdConfig + ic_type::String + recon_scheme::String + flux_type::String + rk_order::Int + wave_speed::Float64 + final_time::Float64 + dt::Float64 + boundary_type::String + left_boundary_value::Float64 + right_boundary_value::Float64 + spatial_order::Int + equation_type::String # ← 新增 + problem_type::String # ← 新增 + + function CfdConfig() + new( + "step", + "eno", + "rusanov", + #"engquist-osher", + 1, + 1.0, + 0.625, + 0.025, + "periodic", + 1.0, + 2.0, + 2, + "linear_advection", # equation_type + "linear_advection" # problem_type + ) + end +end + +""" +专用配置:重建方案(链式调用) +""" +function with_reconstruction(cfg::CfdConfig, scheme::String, order::Union{Int, Nothing}=nothing) + cfg.recon_scheme = lowercase(scheme) + + if order !== nothing + cfg.spatial_order = order + else + if startswith(cfg.recon_scheme, "weno") + cfg.spatial_order = 5 + elseif cfg.recon_scheme == "eno" + cfg.spatial_order = 3 + else + error("不支持的重建格式:$scheme(仅支持 eno/weno)") + end + end + + return cfg # 支持链式调用 +end + +""" +专用配置:边界条件(链式调用) +""" +function with_boundary(cfg::CfdConfig, bc_type::String; left_value=nothing, right_value=nothing) + cfg.boundary_type = bc_type + if left_value !== nothing + cfg.left_boundary_value = left_value + end + if right_value !== nothing + cfg.right_boundary_value = right_value + end + return cfg +end + diff --git a/example/1d-linear-convection/weno3/julia/03f/src/domain.jl b/example/1d-linear-convection/weno3/julia/03f/src/domain.jl new file mode 100644 index 000000000..eec261142 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/domain.jl @@ -0,0 +1,58 @@ +# julia/domain.jl +""" +Domain 结构体(与 domain.py 完全同构) +- 保存 config +- nghosts 计算逻辑 1:1 +- ist/ied 为 0-based(与 Python 一致) +""" + +include("mesh.jl") +include("utils.jl") + +mutable struct Domain + config::Any # 保存 config(与 Python 一致) + mesh::Mesh + nghosts::Int + ist::Int # 0-based + ied::Int # 0-based + ntcells::Int +end + +function Domain(config::Any, mesh::Mesh) + nghosts = _calc_nghosts(config) + ist = nghosts + 1 # ← 1-based 起始索引 + ied = ist + mesh.ncells # ← 1-based 结束索引(不包含) + ntcells = mesh.ncells + 2 * nghosts + Domain(config, mesh, nghosts, ist, ied, ntcells) +end + +function _calc_nghosts(config::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + order = getfield_safe(config, :spatial_order, 2) + + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + if scheme == "eno" + nghosts = order + elseif startswith(scheme, "weno") + nghosts = order ÷ 2 + 1 + else + error("未知重建格式 $(scheme),无法计算ghost层!") + end + + if nghosts <= 0 + error("计算得到的ghost层数量无效:$(nghosts)(阶数$(order),格式$(scheme))") + end + + return nghosts +end + +function is_physical_cell(domain::Domain, idx::Int) + return domain.ist <= idx < domain.ied +end + +function get_physical_indices(domain::Domain) + return domain.ist:(domain.ied - 1) +end diff --git a/example/1d-linear-convection/weno3/julia/03f/src/equations/base.jl b/example/1d-linear-convection/weno3/julia/03f/src/equations/base.jl new file mode 100644 index 000000000..1915d7e32 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/equations/base.jl @@ -0,0 +1,28 @@ +# src/equations/base.jl + +""" +抽象方程基类 +所有具体方程必须实现: +- eq_flux(eq, u) +- max_wave_speed(eq, u) +""" +abstract type Equation end + +""" +通量函数 F(u) +""" +function eq_flux(eq::Equation, u::Float64)::Float64 + error("Not implemented for $(typeof(eq))") +end + +""" +最大波速(用于 Rusanov 通量和 CFL) +""" +function max_wave_speed(eq::Equation, u::Float64)::Float64 + error("Not implemented for $(typeof(eq))") +end + +""" +方程变量数(标量方程返回 1) +""" +num_equations(eq::Equation) = 1 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/equations/equations.jl b/example/1d-linear-convection/weno3/julia/03f/src/equations/equations.jl new file mode 100644 index 000000000..de1195d45 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/equations/equations.jl @@ -0,0 +1,4 @@ +# src/equations/equations.jl + +include("base.jl") +include("linear_advection.jl") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/equations/linear_advection.jl b/example/1d-linear-convection/weno3/julia/03f/src/equations/linear_advection.jl new file mode 100644 index 000000000..3086c1ce0 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/equations/linear_advection.jl @@ -0,0 +1,16 @@ +# src/equations/linear_advection.jl + +""" +线性对流方程: ∂u/∂t + c ∂u/∂x = 0 +""" +mutable struct LinearAdvectionEquation <: Equation + wave_speed::Float64 + function LinearAdvectionEquation(config::Any) + c = getfield_safe(config, :wave_speed, 1.0) + new(c) + end +end + +eq_flux(eq::LinearAdvectionEquation, u::Float64) = eq.wave_speed * u + +max_wave_speed(eq::LinearAdvectionEquation, u::Float64) = abs(eq.wave_speed) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/flux/base.jl b/example/1d-linear-convection/weno3/julia/03f/src/flux/base.jl new file mode 100644 index 000000000..58dd6cc90 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/flux/base.jl @@ -0,0 +1,7 @@ +# src/flux/base.jl +""" +抽象通量计算器接口 +Julia 无 ABC,用文档约定: +- 所有子类型必须实现 `compute!(calc, qL, qR, flux)` +""" +abstract type InviscidFluxCalculator end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/flux/engquist_osher.jl b/example/1d-linear-convection/weno3/julia/03f/src/flux/engquist_osher.jl new file mode 100644 index 000000000..c27b81650 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/flux/engquist_osher.jl @@ -0,0 +1,26 @@ +# src/flux/engquist_osher.jl +mutable struct EngquistOsherFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function EngquistOsherFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::EngquistOsherFluxCalculator, qL::Vector{Float64}, qR::Vector{Float64}, flux::Vector{Float64}) + eq = calc.cfd.equation # ← 从 cfd 获取 Equation + for i in 1:calc.mesh.nnodes + c = eq.wave_speed # ← 假设 LinearAdvectionEquation 有 .wave_speed 字段 + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = qL[i] + u_R = qR[i] + flux[i] = cp * u_L + cm * u_R + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/flux/factory.jl b/example/1d-linear-convection/weno3/julia/03f/src/flux/factory.jl new file mode 100644 index 000000000..6ae4917db --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/flux/factory.jl @@ -0,0 +1,26 @@ +# src/flux/factory.jl + +module FluxCalculatorFactory + +# 全局注册表 +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "通量计算器 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + flux_type = cfd.config.flux_type + if !haskey(_REGISTRY, flux_type) + error("未注册的通量计算器: '$flux_type'。可用: $(keys(_REGISTRY))") + end + return _REGISTRY[flux_type](cfd) +end + +register("rusanov", cfd -> Main.RusanovFluxCalculator(cfd)) +register("engquist-osher", cfd -> Main.EngquistOsherFluxCalculator(cfd)) + +end # module FluxCalculatorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/flux/flux.jl b/example/1d-linear-convection/weno3/julia/03f/src/flux/flux.jl new file mode 100644 index 000000000..533f034c8 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/flux/flux.jl @@ -0,0 +1,10 @@ +# src/flux/flux.jl + +# 加载组件(顺序很重要!) +include("base.jl") +include("rusanov.jl") +include("engquist_osher.jl") +include("factory.jl") + +# 导出(如果你未来用模块) +# export InviscidFluxCalculator, RusanovFluxCalculator, EngquistOsherFluxCalculator, FluxCalculatorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/flux/rusanov.jl b/example/1d-linear-convection/weno3/julia/03f/src/flux/rusanov.jl new file mode 100644 index 000000000..3908c4537 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/flux/rusanov.jl @@ -0,0 +1,29 @@ +# src/flux/rusanov.jl + +mutable struct RusanovFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function RusanovFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::RusanovFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + eq = calc.cfd.equation + for i in 1:calc.mesh.nnodes + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = max_wave_speed(eq, u_L) + c_R = max_wave_speed(eq, u_R) + F_L = eq_flux(eq, u_L) + F_R = eq_flux(eq, u_R) + Smax = max(abs(c_L), abs(c_R)) + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/initial_condition.jl b/example/1d-linear-convection/weno3/julia/03f/src/initial_condition.jl new file mode 100644 index 000000000..1b9993aa8 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/initial_condition.jl @@ -0,0 +1,112 @@ +# julia/initial_condition.jl +""" +初始条件模块(Julia 版) +目标:与 Python initial_condition.py 行为完全一致 +""" + +# ---------------------- 抽象接口(Julia 无继承,用函数约定) ---------------------- +# 所有初始条件必须实现: +# evaluate_at(ic, x::AbstractVector{Float64}) -> Vector{Float64} +# apply(ic, solution) + +# ---------------------- StepFunctionIC ---------------------- +struct StepFunctionIC + config::Any +end + +function evaluate_at(ic::StepFunctionIC, x::AbstractVector{Float64}) + u0 = ones(Float64, length(x)) + for i in eachindex(x) + if 0.5 <= x[i] <= 1.0 + u0[i] = 2.0 + end + end + return u0 +end + +function apply(ic::StepFunctionIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- SineWaveIC ---------------------- +struct SineWaveIC + config::Any +end + +function evaluate_at(ic::SineWaveIC, x::AbstractVector{Float64}) + L = getfield_safe(ic.config, :domain_length, 2.0) + return sin.(2π * x / L) +end + +function apply(ic::SineWaveIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- GaussianPulseIC ---------------------- +struct GaussianPulseIC + config::Any +end + +function evaluate_at(ic::GaussianPulseIC, x::AbstractVector{Float64}) + center = getfield_safe(ic.config, :pulse_center, 0.5) + width = getfield_safe(ic.config, :pulse_width, 0.1) + return exp.(-((x .- center) ./ width).^2) +end + +function apply(ic::GaussianPulseIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- 公共辅助函数 ---------------------- +""" +将初始场 values 应用到 solution.u 的物理区域(含 ghost 的数组) +""" +function _apply_to_interior(solution, values) + domain = solution.domain + # Python: for i in range(domain.ist, domain.ied) + # Julia: for i in domain.ist:domain.ied-1 (因为 ied 是结束索引,不包含) + for i in domain.ist:(domain.ied - 1) + j = i - domain.ist + 1 # values 是 1-based,i - ist 是 0-based → +1 + solution.u[i] = values[j] + end +end + +# ---------------------- InitialConditionFactory ---------------------- +module InitialConditionFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "初始条件 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(ic_type::String, config::Any) + if !(ic_type isa AbstractString) + error("ic_type 必须为字符串,当前值: $ic_type") + end + + if !haskey(_REGISTRY, ic_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的初始条件: '$ic_type'。可用选项: $available") + end + + return _REGISTRY[ic_type](config) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("step", config -> Main.StepFunctionIC(config)) +register("sin", config -> Main.SineWaveIC(config)) +register("gaussian", config -> Main.GaussianPulseIC(config)) + +end # module InitialConditionFactory + +export InitialConditionFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/mesh.jl b/example/1d-linear-convection/weno3/julia/03f/src/mesh.jl new file mode 100644 index 000000000..1b0dd4bf9 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/mesh.jl @@ -0,0 +1,45 @@ +# julia/mesh.jl +""" +Mesh 结构体(与提供的 mesh.py 完全同构) +- 字段名、初始化顺序、循环逻辑完全一致 +- 不做任何泛化或优化 +""" + +mutable struct Mesh + xmin::Float64 + xmax::Float64 + ncells::Int + nnodes::Int + nx::Int + x::Vector{Float64} + xcc::Vector{Float64} + L::Float64 # 在 init_mesh 中赋值 + dx::Float64 # 在 init_mesh 中赋值 + + function Mesh() + # 与 Python __init__ 完全一致 + xmin = 0.0 + xmax = 2.0 + ncells = 40 + nnodes = ncells + 1 + nx = ncells + x = zeros(Float64, nnodes) + xcc = zeros(Float64, ncells) + + # 调用 init_mesh(模拟 Python) + L = xmax - xmin + dx = L / ncells + + # Generate node coordinates: for i in range(self.nnodes) + for i in 1:nnodes + x[i] = xmin + (i - 1) * dx # Python i → Julia i-1 + end + + # Generate cell center coordinates + for i in 1:ncells + xcc[i] = 0.5 * (x[i] + x[i+1]) + end + + new(xmin, xmax, ncells, nnodes, nx, x, xcc, L, dx) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/plotter.jl b/example/1d-linear-convection/weno3/julia/03f/src/plotter.jl new file mode 100644 index 000000000..a77d8d688 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/plotter.jl @@ -0,0 +1,147 @@ +# julia/plotter.jl +""" +CFDPlotter 的 Julia 实现(通过 PythonCall.jl 调用 Matplotlib) +确保与 Python plotter.py 行为完全一致 +""" + +using PythonCall + +# 初始化 Python 环境(加载 matplotlib, inflect) +const plt = pyimport("matplotlib.pyplot") +const inflect = pyimport("inflect") + +mutable struct CFDPlotter + default_styles::Dict{String, Any} + p::Py +end + +function CFDPlotter() + default_styles = Dict{String, Any}( + "numerical" => Dict( + :color => "blue", + :linestyle => "-", + :marker => "o", + :markerfacecolor => "none" + ), + "analytical" => Dict( + :color => "red", + :linestyle => "--", + :marker => "", + :linewidth => 1.5 + ), + "comparison" => [ + Dict(:color => "black", :linestyle => "-", :marker => "o", :markerfacecolor => "none"), + Dict(:color => "blue", :linestyle => "--", :marker => "s", :markerfacecolor => "none"), + Dict(:color => "green", :linestyle => ":", :marker => "^", :markerfacecolor => "none") + ] + ) + p = inflect.engine() + CFDPlotter(default_styles, p) +end + +""" +轻量即时绘图(快速验证结果) +""" +function plot_quick(plotter::CFDPlotter, cfd_result::Dict; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + rk_order = cfd_result["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = cfd_result["config"]["final_time"] + order = cfd_result["config"]["order"] + scheme = uppercase(cfd_result["config"]["scheme"]) + actual_title = "1D Convection (t=$(final_time))\n$(order)th-order $(scheme) + $(rk_str)-order RK" + end + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"]; + label="Numerical ($(uppercase(cfd_result["config"]["scheme"])))", + plotter.default_styles["numerical"]..., + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + # 通用样式 + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +""" +多格式/多精度对比绘图 +""" +function plot_comparison(plotter::CFDPlotter, result_list::Vector{Dict{String, Any}}; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + schemes = [uppercase(r["config"]["scheme"]) * string(r["config"]["order"]) for r in result_list] + rk_order = result_list[1]["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = result_list[1]["config"]["final_time"] + actual_title = "1D Convection Comparison (t=$(final_time))\n$(join(schemes, ", ")) + $(rk_str)-order RK" + end + + # 绘制多个数值解 + for (i, res) in enumerate(result_list) + style = plotter.default_styles["comparison"][mod1(i, length(plotter.default_styles["comparison"]))] + label = "Numerical ($(uppercase(res["config"]["scheme"]))$(res["config"]["order"]))" + plt.plot( + res["x"], res["numerical"]; + label=label, + style..., + markersize=5, linewidth=0.5 + ) + end + + # 绘制解析解 + plt.plot( + result_list[1]["x"], result_list[1]["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +function _set_common_style(plotter::CFDPlotter, title::String) + plt.title(title, fontsize=12) + plt.xlabel("x", fontsize=10) + plt.ylabel("u", fontsize=10) + plt.legend(fontsize=9) + plt.grid(true, color="gray", linestyle="--", linewidth=0.5, alpha=0.7) + plt.tight_layout() +end + +""" +快捷函数:ENO/WENO对比绘图 +""" +function plot_eno_weno_comparison(eno_result::Dict, weno_result::Dict; save_path=nothing) + plotter = CFDPlotter() + plot_comparison(plotter, [eno_result, weno_result]; save_path=save_path) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/problems/base.jl b/example/1d-linear-convection/weno3/julia/03f/src/problems/base.jl new file mode 100644 index 000000000..e29d0fbbe --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/problems/base.jl @@ -0,0 +1,22 @@ +# src/problems/base.jl + +""" +抽象问题基类 +所有具体问题必须实现: +- create_initial_condition(prob, config) +- create_boundary_condition(prob, cfd) +- exact_solution(prob, x, t) +""" +abstract type Problem end + +function create_initial_condition(prob::Problem, config::Any) + error("Not implemented for $(typeof(prob))") +end + +function create_boundary_condition(prob::Problem, cfd::Any) + error("Not implemented for $(typeof(prob))") +end + +function exact_solution(prob::Problem, x::Vector{Float64}, t::Float64)::Vector{Float64} + error("Not implemented for $(typeof(prob))") +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/problems/linear_advection.jl b/example/1d-linear-convection/weno3/julia/03f/src/problems/linear_advection.jl new file mode 100644 index 000000000..8b37d9f85 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/problems/linear_advection.jl @@ -0,0 +1,43 @@ +# src/problems/linear_advection.jl + +""" +线性对流问题:定义初始条件、边界条件、解析解 +""" +mutable struct LinearAdvectionProblem <: Problem + ic_type::String + boundary_type::String + left_value::Float64 + right_value::Float64 + domain_length::Float64 + wave_speed::Float64 # 从 config 获取,未来可从 equation 获取 + + function LinearAdvectionProblem(config::Any) + new( + getfield_safe(config, :ic_type, "step"), + getfield_safe(config, :boundary_type, "periodic"), + getfield_safe(config, :left_boundary_value, 1.0), + getfield_safe(config, :right_boundary_value, 2.0), + getfield_safe(config, :domain_length, 2.0), + getfield_safe(config, :wave_speed, 1.0) + ) + end +end + +function create_initial_condition(prob::LinearAdvectionProblem, config::Any) + return Main.InitialConditionFactory.create(prob.ic_type, config) +end + +function create_boundary_condition(prob::LinearAdvectionProblem, cfd::Any) + # 复用原有 BC 工厂(cfd 需包含 config + domain) + return Main.BoundaryConditionFactory.create(cfd) +end + +function exact_solution(prob::LinearAdvectionProblem, x::Vector{Float64}, t::Float64)::Vector{Float64} + L = prob.domain_length + c = prob.wave_speed + # 周期性平移 + x_shifted = @. (x - c * t + L) % L + # 重用 IC 的 evaluate_at + ic = Main.InitialConditionFactory.create(prob.ic_type, (ic_type=prob.ic_type,)) + return Main.evaluate_at(ic, x_shifted) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/problems/problems.jl b/example/1d-linear-convection/weno3/julia/03f/src/problems/problems.jl new file mode 100644 index 000000000..983113978 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/problems/problems.jl @@ -0,0 +1,4 @@ +# src/problems/problems.jl + +include("base.jl") +include("linear_advection.jl") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/reconstructor/eno.jl b/example/1d-linear-convection/weno3/julia/03f/src/reconstructor/eno.jl new file mode 100644 index 000000000..e78a636f3 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/reconstructor/eno.jl @@ -0,0 +1,107 @@ +# julia/reconstructor/eno.jl +""" +ENO 重构器(与 reconstructor/eno.py 完全同构) +""" + +# ---------------------- ENO 系数初始化 ---------------------- +function _init_eno_coef!(spatial_order::Int, coef::Matrix{Float64}) + if spatial_order == 1 + coef[1, 1] = 1.0 + coef[2, 1] = 1.0 + elseif spatial_order == 2 + coef[1, 1:2] = [3.0/2.0, -1.0/2.0] + coef[2, 1:2] = [1.0/2.0, 1.0/2.0] + coef[3, 1:2] = [-1.0/2.0, 3.0/2.0] + elseif spatial_order == 3 + coef[1, 1:3] = [11.0/6.0, -7.0/6.0, 1.0/3.0] + coef[2, 1:3] = [1.0/3.0, 5.0/6.0, -1.0/6.0] + coef[3, 1:3] = [-1.0/6.0, 5.0/6.0, 1.0/3.0] + coef[4, 1:3] = [1.0/3.0, -7.0/6.0, 11.0/6.0] + elseif spatial_order == 4 + coef[1, 1:4] = [25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0] + coef[2, 1:4] = [1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0] + coef[3, 1:4] = [-1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0] + coef[4, 1:4] = [1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0] + coef[5, 1:4] = [-1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0] + elseif spatial_order == 5 + coef[1, 1:5] = [137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0] + coef[2, 1:5] = [1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0] + coef[3, 1:5] = [-1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0] + coef[4, 1:5] = [1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0] + coef[5, 1:5] = [-1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0] + coef[6, 1:5] = [1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0] + elseif spatial_order == 6 + coef[1, 1:6] = [49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0] + coef[2, 1:6] = [1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0] + coef[3, 1:6] = [-1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0] + coef[4, 1:6] = [1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0] + coef[5, 1:6] = [-1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0] + coef[6, 1:6] = [1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0] + coef[7, 1:6] = [-1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0] + elseif spatial_order == 7 + coef[1, 1:7] = [363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0] + coef[2, 1:7] = [1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0] + coef[3, 1:7] = [-1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0] + coef[4, 1:7] = [1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0] + coef[5, 1:7] = [-1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0] + coef[6, 1:7] = [1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0] + coef[7, 1:7] = [-1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0] + coef[8, 1:7] = [1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0] + else + error("ENO 系数未实现 order=$spatial_order") + end +end + +# ---------------------- ENO 重构器 ---------------------- +mutable struct EnoReconstructor + spatial_order::Int + ntcells::Int + lmc::Vector{Int} + coef::Matrix{Float64} + dd::Matrix{Float64} + + function EnoReconstructor(spatial_order::Int, ntcells::Int) + lmc = zeros(Int, ntcells) + coef = zeros(Float64, spatial_order + 1, spatial_order) + dd = zeros(Float64, spatial_order, ntcells) + _init_eno_coef!(spatial_order, coef) + new(spatial_order, ntcells, lmc, coef, dd) + end +end + +function reconstruct(rec::EnoReconstructor, q::Vector{Float64}, cfd::Any) + # 1. 差商计算 (dd[1,:] = q) + @views rec.dd[1, :] .= q + for m in 2:rec.spatial_order + for j in 1:(rec.ntcells - m + 1) + rec.dd[m, j] = rec.dd[m-1, j+1] - rec.dd[m-1, j] + end + end + + # 2. 选择 smoothest stencil + domain = cfd.domain + for i in (domain.ist - 1):(domain.ied) # Python: range(ist-1, ied+1) → ied+1-1 = ied + rec.lmc[i] = i + for m in 2:rec.spatial_order + if abs(rec.dd[m, rec.lmc[i] - 1]) < abs(rec.dd[m, rec.lmc[i]]) + rec.lmc[i] -= 1 + end + end + end + + # 3. 重构界面值 + solution = cfd.solution + for i in domain.ist:(domain.ied) # Python: range(ist, ied+1) → ied+1-1 = ied + j = i - domain.ist + 1 # Julia 1-based + k1 = rec.lmc[i - 1] + k2 = rec.lmc[i] + r1 = (i - 1) - k1 + 1 + r2 = i - k2 + 1 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in 1:rec.spatial_order + solution.q_face_left[j] += q[k1 + m - 1] * rec.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m - 1] * rec.coef[r2, m] + end + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/reconstructor/factory.jl b/example/1d-linear-convection/weno3/julia/03f/src/reconstructor/factory.jl new file mode 100644 index 000000000..aef273651 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/reconstructor/factory.jl @@ -0,0 +1,46 @@ +# src/reconstructor/factory.jl + +module ReconstructorFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "重构器 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(config::Any, domain::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + # 处理 WENO 默认命名 + if scheme == "weno" + order = getfield_safe(config, :spatial_order, 5) + scheme = "weno$(order)" + end + + if !haskey(_REGISTRY, scheme) + available = sort(collect(keys(_REGISTRY))) + error("未注册的重构器: '$scheme'。可用选项: $available") + end + + return _REGISTRY[scheme](config, domain) +end + +# ✅ 手动注册(与你原有风格一致) +register("eno", (config, domain) -> Main.EnoReconstructor(config.spatial_order, domain.ntcells)) +register("weno3", (config, domain) -> Main.Weno3Reconstructor()) +register("weno5", (config, domain) -> Main.Weno5Reconstructor()) # ← 新增 + +# ---------------------- 辅助函数 ---------------------- +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +end # module ReconstructorFactory + +export ReconstructorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/reconstructor/weno3.jl b/example/1d-linear-convection/weno3/julia/03f/src/reconstructor/weno3.jl new file mode 100644 index 000000000..e7067d573 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/reconstructor/weno3.jl @@ -0,0 +1,65 @@ +# julia/reconstructor/weno3.jl +""" +WENO3 重构器(与 reconstructor/weno3.py 完全同构) +""" + +mutable struct Weno3Reconstructor + # 无字段,与 Python 一致 +end + +function reconstruct(rec::Weno3Reconstructor, q::Vector{Float64}, cfd::Any) + domain = cfd.domain + solution = cfd.solution + _reconstruct_left_interfaces_weno3(domain, q, solution.q_face_left) + _reconstruct_right_interfaces_weno3(domain, q, solution.q_face_right) +end + +function _reconstruct_left_interfaces_weno3(domain, u, qL) + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in (domain.ist - 1):(domain.ied - 1) + j = i - (domain.ist - 1) + 1 # ← Julia 1-based: j = i - (ist-1) 对应 Python j = i - (ist-1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = _reconstruct_from_left_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_right_interfaces_weno3(domain, u, qR) + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in domain.ist:domain.ied + j = i - domain.ist + 1 # ← Julia 1-based: j = i - ist 对应 Python j = i - ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = _reconstruct_from_right_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_from_left_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -0.5*v1 + 1.5*v2 # r=1 stencil + q1 = 0.5*v2 + 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end + +function _reconstruct_from_right_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 0.5*v1 + 0.5*v2 # r=1 stencil + q1 = 1.5*v2 - 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/reconstructor/weno5.jl b/example/1d-linear-convection/weno3/julia/03f/src/reconstructor/weno5.jl new file mode 100644 index 000000000..8e5338bc7 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/reconstructor/weno5.jl @@ -0,0 +1,77 @@ +# src/reconstructor/weno5.jl +""" +WENO5 重构器(与提供的 Python weno5.py 完全同构) +- 无字段(与 Weno3Reconstructor 一致) +- 所有逻辑 1:1 对齐 Python +""" + +mutable struct Weno5Reconstructor + # 无字段,与 Python class Weno5Reconstructor 一致 +end + +function reconstruct(rec::Weno5Reconstructor, q::Vector{Float64}, cfd::Any) + domain = cfd.domain + solution = cfd.solution + _reconstruct_left_interfaces_weno5(domain, q, solution.q_face_left) + _reconstruct_right_interfaces_weno5(domain, q, solution.q_face_right) +end + +function _reconstruct_left_interfaces_weno5(domain, u, qL) + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in (domain.ist - 1):(domain.ied - 1) + j = i - (domain.ist - 1) + 1 # Julia 1-based + v1, v2, v3, v4, v5 = u[i-2], u[i-1], u[i], u[i+1], u[i+2] + qL[j] = _reconstruct_from_left_biased_stencil(v1, v2, v3, v4, v5) + end +end + +function _reconstruct_right_interfaces_weno5(domain, u, qR) + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in domain.ist:domain.ied + j = i - domain.ist + 1 # Julia 1-based + v1, v2, v3, v4, v5 = u[i-2], u[i-1], u[i], u[i+1], u[i+2] + qR[j] = _reconstruct_from_right_biased_stencil(v1, v2, v3, v4, v5) + end +end + +function _reconstruct_from_left_biased_stencil(v1, v2, v3, v4, v5) + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)^2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)^2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)^2 + (1.0/4.0)*(v2 - v4)^2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)^2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)^2 + d0 = 1.0/10.0 + d1 = 3.0/5.0 + d2 = 3.0/10.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha2 = d2 / (eps + beta2)^2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = 1.0/3.0*v1 - 7.0/6.0*v2 + 11.0/6.0*v3 # r=2 + q1 = -1.0/6.0*v2 + 5.0/6.0*v3 + 1.0/3.0*v4 # r=1 + q2 = 1.0/3.0*v3 + 5.0/6.0*v4 - 1.0/6.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 +end + +function _reconstruct_from_right_biased_stencil(v1, v2, v3, v4, v5) + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)^2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)^2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)^2 + (1.0/4.0)*(v2 - v4)^2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)^2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)^2 + d0 = 3.0/10.0 + d1 = 3.0/5.0 + d2 = 1.0/10.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha2 = d2 / (eps + beta2)^2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = -1.0/6.0*v1 + 5.0/6.0*v2 + 1.0/3.0*v3 # r=2 + q1 = 1.0/3.0*v2 + 5.0/6.0*v3 - 1.0/6.0*v4 # r=1 + q2 = 11.0/6.0*v3 - 7.0/6.0*v4 + 1.0/3.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/registry.jl b/example/1d-linear-convection/weno3/julia/03f/src/registry.jl new file mode 100644 index 000000000..6acd9aaaf --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/registry.jl @@ -0,0 +1,164 @@ +""" +统一注册系统 + 通用工厂 +- ComponentRegistry: 组件注册表 +- @register_component: 装饰器宏 +- BaseFactory: 通用工厂接口 +""" + +module ComponentRegistry + +# 注册表:Dict{category => Dict{name => constructor}} +const _REGISTRIES = Dict{String, Dict{String, Function}}() +const _VERBOSE = Ref(true) + +# ---------------------- 注册核心逻辑 ---------------------- +""" + register(category::String, name::String, ctor::Function) + +注册一个组件构造函数到指定类别。 +如果已存在同名组件且不同,则发出警告。 +""" +function register(category::String, name::String, ctor::Function) + if !haskey(_REGISTRIES, category) + _REGISTRIES[category] = Dict{String, Function}() + end + + registry = _REGISTRIES[category] + if haskey(registry, name) + if registry[name] !== ctor && _VERBOSE[] + @warn "覆盖注册: $category.$name" + end + end + + registry[name] = ctor + if _VERBOSE[] + println("✅ 已注册: $category.$name -> $(nameof(ctor))") + end +end + +""" + create(category::String, name::String, args...; kwargs...) + +从注册表创建组件实例。 +""" +function create(category::String, name::String, args...; kwargs...) + if !haskey(_REGISTRIES, category) + error("❌ 未知类别: $category (可用: $(collect(keys(_REGISTRIES))))") + end + + registry = _REGISTRIES[category] + lname = lowercase(name) + if !haskey(registry, lname) + available = sort(collect(keys(registry))) + error("❌ 未找到: $category.$name (可用: $available)") + end + + return registry[lname](args...; kwargs...) +end + +""" + list_all() + +返回所有已注册组件(按类别)。 +""" +function list_all() + return Dict(cat => sort(collect(keys(reg))) for (cat, reg) in _REGISTRIES) +end + +""" + set_verbose(flag::Bool) + +开启/关闭注册提示。 +""" +function set_verbose(flag::Bool) + _VERBOSE[] = flag +end + +end # module ComponentRegistry + + +# ---------------------- 装饰器宏:@register_component ---------------------- +""" +@register_component(category, [name]) + +用法: + @register_component("boundary", "periodic") + struct PeriodicBoundary ... + +若省略 name,则使用类型名的小写形式。 +""" +macro register_component(category::String, name_expr) + error("@register_component 需在类型定义前使用,且必须在模块顶层") +end + +macro register_component(category::String) + error("@register_component(category, name) 需指定 name 或在类型后使用") +end + +# 重载:@register_component("category", "name") struct X ... end +macro register_component(category::String, name::String, ex) + if !Meta.isexpr(ex, :struct) + error("@register_component 必须作用于 struct 定义") + end + + struct_name = ex.args[2] + if Meta.isexpr(struct_name, :curly) + struct_name = struct_name.args[1] + end + + # 插入注册调用(在模块顶层) + quote + $(esc(ex)) + $(ComponentRegistry).register($(category), $(name), $(esc(struct_name))) + end +end + +# 重载:@register_component("category") struct X ... end → name = lowercase(nameof(X)) +macro register_component(category::String, ex) + if !Meta.isexpr(ex, :struct) + error("@register_component 必须作用于 struct 定义") + end + + struct_name = ex.args[2] + if Meta.isexpr(struct_name, :curly) + struct_name = struct_name.args[1] + end + + name_str = string(struct_name) |> lowercase + + quote + $(esc(ex)) + $(ComponentRegistry).register($(category), $(name_str), $(esc(struct_name))) + end +end + + +# ---------------------- 通用工厂 ---------------------- +module BaseFactory + +using ..ComponentRegistry + +""" + create_component(category::String, name::String, args...; kwargs...) + +通用工厂接口,与 Python BaseFactory.create_component 行为一致。 +""" +function create_component(category::String, name::String, args...; kwargs...) + return ComponentRegistry.create(category, name, args...; kwargs...) +end + +""" + get_available_components(category::String) + +列出某类别下所有可用组件。 +""" +function get_available_components(category::String) + all = ComponentRegistry.list_all() + return get(all, category, String[]) +end + +end # module BaseFactory + + +# 导出接口 +export ComponentRegistry, BaseFactory, @register_component \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/residual.jl b/example/1d-linear-convection/weno3/julia/03f/src/residual.jl new file mode 100644 index 000000000..11a885934 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/residual.jl @@ -0,0 +1,69 @@ +# julia/residual.jl +""" +残差计算器(与 residual.py 完全同构) +- 封装重建→通量→散度完整流程 +- 依赖 cfd 的多个字段 +""" + +include("mesh.jl") + +mutable struct ResidualCalculator + cfd::Any + config::Any + domain::Any + solution::Any + mesh::Mesh + reconstructor::Any + flux_calculator::Any + + function ResidualCalculator(cfd::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + mesh = domain.mesh + reconstructor = cfd.reconstructor + + # ✅ 内部创建 flux_calculator(对标 Python) + flux_calculator = FluxCalculatorFactory.create(cfd) + + new(cfd, config, domain, solution, mesh, reconstructor, flux_calculator) + end +end + + +""" +计算完整残差(对外唯一接口) +""" +function compute!(calc::ResidualCalculator) + _reconstruct(calc) + _compute_inviscid_flux(calc) + _compute_flux_divergence(calc) +end + +""" +私有方法:界面值重建 +""" +function _reconstruct(calc::ResidualCalculator) + reconstruct(calc.reconstructor, calc.solution.u, calc.cfd) +end + +""" +私有方法:计算无粘通量 +""" +function _compute_inviscid_flux(calc::ResidualCalculator) + compute!(calc.flux_calculator, + calc.solution.q_face_left, + calc.solution.q_face_right, + calc.solution.flux) +end + +""" +私有方法:计算通量散度(残差 = -dF/dx) +""" +function _compute_flux_divergence(calc::ResidualCalculator) + solution = calc.solution + mesh = calc.mesh + for i in 1:mesh.ncells + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / mesh.dx + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/solution.jl b/example/1d-linear-convection/weno3/julia/03f/src/solution.jl new file mode 100644 index 000000000..7044c0014 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/solution.jl @@ -0,0 +1,60 @@ +# julia/solution.jl +""" +Solution 结构体(与真实 solution.py 完全同构) +字段顺序、方法、逻辑 1:1 对齐 +""" + +include("mesh.jl") +include("domain.jl") +include("initial_condition.jl") + +mutable struct Solution + domain::Domain + q_face_left::Vector{Float64} + q_face_right::Vector{Float64} + flux::Vector{Float64} + res::Vector{Float64} + u::Vector{Float64} + un::Vector{Float64} + + function Solution(config::Any, domain::Domain) + mesh = domain.mesh + + q_face_left = zeros(Float64, mesh.nnodes) + q_face_right = zeros(Float64, mesh.nnodes) + flux = zeros(Float64, mesh.nnodes) + res = zeros(Float64, mesh.ncells) + u = zeros(Float64, domain.ntcells) + un = zeros(Float64, domain.ntcells) + + sol = new(domain, q_face_left, q_face_right, flux, res, u, un) + initialize_from_config(sol, config) + return sol + end +end + +""" +重置解数组为初始状态 +""" +function reset_solution(sol::Solution) + fill!(sol.u, 0.0) + fill!(sol.un, 0.0) +end + +""" +根据配置初始化场 +""" + +function initialize_from_config(sol::Solution, config::Any) + ic_type = getfield_safe(config, :ic_type, "step") + ic = InitialConditionFactory.create(ic_type, config) + apply(ic, sol) +end + +""" +更新旧场:un = u +""" +function update_old_field(sol::Solution) + sol.un .= sol.u # 等价于 Python 的 un[:] = u[:] +end + diff --git a/example/1d-linear-convection/weno3/julia/03f/src/solver.jl b/example/1d-linear-convection/weno3/julia/03f/src/solver.jl new file mode 100644 index 000000000..76a8132d4 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/solver.jl @@ -0,0 +1,163 @@ +# julia/solver.jl +""" +CFD 求解器主类(与 solver.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("initial_condition.jl") +include("boundary.jl") +include("flux/flux.jl") +include("residual.jl") +include("time_integration/time_integration.jl") +include("reconstructor/eno.jl") +include("reconstructor/weno3.jl") +include("reconstructor/weno5.jl") +include("reconstructor/factory.jl") + +include("equations/equations.jl") +include("problems/problems.jl") + +# 导入工厂模块(必须在顶层!) +using .FluxCalculatorFactory +using .BoundaryConditionFactory +using .ReconstructorFactory + +# ---------------------- Cfd 求解器 ---------------------- +mutable struct Cfd + config::Any + domain::Domain + solution::Solution + reconstructor::Any + residual_calculator::ResidualCalculator + integrator::Any + boundary_condition::Any + result::Dict{String, Any} + + function Cfd(config::Any, mesh::Mesh) + domain = Domain(config, mesh) + solution = Solution(config, domain) + reconstructor = ReconstructorFactory.create(config, domain) + + # 1. 创建 Equation 和 Problem + equation = create_equation(config) + problem = create_problem(config) + + # 2. 初始上下文(仅包含不依赖其他组件的字段) + full_cfd = ( + config = config, + domain = domain, + solution = solution, + reconstructor = reconstructor, + equation = equation, # ← 新增 + problem = problem # ← 新增 + ) + + # 3. 创建边界条件(通过 problem) + boundary_condition = create_boundary_condition(problem, full_cfd) + full_cfd = merge(full_cfd, (boundary_condition = boundary_condition,)) + + + # 4. 创建 residual_calculator(现在可访问 equation) + residual_calculator = ResidualCalculator(full_cfd) + full_cfd = merge(full_cfd, (residual_calculator = residual_calculator,)) + + # 5. 创建 integrator + integrator = TimeIntegratorFactory.create(full_cfd) + full_cfd = merge(full_cfd, (integrator = integrator,)) + + # 6. 注入完整 self + residual_calculator.cfd = full_cfd + integrator.base.cfd = full_cfd + + # 7. 初始化解(通过 problem) + ic = create_initial_condition(problem, config) + Main.apply(ic, solution) # 注意:apply 在 Main + + result = Dict{String, Any}() + new(config, domain, solution, reconstructor, residual_calculator, integrator, boundary_condition, result) + + end +end + +""" +通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界 +""" +function exact_solution(cfd::Cfd) + x = cfd.domain.mesh.xcc + T = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = @. (x - c * T + L) % L + + # ✅ 使用工厂创建初始条件(对标 Python) + ic = InitialConditionFactory.create(cfd.config.ic_type, cfd.config) + + return evaluate_at(ic, x_shifted) +end + +""" +主求解循环 +""" +function run!(cfd::Cfd) + # 应用初始边界条件并同步 old field + apply!(cfd.boundary_condition, cfd.solution.u) + update_old_field(cfd.solution) + + t = 0.0 + dt_old = cfd.config.dt + dt = dt_old + + while t < cfd.config.final_time + if t + dt > cfd.config.final_time + dt = cfd.config.final_time - t + end + #@show t, dt, maximum(cfd.solution.u), minimum(cfd.solution.u) + # 执行时间步 + step(cfd.integrator, dt) + t += dt + end + + # 恢复 dt + cfd.config.dt = dt_old + + # 整理结果 + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied-1] # Python: [ist:ied] + analytical = exact_solution(cfd) + + cfd.result = Dict( + "x" => cfd.domain.mesh.xcc, + "numerical" => u_numerical, + "analytical" => analytical, + "config" => Dict( + "scheme" => cfd.config.recon_scheme, + "order" => cfd.config.spatial_order, + "rk_order" => cfd.config.rk_order, + "final_time" => cfd.config.final_time + ) + ) + + return u_numerical +end + +# --- Equation / Problem 创建函数 --- +function create_equation(config::Any) + eq_type = config.equation_type + if eq_type == "linear_advection" + return LinearAdvectionEquation(config) + else + error("Unknown equation type: $eq_type") + end +end + +function create_problem(config::Any) + prob_type = config.problem_type + if prob_type == "linear_advection" + return LinearAdvectionProblem(config) + else + error("Unknown problem type: $prob_type") + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/time_integration/base.jl b/example/1d-linear-convection/weno3/julia/03f/src/time_integration/base.jl new file mode 100644 index 000000000..a5b93bc19 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/time_integration/base.jl @@ -0,0 +1,33 @@ +# src/time_integration/base.jl + +""" +公共基类逻辑(对应 Python TimeIntegratorBase) +""" + +mutable struct TimeIntegratorBase + cfd::Any + config::Any + domain::Domain + solution::Solution + residual_calculator::Any +end + +function TimeIntegratorBase(cfd::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + residual_calculator = cfd.residual_calculator + TimeIntegratorBase(cfd, config, domain, solution, residual_calculator) +end + +function compute_residual(integrator::TimeIntegratorBase) + compute!(integrator.residual_calculator) +end + +function apply_boundary(integrator::TimeIntegratorBase) + apply!(integrator.cfd.boundary_condition, integrator.solution.u) +end + +function map_idx(integrator::TimeIntegratorBase, i::Int) + return i - integrator.domain.ist + 1 +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/time_integration/factory.jl b/example/1d-linear-convection/weno3/julia/03f/src/time_integration/factory.jl new file mode 100644 index 000000000..271fcde5c --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/time_integration/factory.jl @@ -0,0 +1,29 @@ +# src/time_integration/factory.jl + +module TimeIntegratorFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "时间积分器 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + rk_order = cfd.config.rk_order + name = "rk$rk_order" + if !haskey(_REGISTRY, name) + available = sort(collect(keys(_REGISTRY))) + error("未注册的时间积分器: '$name'。可用选项: $available") + end + return _REGISTRY[name](cfd) +end + +# ✅ 关键:用 Main. 引用在 Main 模块中定义的类型 +register("rk1", cfd -> Main.RK1Integrator(cfd)) +register("rk2", cfd -> Main.RK2Integrator(cfd)) +register("rk3", cfd -> Main.RK3Integrator(cfd)) + +end # module TimeIntegratorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/time_integration/rk1.jl b/example/1d-linear-convection/weno3/julia/03f/src/time_integration/rk1.jl new file mode 100644 index 000000000..580db3599 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/time_integration/rk1.jl @@ -0,0 +1,19 @@ +# src/time_integration/rk1.jl + +mutable struct RK1Integrator + base::TimeIntegratorBase +end + +function RK1Integrator(cfd::Any) + RK1Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK1Integrator, dt::Float64) + compute_residual(integrator.base) + for i in integrator.base.domain.ist:(integrator.base.domain.ied - 1) + j = map_idx(integrator.base, i) + integrator.base.solution.u[i] += dt * integrator.base.solution.res[j] + end + apply_boundary(integrator.base) + update_old_field(integrator.base.solution) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/time_integration/rk2.jl b/example/1d-linear-convection/weno3/julia/03f/src/time_integration/rk2.jl new file mode 100644 index 000000000..ca2b7ab6a --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/time_integration/rk2.jl @@ -0,0 +1,30 @@ +# src/time_integration/rk2.jl + +mutable struct RK2Integrator + base::TimeIntegratorBase +end + +function RK2Integrator(cfd::Any) + RK2Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK2Integrator, dt::Float64) + base = integrator.base + compute_residual(base) + u_pred = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u_pred[i] += dt * base.solution.res[j] + end + base.solution.u .= u_pred + apply_boundary(base) + compute_residual(base) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = 0.5 * base.solution.un[i] + + 0.5 * base.solution.u[i] + + 0.5 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/time_integration/rk3.jl b/example/1d-linear-convection/weno3/julia/03f/src/time_integration/rk3.jl new file mode 100644 index 000000000..a8e07736d --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/time_integration/rk3.jl @@ -0,0 +1,44 @@ +# src/time_integration/rk3.jl + +mutable struct RK3Integrator + base::TimeIntegratorBase +end + +function RK3Integrator(cfd::Any) + RK3Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK3Integrator, dt::Float64) + base = integrator.base + # Stage 1 + compute_residual(base) + u1 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u1[i] += dt * base.solution.res[j] + end + base.solution.u .= u1 + apply_boundary(base) + # Stage 2 + compute_residual(base) + u2 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u2[i] = 0.75 * base.solution.un[i] + + 0.25 * base.solution.u[i] + + 0.25 * dt * base.solution.res[j] + end + base.solution.u .= u2 + apply_boundary(base) + # Stage 3 + compute_residual(base) + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = c1 * base.solution.un[i] + + c2 * base.solution.u[i] + + c3 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/time_integration/time_integration.jl b/example/1d-linear-convection/weno3/julia/03f/src/time_integration/time_integration.jl new file mode 100644 index 000000000..4e60bfc2b --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/time_integration/time_integration.jl @@ -0,0 +1,7 @@ +# src/time_integration/time_integration.jl + +include("base.jl") +include("rk1.jl") +include("rk2.jl") +include("rk3.jl") +include("factory.jl") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03f/src/utils.jl b/example/1d-linear-convection/weno3/julia/03f/src/utils.jl new file mode 100644 index 000000000..3786cd1a5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03f/src/utils.jl @@ -0,0 +1,9 @@ +# src/utils.jl + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end diff --git a/example/1d-linear-convection/weno3/julia/03g/examples/run_eno_weno.jl b/example/1d-linear-convection/weno3/julia/03g/examples/run_eno_weno.jl new file mode 100644 index 000000000..f02110d9e --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/examples/run_eno_weno.jl @@ -0,0 +1,99 @@ +# examples/run_eno_weno.jl +""" +1:1 复刻 run_eno_weno.py 的 Julia 版本 +""" + +include("../src/config.jl") +include("../src/mesh.jl") +include("../src/solver.jl") +include("../src/plotter.jl") + +function performEnoWenoAnalysisBAK() + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO3 求解 + println("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + with_reconstruction(config_eno3, "eno", 3) # 显式指定 3 阶 + config_eno3.dt = 0.0025 # 覆盖默认值 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + run!(cfd_eno3) # 求解并生成 result 字典 + + # 3. 配置并运行 WENO3 求解 + println("Running WENO3 solver...") + config_weno3 = CfdConfig() + with_reconstruction(config_weno3, "weno", 3) # 显式指定 3 阶(WENO 默认 5 阶) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + run!(cfd_weno3) + + # 5. 绘制 ENO/WENO 对比图 + println("Plotting comparison results...") + plot_eno_weno_comparison( + cfd_eno3.result, + cfd_weno3.result; + save_path="eno_weno_comparison.png" + ) + + return cfd_eno3, cfd_weno3 +end + +function performEnoWenoAnalysis() + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO3 求解 + println("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + with_reconstruction(config_eno3, "eno", 3) # 显式指定 3 阶 + config_eno3.dt = 0.0025 # 覆盖默认值 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + run!(cfd_eno3) # 求解并生成 result 字典 + + # 3. 配置并运行 WENO3 求解 + println("Running WENO3 solver...") + config_weno3 = CfdConfig() + with_reconstruction(config_weno3, "weno", 3) # 显式指定 3 阶(WENO 默认 5 阶) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + run!(cfd_weno3) + + # 新增 WENO5 + println("Running WENO5 solver...") + + config_weno5 = CfdConfig() + with_reconstruction(config_weno5, "weno", 5) + config_weno5.dt = 0.0025 + config_weno5.rk_order = 2 + + cfd_weno5 = Cfd(config_weno5, mesh) + run!(cfd_weno5) + + println("Plotting comparison results...") + + plotter = CFDPlotter() + + # 绘图时加入 weno5 + plot_comparison( + plotter, + [cfd_eno3.result, cfd_weno3.result, cfd_weno5.result], + save_path="eno_weno_comparison.png" + ) + + return cfd_eno3, cfd_weno3, cfd_weno5 +end + +# 主程序入口 +if abspath(PROGRAM_FILE) == @__FILE__ + performEnoWenoAnalysis() + println("Analysis completed!") +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/boundary.jl b/example/1d-linear-convection/weno3/julia/03g/src/boundary.jl new file mode 100644 index 000000000..82f58b71f --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/boundary.jl @@ -0,0 +1,121 @@ +# julia/boundary.jl +# 目标:与 Python boundary.py 行为完全一致(1:1 同构) +# 前提:ist/ied 在 Julia 中按 1-based 设置(ist = nghosts + 1) + +#using NPZ # 仅用于测试,实际模块可不依赖 + +# ---------------------- 抽象基类(Julia 风格:用函数接口) ---------------------- +# Julia 无 abstract class,用文档+约定 +# 所有子类型必须实现 apply!(bc, u) + +# ---------------------- PeriodicBoundary ---------------------- +mutable struct PeriodicBoundary + cfd::Any +end + +function apply!(self::PeriodicBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist # 1-based, e.g., 3 + ied = self.cfd.domain.ied # 1-based, e.g., 43 + + # 左 ghost: u[ist - 1 - ig] = u[ied - 1 - ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ied - 1 - ig] + end + # 右 ghost: u[ied + ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ied + ig] = u[ist + ig] + end +end + +# ---------------------- DirichletBoundary ---------------------- +mutable struct DirichletBoundary + cfd::Any +end + +function apply!(self::DirichletBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + left_value = getfield_safe(self.cfd.config, :left_boundary_value, 1.0) + right_value = getfield_safe(self.cfd.config, :right_boundary_value, 2.0) + + # 左边界 + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = left_value + end + # 右边界 + for ig in 0:(nghosts-1) + u[ied + ig] = right_value + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Dirichlet边界: 左值=$left_value, 右值=$right_value") + end +end + +# ---------------------- NeumannBoundary ---------------------- +mutable struct NeumannBoundary + cfd::Any +end + +function apply!(self::NeumannBoundary, u::Vector{Float64}) + nghosts = self.cfd.domain.nghosts + ist = self.cfd.domain.ist + ied = self.cfd.domain.ied + + # 左边界零梯度:u[ist - 1 - ig] = u[ist + ig] + for ig in 0:(nghosts-1) + u[ist - 1 - ig] = u[ist + ig] + end + # 右边界零梯度:u[ied + ig] = u[ied - 1 - ig] ← 注意是 ied - 1 - ig + for ig in 0:(nghosts-1) + u[ied + ig] = u[ied - 1 - ig] + end + + # 调试信息 + if getfield_safe(self.cfd.config, :debug, false) + println(" 应用Neumann边界: 零梯度") + end +end + + +# ---------------------- BoundaryConditionFactory ---------------------- +module BoundaryConditionFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "边界条件 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + if !hasproperty(cfd, :config) + error("cfd 缺少 config 字段") + end + bc_type = cfd.config.boundary_type + if !(bc_type isa AbstractString) + error("boundary_type 必须为字符串,当前值: $bc_type") + end + + if !haskey(_REGISTRY, bc_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的边界条件: '$bc_type'。可用选项: $available") + end + + return _REGISTRY[bc_type](cfd) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("periodic", cfd -> Main.PeriodicBoundary(cfd)) +register("dirichlet", cfd -> Main.DirichletBoundary(cfd)) +register("neumann", cfd -> Main.NeumannBoundary(cfd)) + +end # module BoundaryConditionFactory + +export BoundaryConditionFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/config.jl b/example/1d-linear-convection/weno3/julia/03g/src/config.jl new file mode 100644 index 000000000..aaa4bdb52 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/config.jl @@ -0,0 +1,74 @@ +# julia/config.jl +""" +CfdConfig:与 Python config.py 完全同构 +""" +mutable struct CfdConfig + ic_type::String + recon_scheme::String + flux_type::String + rk_order::Int + wave_speed::Float64 + final_time::Float64 + dt::Float64 + boundary_type::String + left_boundary_value::Float64 + right_boundary_value::Float64 + spatial_order::Int + equation_type::String # ← 新增 + problem_type::String # ← 新增 + + function CfdConfig() + new( + "step", + "eno", + "rusanov", + #"engquist-osher", + 1, + 1.0, + 0.625, + 0.025, + "periodic", + 1.0, + 2.0, + 2, + "linear_advection", # equation_type + "linear_advection" # problem_type + ) + end +end + +""" +专用配置:重建方案(链式调用) +""" +function with_reconstruction(cfg::CfdConfig, scheme::String, order::Union{Int, Nothing}=nothing) + cfg.recon_scheme = lowercase(scheme) + + if order !== nothing + cfg.spatial_order = order + else + if startswith(cfg.recon_scheme, "weno") + cfg.spatial_order = 5 + elseif cfg.recon_scheme == "eno" + cfg.spatial_order = 3 + else + error("不支持的重建格式:$scheme(仅支持 eno/weno)") + end + end + + return cfg # 支持链式调用 +end + +""" +专用配置:边界条件(链式调用) +""" +function with_boundary(cfg::CfdConfig, bc_type::String; left_value=nothing, right_value=nothing) + cfg.boundary_type = bc_type + if left_value !== nothing + cfg.left_boundary_value = left_value + end + if right_value !== nothing + cfg.right_boundary_value = right_value + end + return cfg +end + diff --git a/example/1d-linear-convection/weno3/julia/03g/src/domain.jl b/example/1d-linear-convection/weno3/julia/03g/src/domain.jl new file mode 100644 index 000000000..eec261142 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/domain.jl @@ -0,0 +1,58 @@ +# julia/domain.jl +""" +Domain 结构体(与 domain.py 完全同构) +- 保存 config +- nghosts 计算逻辑 1:1 +- ist/ied 为 0-based(与 Python 一致) +""" + +include("mesh.jl") +include("utils.jl") + +mutable struct Domain + config::Any # 保存 config(与 Python 一致) + mesh::Mesh + nghosts::Int + ist::Int # 0-based + ied::Int # 0-based + ntcells::Int +end + +function Domain(config::Any, mesh::Mesh) + nghosts = _calc_nghosts(config) + ist = nghosts + 1 # ← 1-based 起始索引 + ied = ist + mesh.ncells # ← 1-based 结束索引(不包含) + ntcells = mesh.ncells + 2 * nghosts + Domain(config, mesh, nghosts, ist, ied, ntcells) +end + +function _calc_nghosts(config::Any) + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + order = getfield_safe(config, :spatial_order, 2) + + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + if scheme == "eno" + nghosts = order + elseif startswith(scheme, "weno") + nghosts = order ÷ 2 + 1 + else + error("未知重建格式 $(scheme),无法计算ghost层!") + end + + if nghosts <= 0 + error("计算得到的ghost层数量无效:$(nghosts)(阶数$(order),格式$(scheme))") + end + + return nghosts +end + +function is_physical_cell(domain::Domain, idx::Int) + return domain.ist <= idx < domain.ied +end + +function get_physical_indices(domain::Domain) + return domain.ist:(domain.ied - 1) +end diff --git a/example/1d-linear-convection/weno3/julia/03g/src/equations/base.jl b/example/1d-linear-convection/weno3/julia/03g/src/equations/base.jl new file mode 100644 index 000000000..1915d7e32 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/equations/base.jl @@ -0,0 +1,28 @@ +# src/equations/base.jl + +""" +抽象方程基类 +所有具体方程必须实现: +- eq_flux(eq, u) +- max_wave_speed(eq, u) +""" +abstract type Equation end + +""" +通量函数 F(u) +""" +function eq_flux(eq::Equation, u::Float64)::Float64 + error("Not implemented for $(typeof(eq))") +end + +""" +最大波速(用于 Rusanov 通量和 CFL) +""" +function max_wave_speed(eq::Equation, u::Float64)::Float64 + error("Not implemented for $(typeof(eq))") +end + +""" +方程变量数(标量方程返回 1) +""" +num_equations(eq::Equation) = 1 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/equations/equations.jl b/example/1d-linear-convection/weno3/julia/03g/src/equations/equations.jl new file mode 100644 index 000000000..de1195d45 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/equations/equations.jl @@ -0,0 +1,4 @@ +# src/equations/equations.jl + +include("base.jl") +include("linear_advection.jl") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/equations/linear_advection.jl b/example/1d-linear-convection/weno3/julia/03g/src/equations/linear_advection.jl new file mode 100644 index 000000000..3086c1ce0 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/equations/linear_advection.jl @@ -0,0 +1,16 @@ +# src/equations/linear_advection.jl + +""" +线性对流方程: ∂u/∂t + c ∂u/∂x = 0 +""" +mutable struct LinearAdvectionEquation <: Equation + wave_speed::Float64 + function LinearAdvectionEquation(config::Any) + c = getfield_safe(config, :wave_speed, 1.0) + new(c) + end +end + +eq_flux(eq::LinearAdvectionEquation, u::Float64) = eq.wave_speed * u + +max_wave_speed(eq::LinearAdvectionEquation, u::Float64) = abs(eq.wave_speed) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/flux/base.jl b/example/1d-linear-convection/weno3/julia/03g/src/flux/base.jl new file mode 100644 index 000000000..58dd6cc90 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/flux/base.jl @@ -0,0 +1,7 @@ +# src/flux/base.jl +""" +抽象通量计算器接口 +Julia 无 ABC,用文档约定: +- 所有子类型必须实现 `compute!(calc, qL, qR, flux)` +""" +abstract type InviscidFluxCalculator end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/flux/engquist_osher.jl b/example/1d-linear-convection/weno3/julia/03g/src/flux/engquist_osher.jl new file mode 100644 index 000000000..c27b81650 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/flux/engquist_osher.jl @@ -0,0 +1,26 @@ +# src/flux/engquist_osher.jl +mutable struct EngquistOsherFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function EngquistOsherFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::EngquistOsherFluxCalculator, qL::Vector{Float64}, qR::Vector{Float64}, flux::Vector{Float64}) + eq = calc.cfd.equation # ← 从 cfd 获取 Equation + for i in 1:calc.mesh.nnodes + c = eq.wave_speed # ← 假设 LinearAdvectionEquation 有 .wave_speed 字段 + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = qL[i] + u_R = qR[i] + flux[i] = cp * u_L + cm * u_R + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/flux/factory.jl b/example/1d-linear-convection/weno3/julia/03g/src/flux/factory.jl new file mode 100644 index 000000000..6ae4917db --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/flux/factory.jl @@ -0,0 +1,26 @@ +# src/flux/factory.jl + +module FluxCalculatorFactory + +# 全局注册表 +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "通量计算器 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + flux_type = cfd.config.flux_type + if !haskey(_REGISTRY, flux_type) + error("未注册的通量计算器: '$flux_type'。可用: $(keys(_REGISTRY))") + end + return _REGISTRY[flux_type](cfd) +end + +register("rusanov", cfd -> Main.RusanovFluxCalculator(cfd)) +register("engquist-osher", cfd -> Main.EngquistOsherFluxCalculator(cfd)) + +end # module FluxCalculatorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/flux/flux.jl b/example/1d-linear-convection/weno3/julia/03g/src/flux/flux.jl new file mode 100644 index 000000000..533f034c8 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/flux/flux.jl @@ -0,0 +1,10 @@ +# src/flux/flux.jl + +# 加载组件(顺序很重要!) +include("base.jl") +include("rusanov.jl") +include("engquist_osher.jl") +include("factory.jl") + +# 导出(如果你未来用模块) +# export InviscidFluxCalculator, RusanovFluxCalculator, EngquistOsherFluxCalculator, FluxCalculatorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/flux/rusanov.jl b/example/1d-linear-convection/weno3/julia/03g/src/flux/rusanov.jl new file mode 100644 index 000000000..3908c4537 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/flux/rusanov.jl @@ -0,0 +1,29 @@ +# src/flux/rusanov.jl + +mutable struct RusanovFluxCalculator <: InviscidFluxCalculator + cfd::Any + config::Any + mesh::Mesh + wave_speed::Float64 + + function RusanovFluxCalculator(cfd::Any) + config = cfd.config + mesh = cfd.domain.mesh + wave_speed = config.wave_speed + new(cfd, config, mesh, wave_speed) + end +end + +function compute!(calc::RusanovFluxCalculator, q_face_left::Vector{Float64}, q_face_right::Vector{Float64}, flux::Vector{Float64}) + eq = calc.cfd.equation + for i in 1:calc.mesh.nnodes + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = max_wave_speed(eq, u_L) + c_R = max_wave_speed(eq, u_R) + F_L = eq_flux(eq, u_L) + F_R = eq_flux(eq, u_R) + Smax = max(abs(c_L), abs(c_R)) + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/initial_condition.jl b/example/1d-linear-convection/weno3/julia/03g/src/initial_condition.jl new file mode 100644 index 000000000..1b9993aa8 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/initial_condition.jl @@ -0,0 +1,112 @@ +# julia/initial_condition.jl +""" +初始条件模块(Julia 版) +目标:与 Python initial_condition.py 行为完全一致 +""" + +# ---------------------- 抽象接口(Julia 无继承,用函数约定) ---------------------- +# 所有初始条件必须实现: +# evaluate_at(ic, x::AbstractVector{Float64}) -> Vector{Float64} +# apply(ic, solution) + +# ---------------------- StepFunctionIC ---------------------- +struct StepFunctionIC + config::Any +end + +function evaluate_at(ic::StepFunctionIC, x::AbstractVector{Float64}) + u0 = ones(Float64, length(x)) + for i in eachindex(x) + if 0.5 <= x[i] <= 1.0 + u0[i] = 2.0 + end + end + return u0 +end + +function apply(ic::StepFunctionIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- SineWaveIC ---------------------- +struct SineWaveIC + config::Any +end + +function evaluate_at(ic::SineWaveIC, x::AbstractVector{Float64}) + L = getfield_safe(ic.config, :domain_length, 2.0) + return sin.(2π * x / L) +end + +function apply(ic::SineWaveIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- GaussianPulseIC ---------------------- +struct GaussianPulseIC + config::Any +end + +function evaluate_at(ic::GaussianPulseIC, x::AbstractVector{Float64}) + center = getfield_safe(ic.config, :pulse_center, 0.5) + width = getfield_safe(ic.config, :pulse_width, 0.1) + return exp.(-((x .- center) ./ width).^2) +end + +function apply(ic::GaussianPulseIC, solution) + x = solution.domain.mesh.xcc + u0 = evaluate_at(ic, x) + _apply_to_interior(solution, u0) +end + +# ---------------------- 公共辅助函数 ---------------------- +""" +将初始场 values 应用到 solution.u 的物理区域(含 ghost 的数组) +""" +function _apply_to_interior(solution, values) + domain = solution.domain + # Python: for i in range(domain.ist, domain.ied) + # Julia: for i in domain.ist:domain.ied-1 (因为 ied 是结束索引,不包含) + for i in domain.ist:(domain.ied - 1) + j = i - domain.ist + 1 # values 是 1-based,i - ist 是 0-based → +1 + solution.u[i] = values[j] + end +end + +# ---------------------- InitialConditionFactory ---------------------- +module InitialConditionFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "初始条件 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(ic_type::String, config::Any) + if !(ic_type isa AbstractString) + error("ic_type 必须为字符串,当前值: $ic_type") + end + + if !haskey(_REGISTRY, ic_type) + available = sort(collect(keys(_REGISTRY))) + error("未注册的初始条件: '$ic_type'。可用选项: $available") + end + + return _REGISTRY[ic_type](config) +end + +# ✅ 使用 Main. 前缀引用顶层类型 +register("step", config -> Main.StepFunctionIC(config)) +register("sin", config -> Main.SineWaveIC(config)) +register("gaussian", config -> Main.GaussianPulseIC(config)) + +end # module InitialConditionFactory + +export InitialConditionFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/mesh.jl b/example/1d-linear-convection/weno3/julia/03g/src/mesh.jl new file mode 100644 index 000000000..1b0dd4bf9 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/mesh.jl @@ -0,0 +1,45 @@ +# julia/mesh.jl +""" +Mesh 结构体(与提供的 mesh.py 完全同构) +- 字段名、初始化顺序、循环逻辑完全一致 +- 不做任何泛化或优化 +""" + +mutable struct Mesh + xmin::Float64 + xmax::Float64 + ncells::Int + nnodes::Int + nx::Int + x::Vector{Float64} + xcc::Vector{Float64} + L::Float64 # 在 init_mesh 中赋值 + dx::Float64 # 在 init_mesh 中赋值 + + function Mesh() + # 与 Python __init__ 完全一致 + xmin = 0.0 + xmax = 2.0 + ncells = 40 + nnodes = ncells + 1 + nx = ncells + x = zeros(Float64, nnodes) + xcc = zeros(Float64, ncells) + + # 调用 init_mesh(模拟 Python) + L = xmax - xmin + dx = L / ncells + + # Generate node coordinates: for i in range(self.nnodes) + for i in 1:nnodes + x[i] = xmin + (i - 1) * dx # Python i → Julia i-1 + end + + # Generate cell center coordinates + for i in 1:ncells + xcc[i] = 0.5 * (x[i] + x[i+1]) + end + + new(xmin, xmax, ncells, nnodes, nx, x, xcc, L, dx) + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/plotter.jl b/example/1d-linear-convection/weno3/julia/03g/src/plotter.jl new file mode 100644 index 000000000..a77d8d688 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/plotter.jl @@ -0,0 +1,147 @@ +# julia/plotter.jl +""" +CFDPlotter 的 Julia 实现(通过 PythonCall.jl 调用 Matplotlib) +确保与 Python plotter.py 行为完全一致 +""" + +using PythonCall + +# 初始化 Python 环境(加载 matplotlib, inflect) +const plt = pyimport("matplotlib.pyplot") +const inflect = pyimport("inflect") + +mutable struct CFDPlotter + default_styles::Dict{String, Any} + p::Py +end + +function CFDPlotter() + default_styles = Dict{String, Any}( + "numerical" => Dict( + :color => "blue", + :linestyle => "-", + :marker => "o", + :markerfacecolor => "none" + ), + "analytical" => Dict( + :color => "red", + :linestyle => "--", + :marker => "", + :linewidth => 1.5 + ), + "comparison" => [ + Dict(:color => "black", :linestyle => "-", :marker => "o", :markerfacecolor => "none"), + Dict(:color => "blue", :linestyle => "--", :marker => "s", :markerfacecolor => "none"), + Dict(:color => "green", :linestyle => ":", :marker => "^", :markerfacecolor => "none") + ] + ) + p = inflect.engine() + CFDPlotter(default_styles, p) +end + +""" +轻量即时绘图(快速验证结果) +""" +function plot_quick(plotter::CFDPlotter, cfd_result::Dict; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + rk_order = cfd_result["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = cfd_result["config"]["final_time"] + order = cfd_result["config"]["order"] + scheme = uppercase(cfd_result["config"]["scheme"]) + actual_title = "1D Convection (t=$(final_time))\n$(order)th-order $(scheme) + $(rk_str)-order RK" + end + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"]; + label="Numerical ($(uppercase(cfd_result["config"]["scheme"])))", + plotter.default_styles["numerical"]..., + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + # 通用样式 + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +""" +多格式/多精度对比绘图 +""" +function plot_comparison(plotter::CFDPlotter, result_list::Vector{Dict{String, Any}}; title=nothing, show=true, save_path=nothing) + plt.figure("OneFLOW-CFD Solver", figsize=(10, 6)) + + # 自动生成标题 + actual_title = title + if actual_title === nothing + schemes = [uppercase(r["config"]["scheme"]) * string(r["config"]["order"]) for r in result_list] + rk_order = result_list[1]["config"]["rk_order"] + rk_str = plotter.p.ordinal(rk_order) + final_time = result_list[1]["config"]["final_time"] + actual_title = "1D Convection Comparison (t=$(final_time))\n$(join(schemes, ", ")) + $(rk_str)-order RK" + end + + # 绘制多个数值解 + for (i, res) in enumerate(result_list) + style = plotter.default_styles["comparison"][mod1(i, length(plotter.default_styles["comparison"]))] + label = "Numerical ($(uppercase(res["config"]["scheme"]))$(res["config"]["order"]))" + plt.plot( + res["x"], res["numerical"]; + label=label, + style..., + markersize=5, linewidth=0.5 + ) + end + + # 绘制解析解 + plt.plot( + result_list[1]["x"], result_list[1]["analytical"]; + label="Analytical", + plotter.default_styles["analytical"]... + ) + + _set_common_style(plotter, actual_title) + + if save_path !== nothing + plt.savefig(save_path, dpi=300, bbox_inches="tight") + end + if show + plt.show() + end + plt.close() +end + +function _set_common_style(plotter::CFDPlotter, title::String) + plt.title(title, fontsize=12) + plt.xlabel("x", fontsize=10) + plt.ylabel("u", fontsize=10) + plt.legend(fontsize=9) + plt.grid(true, color="gray", linestyle="--", linewidth=0.5, alpha=0.7) + plt.tight_layout() +end + +""" +快捷函数:ENO/WENO对比绘图 +""" +function plot_eno_weno_comparison(eno_result::Dict, weno_result::Dict; save_path=nothing) + plotter = CFDPlotter() + plot_comparison(plotter, [eno_result, weno_result]; save_path=save_path) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/problems/base.jl b/example/1d-linear-convection/weno3/julia/03g/src/problems/base.jl new file mode 100644 index 000000000..e29d0fbbe --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/problems/base.jl @@ -0,0 +1,22 @@ +# src/problems/base.jl + +""" +抽象问题基类 +所有具体问题必须实现: +- create_initial_condition(prob, config) +- create_boundary_condition(prob, cfd) +- exact_solution(prob, x, t) +""" +abstract type Problem end + +function create_initial_condition(prob::Problem, config::Any) + error("Not implemented for $(typeof(prob))") +end + +function create_boundary_condition(prob::Problem, cfd::Any) + error("Not implemented for $(typeof(prob))") +end + +function exact_solution(prob::Problem, x::Vector{Float64}, t::Float64)::Vector{Float64} + error("Not implemented for $(typeof(prob))") +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/problems/linear_advection.jl b/example/1d-linear-convection/weno3/julia/03g/src/problems/linear_advection.jl new file mode 100644 index 000000000..8b37d9f85 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/problems/linear_advection.jl @@ -0,0 +1,43 @@ +# src/problems/linear_advection.jl + +""" +线性对流问题:定义初始条件、边界条件、解析解 +""" +mutable struct LinearAdvectionProblem <: Problem + ic_type::String + boundary_type::String + left_value::Float64 + right_value::Float64 + domain_length::Float64 + wave_speed::Float64 # 从 config 获取,未来可从 equation 获取 + + function LinearAdvectionProblem(config::Any) + new( + getfield_safe(config, :ic_type, "step"), + getfield_safe(config, :boundary_type, "periodic"), + getfield_safe(config, :left_boundary_value, 1.0), + getfield_safe(config, :right_boundary_value, 2.0), + getfield_safe(config, :domain_length, 2.0), + getfield_safe(config, :wave_speed, 1.0) + ) + end +end + +function create_initial_condition(prob::LinearAdvectionProblem, config::Any) + return Main.InitialConditionFactory.create(prob.ic_type, config) +end + +function create_boundary_condition(prob::LinearAdvectionProblem, cfd::Any) + # 复用原有 BC 工厂(cfd 需包含 config + domain) + return Main.BoundaryConditionFactory.create(cfd) +end + +function exact_solution(prob::LinearAdvectionProblem, x::Vector{Float64}, t::Float64)::Vector{Float64} + L = prob.domain_length + c = prob.wave_speed + # 周期性平移 + x_shifted = @. (x - c * t + L) % L + # 重用 IC 的 evaluate_at + ic = Main.InitialConditionFactory.create(prob.ic_type, (ic_type=prob.ic_type,)) + return Main.evaluate_at(ic, x_shifted) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/problems/problems.jl b/example/1d-linear-convection/weno3/julia/03g/src/problems/problems.jl new file mode 100644 index 000000000..983113978 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/problems/problems.jl @@ -0,0 +1,4 @@ +# src/problems/problems.jl + +include("base.jl") +include("linear_advection.jl") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/reconstructor/eno.jl b/example/1d-linear-convection/weno3/julia/03g/src/reconstructor/eno.jl new file mode 100644 index 000000000..5bcef6aba --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/reconstructor/eno.jl @@ -0,0 +1,114 @@ +# julia/reconstructor/eno.jl +""" +ENO 重构器(与 reconstructor/eno.py 完全同构) +""" + +# ---------------------- ENO 系数初始化 ---------------------- +function _init_eno_coef!(spatial_order::Int, coef::Matrix{Float64}) + if spatial_order == 1 + coef[1, 1] = 1.0 + coef[2, 1] = 1.0 + elseif spatial_order == 2 + coef[1, 1:2] = [3.0/2.0, -1.0/2.0] + coef[2, 1:2] = [1.0/2.0, 1.0/2.0] + coef[3, 1:2] = [-1.0/2.0, 3.0/2.0] + elseif spatial_order == 3 + coef[1, 1:3] = [11.0/6.0, -7.0/6.0, 1.0/3.0] + coef[2, 1:3] = [1.0/3.0, 5.0/6.0, -1.0/6.0] + coef[3, 1:3] = [-1.0/6.0, 5.0/6.0, 1.0/3.0] + coef[4, 1:3] = [1.0/3.0, -7.0/6.0, 11.0/6.0] + elseif spatial_order == 4 + coef[1, 1:4] = [25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0] + coef[2, 1:4] = [1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0] + coef[3, 1:4] = [-1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0] + coef[4, 1:4] = [1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0] + coef[5, 1:4] = [-1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0] + elseif spatial_order == 5 + coef[1, 1:5] = [137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0] + coef[2, 1:5] = [1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0] + coef[3, 1:5] = [-1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0] + coef[4, 1:5] = [1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0] + coef[5, 1:5] = [-1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0] + coef[6, 1:5] = [1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0] + elseif spatial_order == 6 + coef[1, 1:6] = [49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0] + coef[2, 1:6] = [1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0] + coef[3, 1:6] = [-1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0] + coef[4, 1:6] = [1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0] + coef[5, 1:6] = [-1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0] + coef[6, 1:6] = [1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0] + coef[7, 1:6] = [-1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0] + elseif spatial_order == 7 + coef[1, 1:7] = [363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0] + coef[2, 1:7] = [1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0] + coef[3, 1:7] = [-1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0] + coef[4, 1:7] = [1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0] + coef[5, 1:7] = [-1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0] + coef[6, 1:7] = [1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0] + coef[7, 1:7] = [-1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0] + coef[8, 1:7] = [1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0] + else + error("ENO 系数未实现 order=$spatial_order") + end +end + +# ---------------------- ENO 重构器 ---------------------- +mutable struct EnoReconstructor + cfd::Any + config::Any + domain::Any + spatial_order::Int + ntcells::Int + lmc::Vector{Int} + coef::Matrix{Float64} + dd::Matrix{Float64} + + function EnoReconstructor(cfd::Any) + config = cfd.config + domain = cfd.domain + spatial_order = config.spatial_order + ntcells = domain.ntcells + lmc = zeros(Int, ntcells) + coef = zeros(Float64, spatial_order + 1, spatial_order) + dd = zeros(Float64, spatial_order, ntcells) + _init_eno_coef!(spatial_order, coef) + new(cfd, config, domain, spatial_order, ntcells, lmc, coef, dd) + end +end + +function reconstruct(rec::EnoReconstructor, q::Vector{Float64}, cfd::Any) + # 1. 差商计算 (dd[1,:] = q) + @views rec.dd[1, :] .= q + for m in 2:rec.spatial_order + for j in 1:(rec.ntcells - m + 1) + rec.dd[m, j] = rec.dd[m-1, j+1] - rec.dd[m-1, j] + end + end + + # 2. 选择 smoothest stencil + domain = cfd.domain + for i in (domain.ist - 1):(domain.ied) # Python: range(ist-1, ied+1) → ied+1-1 = ied + rec.lmc[i] = i + for m in 2:rec.spatial_order + if abs(rec.dd[m, rec.lmc[i] - 1]) < abs(rec.dd[m, rec.lmc[i]]) + rec.lmc[i] -= 1 + end + end + end + + # 3. 重构界面值 + solution = cfd.solution + for i in domain.ist:(domain.ied) # Python: range(ist, ied+1) → ied+1-1 = ied + j = i - domain.ist + 1 # Julia 1-based + k1 = rec.lmc[i - 1] + k2 = rec.lmc[i] + r1 = (i - 1) - k1 + 1 + r2 = i - k2 + 1 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in 1:rec.spatial_order + solution.q_face_left[j] += q[k1 + m - 1] * rec.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m - 1] * rec.coef[r2, m] + end + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/reconstructor/factory.jl b/example/1d-linear-convection/weno3/julia/03g/src/reconstructor/factory.jl new file mode 100644 index 000000000..3b3f4ca8f --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/reconstructor/factory.jl @@ -0,0 +1,50 @@ +# src/reconstructor/factory.jl + +module ReconstructorFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "重构器 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + config = cfd.config + domain = cfd.domain + scheme = lowercase(string(getfield_safe(config, :recon_scheme, ""))) + + if scheme == "" + error("必须先通过 with_reconstruction 设置重建格式!") + end + + # 处理 WENO 默认命名 + if scheme == "weno" + order = getfield_safe(config, :spatial_order, 5) + scheme = "weno$(order)" + end + + if !haskey(_REGISTRY, scheme) + available = sort(collect(keys(_REGISTRY))) + error("未注册的重构器: '$scheme'。可用选项: $available") + end + + return _REGISTRY[scheme](cfd) # ✅ 只传 cfd +end + + +# ✅ 手动注册(与你原有风格一致) +register("eno", cfd -> Main.EnoReconstructor(cfd)) +register("weno3", cfd -> Main.Weno3Reconstructor(cfd)) +register("weno5", cfd -> Main.Weno5Reconstructor(cfd)) + +# ---------------------- 辅助函数 ---------------------- +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end + +end # module ReconstructorFactory + +export ReconstructorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/reconstructor/weno3.jl b/example/1d-linear-convection/weno3/julia/03g/src/reconstructor/weno3.jl new file mode 100644 index 000000000..f5d294901 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/reconstructor/weno3.jl @@ -0,0 +1,68 @@ +# julia/reconstructor/weno3.jl +""" +WENO3 重构器(与 reconstructor/weno3.py 完全同构) +""" + +mutable struct Weno3Reconstructor + cfd::Any + function Weno3Reconstructor(cfd::Any) + new(cfd) + end +end + +function reconstruct(rec::Weno3Reconstructor, q::Vector{Float64}, cfd::Any) + domain = cfd.domain + solution = cfd.solution + _reconstruct_left_interfaces_weno3(domain, q, solution.q_face_left) + _reconstruct_right_interfaces_weno3(domain, q, solution.q_face_right) +end + +function _reconstruct_left_interfaces_weno3(domain, u, qL) + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in (domain.ist - 1):(domain.ied - 1) + j = i - (domain.ist - 1) + 1 # ← Julia 1-based: j = i - (ist-1) 对应 Python j = i - (ist-1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = _reconstruct_from_left_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_right_interfaces_weno3(domain, u, qR) + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in domain.ist:domain.ied + j = i - domain.ist + 1 # ← Julia 1-based: j = i - ist 对应 Python j = i - ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = _reconstruct_from_right_biased_stencil(v1, v2, v3) + end +end + +function _reconstruct_from_left_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -0.5*v1 + 1.5*v2 # r=1 stencil + q1 = 0.5*v2 + 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end + +function _reconstruct_from_right_biased_stencil(v1, v2, v3) + eps = 1e-6 + beta0 = (v2 - v1)^2 + beta1 = (v3 - v2)^2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 0.5*v1 + 0.5*v2 # r=1 stencil + q1 = 1.5*v2 - 0.5*v3 # r=0 stencil + return w0 * q0 + w1 * q1 +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/reconstructor/weno5.jl b/example/1d-linear-convection/weno3/julia/03g/src/reconstructor/weno5.jl new file mode 100644 index 000000000..d1b5db0f6 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/reconstructor/weno5.jl @@ -0,0 +1,80 @@ +# src/reconstructor/weno5.jl +""" +WENO5 重构器(与提供的 Python weno5.py 完全同构) +- 无字段(与 Weno3Reconstructor 一致) +- 所有逻辑 1:1 对齐 Python +""" + +mutable struct Weno5Reconstructor + cfd::Any + function Weno5Reconstructor(cfd::Any) + new(cfd) + end +end + +function reconstruct(rec::Weno5Reconstructor, q::Vector{Float64}, cfd::Any) + domain = cfd.domain + solution = cfd.solution + _reconstruct_left_interfaces_weno5(domain, q, solution.q_face_left) + _reconstruct_right_interfaces_weno5(domain, q, solution.q_face_right) +end + +function _reconstruct_left_interfaces_weno5(domain, u, qL) + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in (domain.ist - 1):(domain.ied - 1) + j = i - (domain.ist - 1) + 1 # Julia 1-based + v1, v2, v3, v4, v5 = u[i-2], u[i-1], u[i], u[i+1], u[i+2] + qL[j] = _reconstruct_from_left_biased_stencil(v1, v2, v3, v4, v5) + end +end + +function _reconstruct_right_interfaces_weno5(domain, u, qR) + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in domain.ist:domain.ied + j = i - domain.ist + 1 # Julia 1-based + v1, v2, v3, v4, v5 = u[i-2], u[i-1], u[i], u[i+1], u[i+2] + qR[j] = _reconstruct_from_right_biased_stencil(v1, v2, v3, v4, v5) + end +end + +function _reconstruct_from_left_biased_stencil(v1, v2, v3, v4, v5) + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)^2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)^2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)^2 + (1.0/4.0)*(v2 - v4)^2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)^2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)^2 + d0 = 1.0/10.0 + d1 = 3.0/5.0 + d2 = 3.0/10.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha2 = d2 / (eps + beta2)^2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = 1.0/3.0*v1 - 7.0/6.0*v2 + 11.0/6.0*v3 # r=2 + q1 = -1.0/6.0*v2 + 5.0/6.0*v3 + 1.0/3.0*v4 # r=1 + q2 = 1.0/3.0*v3 + 5.0/6.0*v4 - 1.0/6.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 +end + +function _reconstruct_from_right_biased_stencil(v1, v2, v3, v4, v5) + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)^2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)^2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)^2 + (1.0/4.0)*(v2 - v4)^2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)^2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)^2 + d0 = 3.0/10.0 + d1 = 3.0/5.0 + d2 = 1.0/10.0 + alpha0 = d0 / (eps + beta0)^2 + alpha1 = d1 / (eps + beta1)^2 + alpha2 = d2 / (eps + beta2)^2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = -1.0/6.0*v1 + 5.0/6.0*v2 + 1.0/3.0*v3 # r=2 + q1 = 1.0/3.0*v2 + 5.0/6.0*v3 - 1.0/6.0*v4 # r=1 + q2 = 11.0/6.0*v3 - 7.0/6.0*v4 + 1.0/3.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/registry.jl b/example/1d-linear-convection/weno3/julia/03g/src/registry.jl new file mode 100644 index 000000000..6acd9aaaf --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/registry.jl @@ -0,0 +1,164 @@ +""" +统一注册系统 + 通用工厂 +- ComponentRegistry: 组件注册表 +- @register_component: 装饰器宏 +- BaseFactory: 通用工厂接口 +""" + +module ComponentRegistry + +# 注册表:Dict{category => Dict{name => constructor}} +const _REGISTRIES = Dict{String, Dict{String, Function}}() +const _VERBOSE = Ref(true) + +# ---------------------- 注册核心逻辑 ---------------------- +""" + register(category::String, name::String, ctor::Function) + +注册一个组件构造函数到指定类别。 +如果已存在同名组件且不同,则发出警告。 +""" +function register(category::String, name::String, ctor::Function) + if !haskey(_REGISTRIES, category) + _REGISTRIES[category] = Dict{String, Function}() + end + + registry = _REGISTRIES[category] + if haskey(registry, name) + if registry[name] !== ctor && _VERBOSE[] + @warn "覆盖注册: $category.$name" + end + end + + registry[name] = ctor + if _VERBOSE[] + println("✅ 已注册: $category.$name -> $(nameof(ctor))") + end +end + +""" + create(category::String, name::String, args...; kwargs...) + +从注册表创建组件实例。 +""" +function create(category::String, name::String, args...; kwargs...) + if !haskey(_REGISTRIES, category) + error("❌ 未知类别: $category (可用: $(collect(keys(_REGISTRIES))))") + end + + registry = _REGISTRIES[category] + lname = lowercase(name) + if !haskey(registry, lname) + available = sort(collect(keys(registry))) + error("❌ 未找到: $category.$name (可用: $available)") + end + + return registry[lname](args...; kwargs...) +end + +""" + list_all() + +返回所有已注册组件(按类别)。 +""" +function list_all() + return Dict(cat => sort(collect(keys(reg))) for (cat, reg) in _REGISTRIES) +end + +""" + set_verbose(flag::Bool) + +开启/关闭注册提示。 +""" +function set_verbose(flag::Bool) + _VERBOSE[] = flag +end + +end # module ComponentRegistry + + +# ---------------------- 装饰器宏:@register_component ---------------------- +""" +@register_component(category, [name]) + +用法: + @register_component("boundary", "periodic") + struct PeriodicBoundary ... + +若省略 name,则使用类型名的小写形式。 +""" +macro register_component(category::String, name_expr) + error("@register_component 需在类型定义前使用,且必须在模块顶层") +end + +macro register_component(category::String) + error("@register_component(category, name) 需指定 name 或在类型后使用") +end + +# 重载:@register_component("category", "name") struct X ... end +macro register_component(category::String, name::String, ex) + if !Meta.isexpr(ex, :struct) + error("@register_component 必须作用于 struct 定义") + end + + struct_name = ex.args[2] + if Meta.isexpr(struct_name, :curly) + struct_name = struct_name.args[1] + end + + # 插入注册调用(在模块顶层) + quote + $(esc(ex)) + $(ComponentRegistry).register($(category), $(name), $(esc(struct_name))) + end +end + +# 重载:@register_component("category") struct X ... end → name = lowercase(nameof(X)) +macro register_component(category::String, ex) + if !Meta.isexpr(ex, :struct) + error("@register_component 必须作用于 struct 定义") + end + + struct_name = ex.args[2] + if Meta.isexpr(struct_name, :curly) + struct_name = struct_name.args[1] + end + + name_str = string(struct_name) |> lowercase + + quote + $(esc(ex)) + $(ComponentRegistry).register($(category), $(name_str), $(esc(struct_name))) + end +end + + +# ---------------------- 通用工厂 ---------------------- +module BaseFactory + +using ..ComponentRegistry + +""" + create_component(category::String, name::String, args...; kwargs...) + +通用工厂接口,与 Python BaseFactory.create_component 行为一致。 +""" +function create_component(category::String, name::String, args...; kwargs...) + return ComponentRegistry.create(category, name, args...; kwargs...) +end + +""" + get_available_components(category::String) + +列出某类别下所有可用组件。 +""" +function get_available_components(category::String) + all = ComponentRegistry.list_all() + return get(all, category, String[]) +end + +end # module BaseFactory + + +# 导出接口 +export ComponentRegistry, BaseFactory, @register_component \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/residual.jl b/example/1d-linear-convection/weno3/julia/03g/src/residual.jl new file mode 100644 index 000000000..11a885934 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/residual.jl @@ -0,0 +1,69 @@ +# julia/residual.jl +""" +残差计算器(与 residual.py 完全同构) +- 封装重建→通量→散度完整流程 +- 依赖 cfd 的多个字段 +""" + +include("mesh.jl") + +mutable struct ResidualCalculator + cfd::Any + config::Any + domain::Any + solution::Any + mesh::Mesh + reconstructor::Any + flux_calculator::Any + + function ResidualCalculator(cfd::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + mesh = domain.mesh + reconstructor = cfd.reconstructor + + # ✅ 内部创建 flux_calculator(对标 Python) + flux_calculator = FluxCalculatorFactory.create(cfd) + + new(cfd, config, domain, solution, mesh, reconstructor, flux_calculator) + end +end + + +""" +计算完整残差(对外唯一接口) +""" +function compute!(calc::ResidualCalculator) + _reconstruct(calc) + _compute_inviscid_flux(calc) + _compute_flux_divergence(calc) +end + +""" +私有方法:界面值重建 +""" +function _reconstruct(calc::ResidualCalculator) + reconstruct(calc.reconstructor, calc.solution.u, calc.cfd) +end + +""" +私有方法:计算无粘通量 +""" +function _compute_inviscid_flux(calc::ResidualCalculator) + compute!(calc.flux_calculator, + calc.solution.q_face_left, + calc.solution.q_face_right, + calc.solution.flux) +end + +""" +私有方法:计算通量散度(残差 = -dF/dx) +""" +function _compute_flux_divergence(calc::ResidualCalculator) + solution = calc.solution + mesh = calc.mesh + for i in 1:mesh.ncells + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / mesh.dx + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/solution.jl b/example/1d-linear-convection/weno3/julia/03g/src/solution.jl new file mode 100644 index 000000000..7044c0014 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/solution.jl @@ -0,0 +1,60 @@ +# julia/solution.jl +""" +Solution 结构体(与真实 solution.py 完全同构) +字段顺序、方法、逻辑 1:1 对齐 +""" + +include("mesh.jl") +include("domain.jl") +include("initial_condition.jl") + +mutable struct Solution + domain::Domain + q_face_left::Vector{Float64} + q_face_right::Vector{Float64} + flux::Vector{Float64} + res::Vector{Float64} + u::Vector{Float64} + un::Vector{Float64} + + function Solution(config::Any, domain::Domain) + mesh = domain.mesh + + q_face_left = zeros(Float64, mesh.nnodes) + q_face_right = zeros(Float64, mesh.nnodes) + flux = zeros(Float64, mesh.nnodes) + res = zeros(Float64, mesh.ncells) + u = zeros(Float64, domain.ntcells) + un = zeros(Float64, domain.ntcells) + + sol = new(domain, q_face_left, q_face_right, flux, res, u, un) + initialize_from_config(sol, config) + return sol + end +end + +""" +重置解数组为初始状态 +""" +function reset_solution(sol::Solution) + fill!(sol.u, 0.0) + fill!(sol.un, 0.0) +end + +""" +根据配置初始化场 +""" + +function initialize_from_config(sol::Solution, config::Any) + ic_type = getfield_safe(config, :ic_type, "step") + ic = InitialConditionFactory.create(ic_type, config) + apply(ic, sol) +end + +""" +更新旧场:un = u +""" +function update_old_field(sol::Solution) + sol.un .= sol.u # 等价于 Python 的 un[:] = u[:] +end + diff --git a/example/1d-linear-convection/weno3/julia/03g/src/solver.jl b/example/1d-linear-convection/weno3/julia/03g/src/solver.jl new file mode 100644 index 000000000..c8c874166 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/solver.jl @@ -0,0 +1,165 @@ +# julia/solver.jl +""" +CFD 求解器主类(与 solver.py 完全同构) +""" + +include("mesh.jl") +include("domain.jl") +include("solution.jl") +include("initial_condition.jl") +include("boundary.jl") +include("flux/flux.jl") +include("residual.jl") +include("time_integration/time_integration.jl") +include("reconstructor/eno.jl") +include("reconstructor/weno3.jl") +include("reconstructor/weno5.jl") +include("reconstructor/factory.jl") + +include("equations/equations.jl") +include("problems/problems.jl") + +# 导入工厂模块(必须在顶层!) +using .FluxCalculatorFactory +using .BoundaryConditionFactory +using .ReconstructorFactory + +# ---------------------- Cfd 求解器 ---------------------- +mutable struct Cfd + config::Any + domain::Domain + solution::Solution + reconstructor::Any + residual_calculator::ResidualCalculator + integrator::Any + boundary_condition::Any + result::Dict{String, Any} + + function Cfd(config::Any, mesh::Mesh) + domain = Domain(config, mesh) + full_cfd = ( + config = config, + domain = domain + ) + + solution = Solution(config, domain) + reconstructor = ReconstructorFactory.create(full_cfd) + + full_cfd = merge(full_cfd, (; solution = solution, reconstructor = reconstructor)) + + + # 1. 创建 Equation 和 Problem + equation = create_equation(config) + problem = create_problem(config) + + # 2. 初始上下文(仅包含不依赖其他组件的字段 + full_cfd = merge(full_cfd, (;equation = equation)) + full_cfd = merge(full_cfd, (;problem = problem)) + + # 3. 创建边界条件(通过 problem) + boundary_condition = create_boundary_condition(problem, full_cfd) + full_cfd = merge(full_cfd, (boundary_condition = boundary_condition,)) + + + # 4. 创建 residual_calculator(现在可访问 equation) + residual_calculator = ResidualCalculator(full_cfd) + full_cfd = merge(full_cfd, (residual_calculator = residual_calculator,)) + + # 5. 创建 integrator + integrator = TimeIntegratorFactory.create(full_cfd) + full_cfd = merge(full_cfd, (integrator = integrator,)) + + # 6. 注入完整 self + residual_calculator.cfd = full_cfd + integrator.base.cfd = full_cfd + + # 7. 初始化解(通过 problem) + ic = create_initial_condition(problem, config) + Main.apply(ic, solution) # 注意:apply 在 Main + + result = Dict{String, Any}() + new(config, domain, solution, reconstructor, residual_calculator, integrator, boundary_condition, result) + + end +end + +""" +通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界 +""" +function exact_solution(cfd::Cfd) + x = cfd.domain.mesh.xcc + T = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = @. (x - c * T + L) % L + + # ✅ 使用工厂创建初始条件(对标 Python) + ic = InitialConditionFactory.create(cfd.config.ic_type, cfd.config) + + return evaluate_at(ic, x_shifted) +end + +""" +主求解循环 +""" +function run!(cfd::Cfd) + # 应用初始边界条件并同步 old field + apply!(cfd.boundary_condition, cfd.solution.u) + update_old_field(cfd.solution) + + t = 0.0 + dt_old = cfd.config.dt + dt = dt_old + + while t < cfd.config.final_time + if t + dt > cfd.config.final_time + dt = cfd.config.final_time - t + end + #@show t, dt, maximum(cfd.solution.u), minimum(cfd.solution.u) + # 执行时间步 + step(cfd.integrator, dt) + t += dt + end + + # 恢复 dt + cfd.config.dt = dt_old + + # 整理结果 + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied-1] # Python: [ist:ied] + analytical = exact_solution(cfd) + + cfd.result = Dict( + "x" => cfd.domain.mesh.xcc, + "numerical" => u_numerical, + "analytical" => analytical, + "config" => Dict( + "scheme" => cfd.config.recon_scheme, + "order" => cfd.config.spatial_order, + "rk_order" => cfd.config.rk_order, + "final_time" => cfd.config.final_time + ) + ) + + return u_numerical +end + +# --- Equation / Problem 创建函数 --- +function create_equation(config::Any) + eq_type = config.equation_type + if eq_type == "linear_advection" + return LinearAdvectionEquation(config) + else + error("Unknown equation type: $eq_type") + end +end + +function create_problem(config::Any) + prob_type = config.problem_type + if prob_type == "linear_advection" + return LinearAdvectionProblem(config) + else + error("Unknown problem type: $prob_type") + end +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/time_integration/base.jl b/example/1d-linear-convection/weno3/julia/03g/src/time_integration/base.jl new file mode 100644 index 000000000..a5b93bc19 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/time_integration/base.jl @@ -0,0 +1,33 @@ +# src/time_integration/base.jl + +""" +公共基类逻辑(对应 Python TimeIntegratorBase) +""" + +mutable struct TimeIntegratorBase + cfd::Any + config::Any + domain::Domain + solution::Solution + residual_calculator::Any +end + +function TimeIntegratorBase(cfd::Any) + config = cfd.config + domain = cfd.domain + solution = cfd.solution + residual_calculator = cfd.residual_calculator + TimeIntegratorBase(cfd, config, domain, solution, residual_calculator) +end + +function compute_residual(integrator::TimeIntegratorBase) + compute!(integrator.residual_calculator) +end + +function apply_boundary(integrator::TimeIntegratorBase) + apply!(integrator.cfd.boundary_condition, integrator.solution.u) +end + +function map_idx(integrator::TimeIntegratorBase, i::Int) + return i - integrator.domain.ist + 1 +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/time_integration/factory.jl b/example/1d-linear-convection/weno3/julia/03g/src/time_integration/factory.jl new file mode 100644 index 000000000..271fcde5c --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/time_integration/factory.jl @@ -0,0 +1,29 @@ +# src/time_integration/factory.jl + +module TimeIntegratorFactory + +const _REGISTRY = Dict{String, Function}() + +function register(name::String, ctor::Function) + if haskey(_REGISTRY, name) + @warn "时间积分器 '$name' 已注册,将被覆盖" + end + _REGISTRY[name] = ctor +end + +function create(cfd::Any) + rk_order = cfd.config.rk_order + name = "rk$rk_order" + if !haskey(_REGISTRY, name) + available = sort(collect(keys(_REGISTRY))) + error("未注册的时间积分器: '$name'。可用选项: $available") + end + return _REGISTRY[name](cfd) +end + +# ✅ 关键:用 Main. 引用在 Main 模块中定义的类型 +register("rk1", cfd -> Main.RK1Integrator(cfd)) +register("rk2", cfd -> Main.RK2Integrator(cfd)) +register("rk3", cfd -> Main.RK3Integrator(cfd)) + +end # module TimeIntegratorFactory \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/time_integration/rk1.jl b/example/1d-linear-convection/weno3/julia/03g/src/time_integration/rk1.jl new file mode 100644 index 000000000..580db3599 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/time_integration/rk1.jl @@ -0,0 +1,19 @@ +# src/time_integration/rk1.jl + +mutable struct RK1Integrator + base::TimeIntegratorBase +end + +function RK1Integrator(cfd::Any) + RK1Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK1Integrator, dt::Float64) + compute_residual(integrator.base) + for i in integrator.base.domain.ist:(integrator.base.domain.ied - 1) + j = map_idx(integrator.base, i) + integrator.base.solution.u[i] += dt * integrator.base.solution.res[j] + end + apply_boundary(integrator.base) + update_old_field(integrator.base.solution) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/time_integration/rk2.jl b/example/1d-linear-convection/weno3/julia/03g/src/time_integration/rk2.jl new file mode 100644 index 000000000..ca2b7ab6a --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/time_integration/rk2.jl @@ -0,0 +1,30 @@ +# src/time_integration/rk2.jl + +mutable struct RK2Integrator + base::TimeIntegratorBase +end + +function RK2Integrator(cfd::Any) + RK2Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK2Integrator, dt::Float64) + base = integrator.base + compute_residual(base) + u_pred = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u_pred[i] += dt * base.solution.res[j] + end + base.solution.u .= u_pred + apply_boundary(base) + compute_residual(base) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = 0.5 * base.solution.un[i] + + 0.5 * base.solution.u[i] + + 0.5 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/time_integration/rk3.jl b/example/1d-linear-convection/weno3/julia/03g/src/time_integration/rk3.jl new file mode 100644 index 000000000..a8e07736d --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/time_integration/rk3.jl @@ -0,0 +1,44 @@ +# src/time_integration/rk3.jl + +mutable struct RK3Integrator + base::TimeIntegratorBase +end + +function RK3Integrator(cfd::Any) + RK3Integrator(TimeIntegratorBase(cfd)) +end + +function step(integrator::RK3Integrator, dt::Float64) + base = integrator.base + # Stage 1 + compute_residual(base) + u1 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u1[i] += dt * base.solution.res[j] + end + base.solution.u .= u1 + apply_boundary(base) + # Stage 2 + compute_residual(base) + u2 = copy(base.solution.u) + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + u2[i] = 0.75 * base.solution.un[i] + + 0.25 * base.solution.u[i] + + 0.25 * dt * base.solution.res[j] + end + base.solution.u .= u2 + apply_boundary(base) + # Stage 3 + compute_residual(base) + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in base.domain.ist:(base.domain.ied - 1) + j = map_idx(base, i) + base.solution.u[i] = c1 * base.solution.un[i] + + c2 * base.solution.u[i] + + c3 * dt * base.solution.res[j] + end + apply_boundary(base) + update_old_field(base.solution) +end \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/time_integration/time_integration.jl b/example/1d-linear-convection/weno3/julia/03g/src/time_integration/time_integration.jl new file mode 100644 index 000000000..4e60bfc2b --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/time_integration/time_integration.jl @@ -0,0 +1,7 @@ +# src/time_integration/time_integration.jl + +include("base.jl") +include("rk1.jl") +include("rk2.jl") +include("rk3.jl") +include("factory.jl") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/julia/03g/src/utils.jl b/example/1d-linear-convection/weno3/julia/03g/src/utils.jl new file mode 100644 index 000000000..3786cd1a5 --- /dev/null +++ b/example/1d-linear-convection/weno3/julia/03g/src/utils.jl @@ -0,0 +1,9 @@ +# src/utils.jl + +# ---------------------- 辅助函数 ---------------------- +""" +模拟 Python 的 getattr(obj, name, default) +""" +function getfield_safe(obj, name::Symbol, default) + return isdefined(obj, name) ? getfield(obj, name) : default +end diff --git a/example/1d-linear-convection/weno3/python-v1/01/boundary.py b/example/1d-linear-convection/weno3/python-v1/01/boundary.py new file mode 100644 index 000000000..3a271aa29 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01/boundary.py @@ -0,0 +1,104 @@ +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from factories.base_factory import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) + diff --git a/example/1d-linear-convection/weno3/python-v1/01/cfd_registry.py b/example/1d-linear-convection/weno3/python-v1/01/cfd_registry.py new file mode 100644 index 000000000..c12eb106c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01/cfd_registry.py @@ -0,0 +1,62 @@ +# cfd_registry.py +""" +CFD注册系统统一导入模块 +所有其他模块从这里导入注册系统 +""" + +import sys +import os + +# 添加当前目录到路径,确保能找到registry.py +current_dir = os.path.dirname(os.path.abspath(__file__)) +if current_dir not in sys.path: + sys.path.insert(0, current_dir) + +try: + # 尝试导入注册系统 + from registry import ComponentRegistry, register_component + REGISTRY_AVAILABLE = True +except ImportError: + # 如果找不到,提供简单的空实现 + print("⚠️ 警告: 未找到 registry.py,使用最小化回退实现") + + class ComponentRegistry: + """最小化的注册系统回退实现""" + _registries = {} + + @classmethod + def register(cls, category, name, component_class): + if category not in cls._registries: + cls._registries[category] = {} + cls._registries[category][name] = component_class + + @classmethod + def get(cls, category, name): + if category not in cls._registries: + raise ValueError(f"未知类别: {category}") + if name not in cls._registries[category]: + raise ValueError(f"未找到: {category}.{name}") + return cls._registries[category][name] + + @classmethod + def create(cls, category, name, *args, **kwargs): + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) + for cat, comps in cls._registries.items()} + + def register_component(category, name=None): + """简化的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + REGISTRY_AVAILABLE = False + +# 导出统一的接口 +__all__ = ['ComponentRegistry', 'register_component', 'REGISTRY_AVAILABLE'] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01/config.py b/example/1d-linear-convection/weno3/python-v1/01/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01/domain.py b/example/1d-linear-convection/weno3/python-v1/01/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01/factories/base_factory.py b/example/1d-linear-convection/weno3/python-v1/01/factories/base_factory.py new file mode 100644 index 000000000..7b8b6105e --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01/factories/base_factory.py @@ -0,0 +1,49 @@ +# factories/base_factory.py +""" +工厂基类 - 提供统一的组件创建接口 +""" + +from cfd_registry import ComponentRegistry +from typing import Any, Optional + +class BaseFactory: + """工厂基类,提供统一的组件创建方法""" + + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + """ + 统一创建组件的方法 + + Args: + category: 组件类别(如'boundary', 'flux'等) + name: 组件名称(如'periodic', 'rusanov'等) + *args, **kwargs: 传递给构造函数的参数 + + Returns: + 创建的组件实例 + + Raises: + ValueError: 如果组件不存在 + """ + name_lower = name.lower() + + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + # 获取可用组件列表 + available = ComponentRegistry.list_all().get(category, []) + + # 构建友好的错误信息 + if available: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"可用类型:{available}") + else: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"({category}类别下没有注册任何组件)") + + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + """获取指定类别的可用组件列表""" + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01/flux/__init__.py b/example/1d-linear-convection/weno3/python-v1/01/flux/__init__.py new file mode 100644 index 000000000..432a36cb0 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01/flux/__init__.py @@ -0,0 +1,7 @@ +# flux/__init__.py +from .base import InviscidFluxCalculator +from .factory import FluxCalculatorFactory + +# 确保子模块被导入以触发注册 +from . import rusanov +from . import engquist_osher \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01/flux/base.py b/example/1d-linear-convection/weno3/python-v1/01/flux/base.py new file mode 100644 index 000000000..7a5a134fc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01/flux/base.py @@ -0,0 +1,25 @@ +# flux/base.py +""" +抽象通量计算基类 +""" + +from abc import ABC, abstractmethod + +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01/flux/engquist_osher.py b/example/1d-linear-convection/weno3/python-v1/01/flux/engquist_osher.py new file mode 100644 index 000000000..34ad5f9c2 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01/flux/engquist_osher.py @@ -0,0 +1,19 @@ +# flux/engquist_osher.py +""" +Engquist-Osher 通量计算器(线性对流专用) +""" + +from cfd_registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01/flux/factory.py b/example/1d-linear-convection/weno3/python-v1/01/flux/factory.py new file mode 100644 index 000000000..c2f6f0756 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01/flux/factory.py @@ -0,0 +1,25 @@ +# flux/factory.py +""" +通量计算器专用工厂(封装注册细节,提供清晰接口) +符合你希望“将创建逻辑封装在工厂中”的设计原则 +""" + +from factories.base_factory import BaseFactory + +class FluxCalculatorFactory: + """通量计算器工厂:隐藏注册类别和组件名称等细节""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD 主对象,包含 config.flux_type 等配置 + + Returns: + 实现 InviscidFluxCalculator 接口的通量计算器实例 + """ + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01/flux/rusanov.py b/example/1d-linear-convection/weno3/python-v1/01/flux/rusanov.py new file mode 100644 index 000000000..62dd207ca --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01/flux/rusanov.py @@ -0,0 +1,21 @@ +# flux/rusanov.py +""" +Rusanov(Lax-Friedrichs)通量计算器 +""" + +from cfd_registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01/initial_condition.py b/example/1d-linear-convection/weno3/python-v1/01/initial_condition.py new file mode 100644 index 000000000..963e9cdde --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01/initial_condition.py @@ -0,0 +1,107 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def exact_solution(self, cfd): + """ + 默认解析解:线性对流 u(x, t) = u0(x - c * t) + 子类可重写以支持更复杂物理 + """ + x = cfd.domain.mesh.xcc + t = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * t + L) % L + return self.evaluate_at(x_shifted) + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from factories.base_factory import BaseFactory + return BaseFactory.create_component('initial_condition', config.ic_type, config) + + @classmethod + def get_exact_solution(cls, cfd): + return cls.create(cfd.config).exact_solution(cfd) # 一行! + diff --git a/example/1d-linear-convection/weno3/python-v1/01/mesh.py b/example/1d-linear-convection/weno3/python-v1/01/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01/plotter.py b/example/1d-linear-convection/weno3/python-v1/01/plotter.py new file mode 100644 index 000000000..e1f99ae25 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01/plotter.py @@ -0,0 +1,109 @@ +# plotter.py + +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01/problem.py b/example/1d-linear-convection/weno3/python-v1/01/problem.py new file mode 100644 index 000000000..53719f265 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01/problem.py @@ -0,0 +1,38 @@ +# problem.py +""" +问题定义模块:每个 Problem 子类代表一个完整测试用例 +包含初始条件、解析解(可选)、物理方程等 +""" + +from abc import ABC, abstractmethod +from initial_condition import InitialConditionFactory + + +class Problem(ABC): + """ + 抽象问题基类 + 每个具体问题(如线性对流、Sod 激波管)应继承此类 + """ + def __init__(self, config): + self.config = config + + @abstractmethod + def initial_condition(self): + """ + 返回 InitialCondition 实例 + """ + pass + + def exact_solution(self, cfd): + """ + 可选:返回解析解(数值数组) + 若无解析解,可抛出 NotImplementedError 或返回 None + """ + x = cfd.domain.mesh.xcc + raise NotImplementedError( + f"Problem '{self.__class__.__name__}' does not provide an exact solution." + ) + + @property + def name(self): + return self.__class__.__name__ \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01/problems/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/01/problems/linear_advection.py new file mode 100644 index 000000000..ea1e2df20 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01/problems/linear_advection.py @@ -0,0 +1,27 @@ +# problems/linear_advection.py +""" +线性对流问题:支持任意初始条件 + 周期平移解析解 +""" + +import numpy as np +from problem import Problem +from initial_condition import InitialConditionFactory + + +class LinearAdvectionProblem(Problem): + """ + 线性对流问题 u_t + c u_x = 0 + 解析解:u(x, t) = u0(x - c * t) + """ + + def initial_condition(self): + return InitialConditionFactory.create(self.config) + + def exact_solution(self, cfd): + ic = self.initial_condition() + x = cfd.domain.mesh.xcc + t = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + x_shifted = (x - c * t + L) % L + return ic.evaluate_at(x_shifted) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python-v1/01/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01/reconstructor/base.py b/example/1d-linear-convection/weno3/python-v1/01/reconstructor/base.py new file mode 100644 index 000000000..094b47120 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def compute_face_values(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01/reconstructor/eno.py b/example/1d-linear-convection/weno3/python-v1/01/reconstructor/eno.py new file mode 100644 index 000000000..8fde43334 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def compute_face_values(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01/reconstructor/factory.py b/example/1d-linear-convection/weno3/python-v1/01/reconstructor/factory.py new file mode 100644 index 000000000..efa4a1446 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from cfd_registry import ComponentRegistry + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from factories.base_factory import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python-v1/01/reconstructor/weno3.py new file mode 100644 index 000000000..929061c13 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01/reconstructor/weno3.py @@ -0,0 +1,59 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/python-v1/01/registry.py b/example/1d-linear-convection/weno3/python-v1/01/registry.py new file mode 100644 index 000000000..61be90500 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01/registry.py @@ -0,0 +1,75 @@ +# registry.py (改进版) +""" +CFD组件注册系统 - 简洁高效版 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表""" + + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True # 控制是否打印注册信息 + + @classmethod + def set_verbose(cls, verbose: bool): + """设置是否打印注册信息""" + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + # 检查是否已注册 + if name in cls._registries[category]: + if cls._registries[category][name] == component_class: + # 相同类重复注册,静默跳过 + return + elif cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取组件类""" + if category not in cls._registries: + available = list(cls._registries.keys()) + raise ValueError(f"❌ 未知类别: {category} (可用类别: {available})") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + @classmethod + def get_count(cls) -> int: + """获取注册组件总数""" + return sum(len(components) for components in cls._registries.values()) + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01/residual.py b/example/1d-linear-convection/weno3/python-v1/01/residual.py new file mode 100644 index 000000000..e0f96cd3c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux.factory import FluxCalculatorFactory +from reconstructor import ReconstructorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + + self.reconstructor = ReconstructorFactory.create(self.config, self.domain) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._compute_face_values() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _compute_face_values(self): + """私有方法:界面值重建""" + self.reconstructor.compute_face_values(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01/result.py b/example/1d-linear-convection/weno3/python-v1/01/result.py new file mode 100644 index 000000000..acd257b0d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01/result.py @@ -0,0 +1,21 @@ +# result.py + +from initial_condition import InitialConditionFactory + +class ResultAssembler: + @staticmethod + def assemble(cfd: 'Cfd') -> dict: + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied].copy() + analytical = InitialConditionFactory.get_exact_solution(cfd) + + return { + "x": cfd.domain.mesh.xcc, + "numerical": u_numerical, + "analytical": analytical, + "config": { + "scheme": cfd.config.recon_scheme, + "order": cfd.config.spatial_order, + "rk_order": cfd.config.rk_order, + "final_time": cfd.config.final_time + } + } \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01/run_eno_weno.py b/example/1d-linear-convection/weno3/python-v1/01/run_eno_weno.py new file mode 100644 index 000000000..25efa62fb --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01/run_eno_weno.py @@ -0,0 +1,52 @@ +# run_eno_weno.py + +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +# ✅ 新增:导入 Problem +from problems.linear_advection import LinearAdvectionProblem + + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行 ENO3 求解 + print("Running ENO3 solver...") + config_eno3 = CfdConfig() + config_eno3.with_reconstruction("eno", 3) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno3 = LinearAdvectionProblem(config_eno3) + cfd_eno3 = Cfd(problem_eno3, mesh) # ← 注入 problem + cfd_eno3.run() + + # 3. 配置并运行 WENO3 求解 + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno3 = LinearAdvectionProblem(config_weno3) + cfd_weno3 = Cfd(problem_weno3, mesh) # ← 注入 problem + cfd_weno3.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" + ) + + +if __name__ == "__main__": + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01/solution.py b/example/1d-linear-convection/weno3/python-v1/01/solution.py new file mode 100644 index 000000000..3d4cba569 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01/solver.py b/example/1d-linear-convection/weno3/python-v1/01/solver.py new file mode 100644 index 000000000..32ffb15bc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01/solver.py @@ -0,0 +1,44 @@ +from boundary import BoundaryConditionFactory +from initial_condition import InitialConditionFactory +from time_integration import TimeIntegrator,TimeIntegratorFactory +from mesh import Mesh +from domain import Domain +from solution import Solution +from config import CfdConfig +from result import ResultAssembler +from problem import Problem + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, problem: Problem, mesh): + self.problem = problem # ← 核心:持有 Problem 实例 + self.config = problem.config # ← config 来自 problem + self.domain = Domain(problem.config, mesh) + self.solution = Solution(problem.config, self.domain) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + self.integrator.step(dt) + t += dt + config.dt = dt_old + + self.result = ResultAssembler.assemble(self) + return self.result["numerical"] + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01/time_integration.py b/example/1d-linear-convection/weno3/python-v1/01/time_integration.py new file mode 100644 index 000000000..51b2b20c1 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01/time_integration.py @@ -0,0 +1,126 @@ +# time_integration.py + +""" +时间推进器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component +from residual import ResidualCalculator + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = ResidualCalculator(cfd) + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(使用装饰器注册) ---------------------- + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class TimeIntegratorFactory: + """时间推进器工厂""" + + @staticmethod + def create(cfd) -> 'TimeIntegrator': + """ + 创建时间推进器实例 + + Args: + cfd: CFD对象 + + Returns: + 时间推进器实例 + """ + from factories.base_factory import BaseFactory + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01a/boundary.py b/example/1d-linear-convection/weno3/python-v1/01a/boundary.py new file mode 100644 index 000000000..3a271aa29 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01a/boundary.py @@ -0,0 +1,104 @@ +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from factories.base_factory import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) + diff --git a/example/1d-linear-convection/weno3/python-v1/01a/cfd_registry.py b/example/1d-linear-convection/weno3/python-v1/01a/cfd_registry.py new file mode 100644 index 000000000..c12eb106c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01a/cfd_registry.py @@ -0,0 +1,62 @@ +# cfd_registry.py +""" +CFD注册系统统一导入模块 +所有其他模块从这里导入注册系统 +""" + +import sys +import os + +# 添加当前目录到路径,确保能找到registry.py +current_dir = os.path.dirname(os.path.abspath(__file__)) +if current_dir not in sys.path: + sys.path.insert(0, current_dir) + +try: + # 尝试导入注册系统 + from registry import ComponentRegistry, register_component + REGISTRY_AVAILABLE = True +except ImportError: + # 如果找不到,提供简单的空实现 + print("⚠️ 警告: 未找到 registry.py,使用最小化回退实现") + + class ComponentRegistry: + """最小化的注册系统回退实现""" + _registries = {} + + @classmethod + def register(cls, category, name, component_class): + if category not in cls._registries: + cls._registries[category] = {} + cls._registries[category][name] = component_class + + @classmethod + def get(cls, category, name): + if category not in cls._registries: + raise ValueError(f"未知类别: {category}") + if name not in cls._registries[category]: + raise ValueError(f"未找到: {category}.{name}") + return cls._registries[category][name] + + @classmethod + def create(cls, category, name, *args, **kwargs): + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) + for cat, comps in cls._registries.items()} + + def register_component(category, name=None): + """简化的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + REGISTRY_AVAILABLE = False + +# 导出统一的接口 +__all__ = ['ComponentRegistry', 'register_component', 'REGISTRY_AVAILABLE'] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01a/config.py b/example/1d-linear-convection/weno3/python-v1/01a/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01a/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01a/domain.py b/example/1d-linear-convection/weno3/python-v1/01a/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01a/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01a/factories/base_factory.py b/example/1d-linear-convection/weno3/python-v1/01a/factories/base_factory.py new file mode 100644 index 000000000..7b8b6105e --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01a/factories/base_factory.py @@ -0,0 +1,49 @@ +# factories/base_factory.py +""" +工厂基类 - 提供统一的组件创建接口 +""" + +from cfd_registry import ComponentRegistry +from typing import Any, Optional + +class BaseFactory: + """工厂基类,提供统一的组件创建方法""" + + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + """ + 统一创建组件的方法 + + Args: + category: 组件类别(如'boundary', 'flux'等) + name: 组件名称(如'periodic', 'rusanov'等) + *args, **kwargs: 传递给构造函数的参数 + + Returns: + 创建的组件实例 + + Raises: + ValueError: 如果组件不存在 + """ + name_lower = name.lower() + + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + # 获取可用组件列表 + available = ComponentRegistry.list_all().get(category, []) + + # 构建友好的错误信息 + if available: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"可用类型:{available}") + else: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"({category}类别下没有注册任何组件)") + + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + """获取指定类别的可用组件列表""" + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01a/flux/__init__.py b/example/1d-linear-convection/weno3/python-v1/01a/flux/__init__.py new file mode 100644 index 000000000..432a36cb0 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01a/flux/__init__.py @@ -0,0 +1,7 @@ +# flux/__init__.py +from .base import InviscidFluxCalculator +from .factory import FluxCalculatorFactory + +# 确保子模块被导入以触发注册 +from . import rusanov +from . import engquist_osher \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01a/flux/base.py b/example/1d-linear-convection/weno3/python-v1/01a/flux/base.py new file mode 100644 index 000000000..7a5a134fc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01a/flux/base.py @@ -0,0 +1,25 @@ +# flux/base.py +""" +抽象通量计算基类 +""" + +from abc import ABC, abstractmethod + +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01a/flux/engquist_osher.py b/example/1d-linear-convection/weno3/python-v1/01a/flux/engquist_osher.py new file mode 100644 index 000000000..34ad5f9c2 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01a/flux/engquist_osher.py @@ -0,0 +1,19 @@ +# flux/engquist_osher.py +""" +Engquist-Osher 通量计算器(线性对流专用) +""" + +from cfd_registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01a/flux/factory.py b/example/1d-linear-convection/weno3/python-v1/01a/flux/factory.py new file mode 100644 index 000000000..c2f6f0756 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01a/flux/factory.py @@ -0,0 +1,25 @@ +# flux/factory.py +""" +通量计算器专用工厂(封装注册细节,提供清晰接口) +符合你希望“将创建逻辑封装在工厂中”的设计原则 +""" + +from factories.base_factory import BaseFactory + +class FluxCalculatorFactory: + """通量计算器工厂:隐藏注册类别和组件名称等细节""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD 主对象,包含 config.flux_type 等配置 + + Returns: + 实现 InviscidFluxCalculator 接口的通量计算器实例 + """ + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01a/flux/rusanov.py b/example/1d-linear-convection/weno3/python-v1/01a/flux/rusanov.py new file mode 100644 index 000000000..62dd207ca --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01a/flux/rusanov.py @@ -0,0 +1,21 @@ +# flux/rusanov.py +""" +Rusanov(Lax-Friedrichs)通量计算器 +""" + +from cfd_registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01a/initial_condition.py b/example/1d-linear-convection/weno3/python-v1/01a/initial_condition.py new file mode 100644 index 000000000..963e9cdde --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01a/initial_condition.py @@ -0,0 +1,107 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def exact_solution(self, cfd): + """ + 默认解析解:线性对流 u(x, t) = u0(x - c * t) + 子类可重写以支持更复杂物理 + """ + x = cfd.domain.mesh.xcc + t = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * t + L) % L + return self.evaluate_at(x_shifted) + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from factories.base_factory import BaseFactory + return BaseFactory.create_component('initial_condition', config.ic_type, config) + + @classmethod + def get_exact_solution(cls, cfd): + return cls.create(cfd.config).exact_solution(cfd) # 一行! + diff --git a/example/1d-linear-convection/weno3/python-v1/01a/mesh.py b/example/1d-linear-convection/weno3/python-v1/01a/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01a/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01a/plotter.py b/example/1d-linear-convection/weno3/python-v1/01a/plotter.py new file mode 100644 index 000000000..e1f99ae25 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01a/plotter.py @@ -0,0 +1,109 @@ +# plotter.py + +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01a/problem.py b/example/1d-linear-convection/weno3/python-v1/01a/problem.py new file mode 100644 index 000000000..53719f265 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01a/problem.py @@ -0,0 +1,38 @@ +# problem.py +""" +问题定义模块:每个 Problem 子类代表一个完整测试用例 +包含初始条件、解析解(可选)、物理方程等 +""" + +from abc import ABC, abstractmethod +from initial_condition import InitialConditionFactory + + +class Problem(ABC): + """ + 抽象问题基类 + 每个具体问题(如线性对流、Sod 激波管)应继承此类 + """ + def __init__(self, config): + self.config = config + + @abstractmethod + def initial_condition(self): + """ + 返回 InitialCondition 实例 + """ + pass + + def exact_solution(self, cfd): + """ + 可选:返回解析解(数值数组) + 若无解析解,可抛出 NotImplementedError 或返回 None + """ + x = cfd.domain.mesh.xcc + raise NotImplementedError( + f"Problem '{self.__class__.__name__}' does not provide an exact solution." + ) + + @property + def name(self): + return self.__class__.__name__ \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01a/problems/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/01a/problems/linear_advection.py new file mode 100644 index 000000000..ea1e2df20 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01a/problems/linear_advection.py @@ -0,0 +1,27 @@ +# problems/linear_advection.py +""" +线性对流问题:支持任意初始条件 + 周期平移解析解 +""" + +import numpy as np +from problem import Problem +from initial_condition import InitialConditionFactory + + +class LinearAdvectionProblem(Problem): + """ + 线性对流问题 u_t + c u_x = 0 + 解析解:u(x, t) = u0(x - c * t) + """ + + def initial_condition(self): + return InitialConditionFactory.create(self.config) + + def exact_solution(self, cfd): + ic = self.initial_condition() + x = cfd.domain.mesh.xcc + t = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + x_shifted = (x - c * t + L) % L + return ic.evaluate_at(x_shifted) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01a/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python-v1/01a/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01a/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01a/reconstructor/base.py b/example/1d-linear-convection/weno3/python-v1/01a/reconstructor/base.py new file mode 100644 index 000000000..094b47120 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01a/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def compute_face_values(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01a/reconstructor/eno.py b/example/1d-linear-convection/weno3/python-v1/01a/reconstructor/eno.py new file mode 100644 index 000000000..8fde43334 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01a/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def compute_face_values(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01a/reconstructor/factory.py b/example/1d-linear-convection/weno3/python-v1/01a/reconstructor/factory.py new file mode 100644 index 000000000..efa4a1446 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01a/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from cfd_registry import ComponentRegistry + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from factories.base_factory import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01a/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python-v1/01a/reconstructor/weno3.py new file mode 100644 index 000000000..929061c13 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01a/reconstructor/weno3.py @@ -0,0 +1,59 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/python-v1/01a/registry.py b/example/1d-linear-convection/weno3/python-v1/01a/registry.py new file mode 100644 index 000000000..61be90500 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01a/registry.py @@ -0,0 +1,75 @@ +# registry.py (改进版) +""" +CFD组件注册系统 - 简洁高效版 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表""" + + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True # 控制是否打印注册信息 + + @classmethod + def set_verbose(cls, verbose: bool): + """设置是否打印注册信息""" + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + # 检查是否已注册 + if name in cls._registries[category]: + if cls._registries[category][name] == component_class: + # 相同类重复注册,静默跳过 + return + elif cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取组件类""" + if category not in cls._registries: + available = list(cls._registries.keys()) + raise ValueError(f"❌ 未知类别: {category} (可用类别: {available})") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + @classmethod + def get_count(cls) -> int: + """获取注册组件总数""" + return sum(len(components) for components in cls._registries.values()) + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01a/residual.py b/example/1d-linear-convection/weno3/python-v1/01a/residual.py new file mode 100644 index 000000000..e0f96cd3c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01a/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux.factory import FluxCalculatorFactory +from reconstructor import ReconstructorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + + self.reconstructor = ReconstructorFactory.create(self.config, self.domain) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._compute_face_values() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _compute_face_values(self): + """私有方法:界面值重建""" + self.reconstructor.compute_face_values(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01a/result.py b/example/1d-linear-convection/weno3/python-v1/01a/result.py new file mode 100644 index 000000000..acd257b0d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01a/result.py @@ -0,0 +1,21 @@ +# result.py + +from initial_condition import InitialConditionFactory + +class ResultAssembler: + @staticmethod + def assemble(cfd: 'Cfd') -> dict: + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied].copy() + analytical = InitialConditionFactory.get_exact_solution(cfd) + + return { + "x": cfd.domain.mesh.xcc, + "numerical": u_numerical, + "analytical": analytical, + "config": { + "scheme": cfd.config.recon_scheme, + "order": cfd.config.spatial_order, + "rk_order": cfd.config.rk_order, + "final_time": cfd.config.final_time + } + } \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01a/run_eno_weno.py b/example/1d-linear-convection/weno3/python-v1/01a/run_eno_weno.py new file mode 100644 index 000000000..25efa62fb --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01a/run_eno_weno.py @@ -0,0 +1,52 @@ +# run_eno_weno.py + +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +# ✅ 新增:导入 Problem +from problems.linear_advection import LinearAdvectionProblem + + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行 ENO3 求解 + print("Running ENO3 solver...") + config_eno3 = CfdConfig() + config_eno3.with_reconstruction("eno", 3) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno3 = LinearAdvectionProblem(config_eno3) + cfd_eno3 = Cfd(problem_eno3, mesh) # ← 注入 problem + cfd_eno3.run() + + # 3. 配置并运行 WENO3 求解 + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno3 = LinearAdvectionProblem(config_weno3) + cfd_weno3 = Cfd(problem_weno3, mesh) # ← 注入 problem + cfd_weno3.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" + ) + + +if __name__ == "__main__": + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01a/solution.py b/example/1d-linear-convection/weno3/python-v1/01a/solution.py new file mode 100644 index 000000000..a0def7c3a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01a/solution.py @@ -0,0 +1,38 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01a/solver.py b/example/1d-linear-convection/weno3/python-v1/01a/solver.py new file mode 100644 index 000000000..bac8aeb33 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01a/solver.py @@ -0,0 +1,42 @@ +from boundary import BoundaryConditionFactory +from initial_condition import InitialConditionFactory +from time_integration import TimeIntegrator,TimeIntegratorFactory +from mesh import Mesh +from domain import Domain +from solution import Solution +from config import CfdConfig +from result import ResultAssembler +from problem import Problem + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, problem: Problem, mesh): + self.problem = problem # ← 核心:持有 Problem 实例 + self.config = problem.config # ← config 来自 problem + self.domain = Domain(problem.config, mesh) + self.solution = Solution(problem.config, self.domain) + self.integrator = TimeIntegratorFactory.create(self) + #self.boundary_condition = BoundaryConditionFactory.create(self) + + def run(self): + self.integrator.init_total_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + self.integrator.step(dt) + t += dt + config.dt = dt_old + + self.result = ResultAssembler.assemble(self) + return self.result["numerical"] + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01a/time_integration.py b/example/1d-linear-convection/weno3/python-v1/01a/time_integration.py new file mode 100644 index 000000000..a283a710d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01a/time_integration.py @@ -0,0 +1,132 @@ +# time_integration.py + +""" +时间推进器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component +from residual import ResidualCalculator +from boundary import BoundaryConditionFactory + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = ResidualCalculator(cfd) + self.boundary_condition = BoundaryConditionFactory.create(cfd) + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + def init_total_field(self): + self.solution.initialize_from_config(self.config) + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(使用装饰器注册) ---------------------- + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class TimeIntegratorFactory: + """时间推进器工厂""" + + @staticmethod + def create(cfd) -> 'TimeIntegrator': + """ + 创建时间推进器实例 + + Args: + cfd: CFD对象 + + Returns: + 时间推进器实例 + """ + from factories.base_factory import BaseFactory + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01b/boundary.py b/example/1d-linear-convection/weno3/python-v1/01b/boundary.py new file mode 100644 index 000000000..3a271aa29 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01b/boundary.py @@ -0,0 +1,104 @@ +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from factories.base_factory import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) + diff --git a/example/1d-linear-convection/weno3/python-v1/01b/cfd_registry.py b/example/1d-linear-convection/weno3/python-v1/01b/cfd_registry.py new file mode 100644 index 000000000..c12eb106c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01b/cfd_registry.py @@ -0,0 +1,62 @@ +# cfd_registry.py +""" +CFD注册系统统一导入模块 +所有其他模块从这里导入注册系统 +""" + +import sys +import os + +# 添加当前目录到路径,确保能找到registry.py +current_dir = os.path.dirname(os.path.abspath(__file__)) +if current_dir not in sys.path: + sys.path.insert(0, current_dir) + +try: + # 尝试导入注册系统 + from registry import ComponentRegistry, register_component + REGISTRY_AVAILABLE = True +except ImportError: + # 如果找不到,提供简单的空实现 + print("⚠️ 警告: 未找到 registry.py,使用最小化回退实现") + + class ComponentRegistry: + """最小化的注册系统回退实现""" + _registries = {} + + @classmethod + def register(cls, category, name, component_class): + if category not in cls._registries: + cls._registries[category] = {} + cls._registries[category][name] = component_class + + @classmethod + def get(cls, category, name): + if category not in cls._registries: + raise ValueError(f"未知类别: {category}") + if name not in cls._registries[category]: + raise ValueError(f"未找到: {category}.{name}") + return cls._registries[category][name] + + @classmethod + def create(cls, category, name, *args, **kwargs): + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) + for cat, comps in cls._registries.items()} + + def register_component(category, name=None): + """简化的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + REGISTRY_AVAILABLE = False + +# 导出统一的接口 +__all__ = ['ComponentRegistry', 'register_component', 'REGISTRY_AVAILABLE'] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01b/config.py b/example/1d-linear-convection/weno3/python-v1/01b/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01b/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01b/domain.py b/example/1d-linear-convection/weno3/python-v1/01b/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01b/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01b/factories/base_factory.py b/example/1d-linear-convection/weno3/python-v1/01b/factories/base_factory.py new file mode 100644 index 000000000..7b8b6105e --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01b/factories/base_factory.py @@ -0,0 +1,49 @@ +# factories/base_factory.py +""" +工厂基类 - 提供统一的组件创建接口 +""" + +from cfd_registry import ComponentRegistry +from typing import Any, Optional + +class BaseFactory: + """工厂基类,提供统一的组件创建方法""" + + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + """ + 统一创建组件的方法 + + Args: + category: 组件类别(如'boundary', 'flux'等) + name: 组件名称(如'periodic', 'rusanov'等) + *args, **kwargs: 传递给构造函数的参数 + + Returns: + 创建的组件实例 + + Raises: + ValueError: 如果组件不存在 + """ + name_lower = name.lower() + + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + # 获取可用组件列表 + available = ComponentRegistry.list_all().get(category, []) + + # 构建友好的错误信息 + if available: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"可用类型:{available}") + else: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"({category}类别下没有注册任何组件)") + + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + """获取指定类别的可用组件列表""" + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01b/flux/__init__.py b/example/1d-linear-convection/weno3/python-v1/01b/flux/__init__.py new file mode 100644 index 000000000..432a36cb0 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01b/flux/__init__.py @@ -0,0 +1,7 @@ +# flux/__init__.py +from .base import InviscidFluxCalculator +from .factory import FluxCalculatorFactory + +# 确保子模块被导入以触发注册 +from . import rusanov +from . import engquist_osher \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01b/flux/base.py b/example/1d-linear-convection/weno3/python-v1/01b/flux/base.py new file mode 100644 index 000000000..7a5a134fc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01b/flux/base.py @@ -0,0 +1,25 @@ +# flux/base.py +""" +抽象通量计算基类 +""" + +from abc import ABC, abstractmethod + +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01b/flux/engquist_osher.py b/example/1d-linear-convection/weno3/python-v1/01b/flux/engquist_osher.py new file mode 100644 index 000000000..34ad5f9c2 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01b/flux/engquist_osher.py @@ -0,0 +1,19 @@ +# flux/engquist_osher.py +""" +Engquist-Osher 通量计算器(线性对流专用) +""" + +from cfd_registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01b/flux/factory.py b/example/1d-linear-convection/weno3/python-v1/01b/flux/factory.py new file mode 100644 index 000000000..c2f6f0756 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01b/flux/factory.py @@ -0,0 +1,25 @@ +# flux/factory.py +""" +通量计算器专用工厂(封装注册细节,提供清晰接口) +符合你希望“将创建逻辑封装在工厂中”的设计原则 +""" + +from factories.base_factory import BaseFactory + +class FluxCalculatorFactory: + """通量计算器工厂:隐藏注册类别和组件名称等细节""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD 主对象,包含 config.flux_type 等配置 + + Returns: + 实现 InviscidFluxCalculator 接口的通量计算器实例 + """ + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01b/flux/rusanov.py b/example/1d-linear-convection/weno3/python-v1/01b/flux/rusanov.py new file mode 100644 index 000000000..62dd207ca --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01b/flux/rusanov.py @@ -0,0 +1,21 @@ +# flux/rusanov.py +""" +Rusanov(Lax-Friedrichs)通量计算器 +""" + +from cfd_registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01b/initial_condition.py b/example/1d-linear-convection/weno3/python-v1/01b/initial_condition.py new file mode 100644 index 000000000..963e9cdde --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01b/initial_condition.py @@ -0,0 +1,107 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def exact_solution(self, cfd): + """ + 默认解析解:线性对流 u(x, t) = u0(x - c * t) + 子类可重写以支持更复杂物理 + """ + x = cfd.domain.mesh.xcc + t = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * t + L) % L + return self.evaluate_at(x_shifted) + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from factories.base_factory import BaseFactory + return BaseFactory.create_component('initial_condition', config.ic_type, config) + + @classmethod + def get_exact_solution(cls, cfd): + return cls.create(cfd.config).exact_solution(cfd) # 一行! + diff --git a/example/1d-linear-convection/weno3/python-v1/01b/mesh.py b/example/1d-linear-convection/weno3/python-v1/01b/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01b/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01b/plotter.py b/example/1d-linear-convection/weno3/python-v1/01b/plotter.py new file mode 100644 index 000000000..e1f99ae25 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01b/plotter.py @@ -0,0 +1,109 @@ +# plotter.py + +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01b/problem.py b/example/1d-linear-convection/weno3/python-v1/01b/problem.py new file mode 100644 index 000000000..53719f265 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01b/problem.py @@ -0,0 +1,38 @@ +# problem.py +""" +问题定义模块:每个 Problem 子类代表一个完整测试用例 +包含初始条件、解析解(可选)、物理方程等 +""" + +from abc import ABC, abstractmethod +from initial_condition import InitialConditionFactory + + +class Problem(ABC): + """ + 抽象问题基类 + 每个具体问题(如线性对流、Sod 激波管)应继承此类 + """ + def __init__(self, config): + self.config = config + + @abstractmethod + def initial_condition(self): + """ + 返回 InitialCondition 实例 + """ + pass + + def exact_solution(self, cfd): + """ + 可选:返回解析解(数值数组) + 若无解析解,可抛出 NotImplementedError 或返回 None + """ + x = cfd.domain.mesh.xcc + raise NotImplementedError( + f"Problem '{self.__class__.__name__}' does not provide an exact solution." + ) + + @property + def name(self): + return self.__class__.__name__ \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01b/problems/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/01b/problems/linear_advection.py new file mode 100644 index 000000000..ea1e2df20 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01b/problems/linear_advection.py @@ -0,0 +1,27 @@ +# problems/linear_advection.py +""" +线性对流问题:支持任意初始条件 + 周期平移解析解 +""" + +import numpy as np +from problem import Problem +from initial_condition import InitialConditionFactory + + +class LinearAdvectionProblem(Problem): + """ + 线性对流问题 u_t + c u_x = 0 + 解析解:u(x, t) = u0(x - c * t) + """ + + def initial_condition(self): + return InitialConditionFactory.create(self.config) + + def exact_solution(self, cfd): + ic = self.initial_condition() + x = cfd.domain.mesh.xcc + t = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + x_shifted = (x - c * t + L) % L + return ic.evaluate_at(x_shifted) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01b/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python-v1/01b/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01b/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01b/reconstructor/base.py b/example/1d-linear-convection/weno3/python-v1/01b/reconstructor/base.py new file mode 100644 index 000000000..094b47120 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01b/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def compute_face_values(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01b/reconstructor/eno.py b/example/1d-linear-convection/weno3/python-v1/01b/reconstructor/eno.py new file mode 100644 index 000000000..8fde43334 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01b/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def compute_face_values(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01b/reconstructor/factory.py b/example/1d-linear-convection/weno3/python-v1/01b/reconstructor/factory.py new file mode 100644 index 000000000..efa4a1446 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01b/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from cfd_registry import ComponentRegistry + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from factories.base_factory import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01b/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python-v1/01b/reconstructor/weno3.py new file mode 100644 index 000000000..929061c13 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01b/reconstructor/weno3.py @@ -0,0 +1,59 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/python-v1/01b/registry.py b/example/1d-linear-convection/weno3/python-v1/01b/registry.py new file mode 100644 index 000000000..61be90500 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01b/registry.py @@ -0,0 +1,75 @@ +# registry.py (改进版) +""" +CFD组件注册系统 - 简洁高效版 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表""" + + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True # 控制是否打印注册信息 + + @classmethod + def set_verbose(cls, verbose: bool): + """设置是否打印注册信息""" + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + # 检查是否已注册 + if name in cls._registries[category]: + if cls._registries[category][name] == component_class: + # 相同类重复注册,静默跳过 + return + elif cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取组件类""" + if category not in cls._registries: + available = list(cls._registries.keys()) + raise ValueError(f"❌ 未知类别: {category} (可用类别: {available})") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + @classmethod + def get_count(cls) -> int: + """获取注册组件总数""" + return sum(len(components) for components in cls._registries.values()) + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01b/residual.py b/example/1d-linear-convection/weno3/python-v1/01b/residual.py new file mode 100644 index 000000000..e0f96cd3c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01b/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux.factory import FluxCalculatorFactory +from reconstructor import ReconstructorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + + self.reconstructor = ReconstructorFactory.create(self.config, self.domain) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._compute_face_values() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _compute_face_values(self): + """私有方法:界面值重建""" + self.reconstructor.compute_face_values(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01b/result.py b/example/1d-linear-convection/weno3/python-v1/01b/result.py new file mode 100644 index 000000000..acd257b0d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01b/result.py @@ -0,0 +1,21 @@ +# result.py + +from initial_condition import InitialConditionFactory + +class ResultAssembler: + @staticmethod + def assemble(cfd: 'Cfd') -> dict: + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied].copy() + analytical = InitialConditionFactory.get_exact_solution(cfd) + + return { + "x": cfd.domain.mesh.xcc, + "numerical": u_numerical, + "analytical": analytical, + "config": { + "scheme": cfd.config.recon_scheme, + "order": cfd.config.spatial_order, + "rk_order": cfd.config.rk_order, + "final_time": cfd.config.final_time + } + } \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01b/run_eno_weno.py b/example/1d-linear-convection/weno3/python-v1/01b/run_eno_weno.py new file mode 100644 index 000000000..25efa62fb --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01b/run_eno_weno.py @@ -0,0 +1,52 @@ +# run_eno_weno.py + +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +# ✅ 新增:导入 Problem +from problems.linear_advection import LinearAdvectionProblem + + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行 ENO3 求解 + print("Running ENO3 solver...") + config_eno3 = CfdConfig() + config_eno3.with_reconstruction("eno", 3) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno3 = LinearAdvectionProblem(config_eno3) + cfd_eno3 = Cfd(problem_eno3, mesh) # ← 注入 problem + cfd_eno3.run() + + # 3. 配置并运行 WENO3 求解 + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno3 = LinearAdvectionProblem(config_weno3) + cfd_weno3 = Cfd(problem_weno3, mesh) # ← 注入 problem + cfd_weno3.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" + ) + + +if __name__ == "__main__": + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01b/solution.py b/example/1d-linear-convection/weno3/python-v1/01b/solution.py new file mode 100644 index 000000000..90666c34a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01b/solution.py @@ -0,0 +1,32 @@ +# solution.py +import numpy as np + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01b/solver.py b/example/1d-linear-convection/weno3/python-v1/01b/solver.py new file mode 100644 index 000000000..ee827a990 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01b/solver.py @@ -0,0 +1,67 @@ +# solver.py + +from mesh import Mesh +from domain import Domain +from solution import Solution +from config import CfdConfig +from result import ResultAssembler +from problem import Problem +from initial_condition import InitialConditionFactory +from boundary import BoundaryConditionFactory +from residual import ResidualCalculator +from time_integration import TimeIntegrator,TimeIntegratorFactory + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, problem: Problem, mesh): + self.problem = problem + self.config = problem.config + self.domain = Domain(problem.config, mesh) + self.solution = Solution(problem.config, self.domain) + + self.initial_condition = InitialConditionFactory.create(self.config) + self.boundary_condition = BoundaryConditionFactory.create(self) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + + # ==================== 公共接口(供 TimeIntegrator 调用) ==================== + def compute_residual(self): + """计算物理残差(封装重建→通量→散度)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件""" + self.boundary_condition.apply(self.solution.u) + + # ==================== 初始化 ==================== + def initialize(self): + """初始化全场:先 IC,再 BC,最后同步 old field""" + self.initial_condition.apply(self.solution) + # 应用边界条件到初始场 + self.apply_boundary() + # 同步 old field + self.solution.update_old_field() + + + def run(self): + self.initialize() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + self.integrator.step(dt) + t += dt + config.dt = dt_old + + self.result = ResultAssembler.assemble(self) + return self.result["numerical"] + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01b/time_integration.py b/example/1d-linear-convection/weno3/python-v1/01b/time_integration.py new file mode 100644 index 000000000..6cdb30068 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01b/time_integration.py @@ -0,0 +1,124 @@ +# time_integration.py + +""" +时间推进器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.cfd.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.apply_boundary() + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(使用装饰器注册) ---------------------- + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class TimeIntegratorFactory: + """时间推进器工厂""" + + @staticmethod + def create(cfd) -> 'TimeIntegrator': + """ + 创建时间推进器实例 + + Args: + cfd: CFD对象 + + Returns: + 时间推进器实例 + """ + from factories.base_factory import BaseFactory + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/boundary.py b/example/1d-linear-convection/weno3/python-v1/01c/boundary.py new file mode 100644 index 000000000..3a271aa29 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/boundary.py @@ -0,0 +1,104 @@ +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from factories.base_factory import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) + diff --git a/example/1d-linear-convection/weno3/python-v1/01c/cfd_registry.py b/example/1d-linear-convection/weno3/python-v1/01c/cfd_registry.py new file mode 100644 index 000000000..c12eb106c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/cfd_registry.py @@ -0,0 +1,62 @@ +# cfd_registry.py +""" +CFD注册系统统一导入模块 +所有其他模块从这里导入注册系统 +""" + +import sys +import os + +# 添加当前目录到路径,确保能找到registry.py +current_dir = os.path.dirname(os.path.abspath(__file__)) +if current_dir not in sys.path: + sys.path.insert(0, current_dir) + +try: + # 尝试导入注册系统 + from registry import ComponentRegistry, register_component + REGISTRY_AVAILABLE = True +except ImportError: + # 如果找不到,提供简单的空实现 + print("⚠️ 警告: 未找到 registry.py,使用最小化回退实现") + + class ComponentRegistry: + """最小化的注册系统回退实现""" + _registries = {} + + @classmethod + def register(cls, category, name, component_class): + if category not in cls._registries: + cls._registries[category] = {} + cls._registries[category][name] = component_class + + @classmethod + def get(cls, category, name): + if category not in cls._registries: + raise ValueError(f"未知类别: {category}") + if name not in cls._registries[category]: + raise ValueError(f"未找到: {category}.{name}") + return cls._registries[category][name] + + @classmethod + def create(cls, category, name, *args, **kwargs): + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) + for cat, comps in cls._registries.items()} + + def register_component(category, name=None): + """简化的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + REGISTRY_AVAILABLE = False + +# 导出统一的接口 +__all__ = ['ComponentRegistry', 'register_component', 'REGISTRY_AVAILABLE'] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/config.py b/example/1d-linear-convection/weno3/python-v1/01c/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/domain.py b/example/1d-linear-convection/weno3/python-v1/01c/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/factories/base_factory.py b/example/1d-linear-convection/weno3/python-v1/01c/factories/base_factory.py new file mode 100644 index 000000000..7b8b6105e --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/factories/base_factory.py @@ -0,0 +1,49 @@ +# factories/base_factory.py +""" +工厂基类 - 提供统一的组件创建接口 +""" + +from cfd_registry import ComponentRegistry +from typing import Any, Optional + +class BaseFactory: + """工厂基类,提供统一的组件创建方法""" + + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + """ + 统一创建组件的方法 + + Args: + category: 组件类别(如'boundary', 'flux'等) + name: 组件名称(如'periodic', 'rusanov'等) + *args, **kwargs: 传递给构造函数的参数 + + Returns: + 创建的组件实例 + + Raises: + ValueError: 如果组件不存在 + """ + name_lower = name.lower() + + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + # 获取可用组件列表 + available = ComponentRegistry.list_all().get(category, []) + + # 构建友好的错误信息 + if available: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"可用类型:{available}") + else: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"({category}类别下没有注册任何组件)") + + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + """获取指定类别的可用组件列表""" + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/flux/__init__.py b/example/1d-linear-convection/weno3/python-v1/01c/flux/__init__.py new file mode 100644 index 000000000..432a36cb0 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/flux/__init__.py @@ -0,0 +1,7 @@ +# flux/__init__.py +from .base import InviscidFluxCalculator +from .factory import FluxCalculatorFactory + +# 确保子模块被导入以触发注册 +from . import rusanov +from . import engquist_osher \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/flux/base.py b/example/1d-linear-convection/weno3/python-v1/01c/flux/base.py new file mode 100644 index 000000000..7a5a134fc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/flux/base.py @@ -0,0 +1,25 @@ +# flux/base.py +""" +抽象通量计算基类 +""" + +from abc import ABC, abstractmethod + +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/flux/engquist_osher.py b/example/1d-linear-convection/weno3/python-v1/01c/flux/engquist_osher.py new file mode 100644 index 000000000..34ad5f9c2 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/flux/engquist_osher.py @@ -0,0 +1,19 @@ +# flux/engquist_osher.py +""" +Engquist-Osher 通量计算器(线性对流专用) +""" + +from cfd_registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/flux/factory.py b/example/1d-linear-convection/weno3/python-v1/01c/flux/factory.py new file mode 100644 index 000000000..c2f6f0756 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/flux/factory.py @@ -0,0 +1,25 @@ +# flux/factory.py +""" +通量计算器专用工厂(封装注册细节,提供清晰接口) +符合你希望“将创建逻辑封装在工厂中”的设计原则 +""" + +from factories.base_factory import BaseFactory + +class FluxCalculatorFactory: + """通量计算器工厂:隐藏注册类别和组件名称等细节""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD 主对象,包含 config.flux_type 等配置 + + Returns: + 实现 InviscidFluxCalculator 接口的通量计算器实例 + """ + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/flux/rusanov.py b/example/1d-linear-convection/weno3/python-v1/01c/flux/rusanov.py new file mode 100644 index 000000000..62dd207ca --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/flux/rusanov.py @@ -0,0 +1,21 @@ +# flux/rusanov.py +""" +Rusanov(Lax-Friedrichs)通量计算器 +""" + +from cfd_registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/initial_condition.py b/example/1d-linear-convection/weno3/python-v1/01c/initial_condition.py new file mode 100644 index 000000000..963e9cdde --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/initial_condition.py @@ -0,0 +1,107 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def exact_solution(self, cfd): + """ + 默认解析解:线性对流 u(x, t) = u0(x - c * t) + 子类可重写以支持更复杂物理 + """ + x = cfd.domain.mesh.xcc + t = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * t + L) % L + return self.evaluate_at(x_shifted) + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from factories.base_factory import BaseFactory + return BaseFactory.create_component('initial_condition', config.ic_type, config) + + @classmethod + def get_exact_solution(cls, cfd): + return cls.create(cfd.config).exact_solution(cfd) # 一行! + diff --git a/example/1d-linear-convection/weno3/python-v1/01c/mesh.py b/example/1d-linear-convection/weno3/python-v1/01c/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/plotter.py b/example/1d-linear-convection/weno3/python-v1/01c/plotter.py new file mode 100644 index 000000000..e1f99ae25 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/plotter.py @@ -0,0 +1,109 @@ +# plotter.py + +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/problem.py b/example/1d-linear-convection/weno3/python-v1/01c/problem.py new file mode 100644 index 000000000..53719f265 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/problem.py @@ -0,0 +1,38 @@ +# problem.py +""" +问题定义模块:每个 Problem 子类代表一个完整测试用例 +包含初始条件、解析解(可选)、物理方程等 +""" + +from abc import ABC, abstractmethod +from initial_condition import InitialConditionFactory + + +class Problem(ABC): + """ + 抽象问题基类 + 每个具体问题(如线性对流、Sod 激波管)应继承此类 + """ + def __init__(self, config): + self.config = config + + @abstractmethod + def initial_condition(self): + """ + 返回 InitialCondition 实例 + """ + pass + + def exact_solution(self, cfd): + """ + 可选:返回解析解(数值数组) + 若无解析解,可抛出 NotImplementedError 或返回 None + """ + x = cfd.domain.mesh.xcc + raise NotImplementedError( + f"Problem '{self.__class__.__name__}' does not provide an exact solution." + ) + + @property + def name(self): + return self.__class__.__name__ \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/problems/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/01c/problems/linear_advection.py new file mode 100644 index 000000000..ea1e2df20 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/problems/linear_advection.py @@ -0,0 +1,27 @@ +# problems/linear_advection.py +""" +线性对流问题:支持任意初始条件 + 周期平移解析解 +""" + +import numpy as np +from problem import Problem +from initial_condition import InitialConditionFactory + + +class LinearAdvectionProblem(Problem): + """ + 线性对流问题 u_t + c u_x = 0 + 解析解:u(x, t) = u0(x - c * t) + """ + + def initial_condition(self): + return InitialConditionFactory.create(self.config) + + def exact_solution(self, cfd): + ic = self.initial_condition() + x = cfd.domain.mesh.xcc + t = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + x_shifted = (x - c * t + L) % L + return ic.evaluate_at(x_shifted) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python-v1/01c/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/reconstructor/base.py b/example/1d-linear-convection/weno3/python-v1/01c/reconstructor/base.py new file mode 100644 index 000000000..094b47120 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def compute_face_values(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/reconstructor/eno.py b/example/1d-linear-convection/weno3/python-v1/01c/reconstructor/eno.py new file mode 100644 index 000000000..8fde43334 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def compute_face_values(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/reconstructor/factory.py b/example/1d-linear-convection/weno3/python-v1/01c/reconstructor/factory.py new file mode 100644 index 000000000..efa4a1446 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from cfd_registry import ComponentRegistry + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from factories.base_factory import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python-v1/01c/reconstructor/weno3.py new file mode 100644 index 000000000..929061c13 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/reconstructor/weno3.py @@ -0,0 +1,59 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/python-v1/01c/registry.py b/example/1d-linear-convection/weno3/python-v1/01c/registry.py new file mode 100644 index 000000000..61be90500 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/registry.py @@ -0,0 +1,75 @@ +# registry.py (改进版) +""" +CFD组件注册系统 - 简洁高效版 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表""" + + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True # 控制是否打印注册信息 + + @classmethod + def set_verbose(cls, verbose: bool): + """设置是否打印注册信息""" + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + # 检查是否已注册 + if name in cls._registries[category]: + if cls._registries[category][name] == component_class: + # 相同类重复注册,静默跳过 + return + elif cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取组件类""" + if category not in cls._registries: + available = list(cls._registries.keys()) + raise ValueError(f"❌ 未知类别: {category} (可用类别: {available})") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + @classmethod + def get_count(cls) -> int: + """获取注册组件总数""" + return sum(len(components) for components in cls._registries.values()) + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/residual.py b/example/1d-linear-convection/weno3/python-v1/01c/residual.py new file mode 100644 index 000000000..e0f96cd3c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux.factory import FluxCalculatorFactory +from reconstructor import ReconstructorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + + self.reconstructor = ReconstructorFactory.create(self.config, self.domain) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._compute_face_values() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _compute_face_values(self): + """私有方法:界面值重建""" + self.reconstructor.compute_face_values(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/result.py b/example/1d-linear-convection/weno3/python-v1/01c/result.py new file mode 100644 index 000000000..acd257b0d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/result.py @@ -0,0 +1,21 @@ +# result.py + +from initial_condition import InitialConditionFactory + +class ResultAssembler: + @staticmethod + def assemble(cfd: 'Cfd') -> dict: + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied].copy() + analytical = InitialConditionFactory.get_exact_solution(cfd) + + return { + "x": cfd.domain.mesh.xcc, + "numerical": u_numerical, + "analytical": analytical, + "config": { + "scheme": cfd.config.recon_scheme, + "order": cfd.config.spatial_order, + "rk_order": cfd.config.rk_order, + "final_time": cfd.config.final_time + } + } \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/run_eno_weno.py b/example/1d-linear-convection/weno3/python-v1/01c/run_eno_weno.py new file mode 100644 index 000000000..25efa62fb --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/run_eno_weno.py @@ -0,0 +1,52 @@ +# run_eno_weno.py + +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +# ✅ 新增:导入 Problem +from problems.linear_advection import LinearAdvectionProblem + + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行 ENO3 求解 + print("Running ENO3 solver...") + config_eno3 = CfdConfig() + config_eno3.with_reconstruction("eno", 3) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno3 = LinearAdvectionProblem(config_eno3) + cfd_eno3 = Cfd(problem_eno3, mesh) # ← 注入 problem + cfd_eno3.run() + + # 3. 配置并运行 WENO3 求解 + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno3 = LinearAdvectionProblem(config_weno3) + cfd_weno3 = Cfd(problem_weno3, mesh) # ← 注入 problem + cfd_weno3.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" + ) + + +if __name__ == "__main__": + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/solution.py b/example/1d-linear-convection/weno3/python-v1/01c/solution.py new file mode 100644 index 000000000..90666c34a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/solution.py @@ -0,0 +1,32 @@ +# solution.py +import numpy as np + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/solver.py b/example/1d-linear-convection/weno3/python-v1/01c/solver.py new file mode 100644 index 000000000..ee827a990 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/solver.py @@ -0,0 +1,67 @@ +# solver.py + +from mesh import Mesh +from domain import Domain +from solution import Solution +from config import CfdConfig +from result import ResultAssembler +from problem import Problem +from initial_condition import InitialConditionFactory +from boundary import BoundaryConditionFactory +from residual import ResidualCalculator +from time_integration import TimeIntegrator,TimeIntegratorFactory + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, problem: Problem, mesh): + self.problem = problem + self.config = problem.config + self.domain = Domain(problem.config, mesh) + self.solution = Solution(problem.config, self.domain) + + self.initial_condition = InitialConditionFactory.create(self.config) + self.boundary_condition = BoundaryConditionFactory.create(self) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + + # ==================== 公共接口(供 TimeIntegrator 调用) ==================== + def compute_residual(self): + """计算物理残差(封装重建→通量→散度)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件""" + self.boundary_condition.apply(self.solution.u) + + # ==================== 初始化 ==================== + def initialize(self): + """初始化全场:先 IC,再 BC,最后同步 old field""" + self.initial_condition.apply(self.solution) + # 应用边界条件到初始场 + self.apply_boundary() + # 同步 old field + self.solution.update_old_field() + + + def run(self): + self.initialize() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + self.integrator.step(dt) + t += dt + config.dt = dt_old + + self.result = ResultAssembler.assemble(self) + return self.result["numerical"] + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/time_integration/__init__.py b/example/1d-linear-convection/weno3/python-v1/01c/time_integration/__init__.py new file mode 100644 index 000000000..6342de3bb --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/time_integration/__init__.py @@ -0,0 +1,8 @@ +# time_integration/__init__.py + +# 导出统一接口 +from .factory import TimeIntegratorFactory +from .base import TimeIntegrator + +# 触发子模块注册(关键!) +from . import rk1, rk2, rk3 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/time_integration/base.py b/example/1d-linear-convection/weno3/python-v1/01c/time_integration/base.py new file mode 100644 index 000000000..0cdf29a48 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/time_integration/base.py @@ -0,0 +1,27 @@ +# time_integration/base.py + +from abc import ABC, abstractmethod + +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + + @abstractmethod + def step(self, dt): + pass + + def compute_residual(self): + """计算残差(委托给 Cfd)""" + self.cfd.compute_residual() + + def apply_boundary(self): + """应用边界条件(委托给 Cfd)""" + self.cfd.apply_boundary() + + def map_idx(self, i): + """物理网格索引 → 残差数组索引""" + return i - self.domain.ist \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/time_integration/factory.py b/example/1d-linear-convection/weno3/python-v1/01c/time_integration/factory.py new file mode 100644 index 000000000..b9dd518fe --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/time_integration/factory.py @@ -0,0 +1,10 @@ +# time_integration/factory.py + +from factories.base_factory import BaseFactory + +class TimeIntegratorFactory: + @staticmethod + def create(cfd) -> 'TimeIntegrator': + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/time_integration/rk1.py b/example/1d-linear-convection/weno3/python-v1/01c/time_integration/rk1.py new file mode 100644 index 000000000..5906fe571 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/time_integration/rk1.py @@ -0,0 +1,15 @@ +# time_integration/rk1.py + +from .base import TimeIntegrator +from cfd_registry import register_component + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/time_integration/rk2.py b/example/1d-linear-convection/weno3/python-v1/01c/time_integration/rk2.py new file mode 100644 index 000000000..311c4543c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/time_integration/rk2.py @@ -0,0 +1,29 @@ +# time_integration/rk2.py + +from .base import TimeIntegrator +from cfd_registry import register_component + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + 0.5 * self.solution.un[i] + + 0.5 * self.solution.u[i] + + 0.5 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01c/time_integration/rk3.py b/example/1d-linear-convection/weno3/python-v1/01c/time_integration/rk3.py new file mode 100644 index 000000000..e91cd68cc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01c/time_integration/rk3.py @@ -0,0 +1,43 @@ +# time_integration/rk3.py + +from .base import TimeIntegrator +from cfd_registry import register_component + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # Stage 1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # Stage 2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = ( + 0.75 * self.solution.un[i] + + 0.25 * self.solution.u[i] + + 0.25 * dt * self.solution.res[j] + ) + self.solution.u[:] = u2 + self.apply_boundary() + + # Stage 3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + c1 * self.solution.un[i] + + c2 * self.solution.u[i] + + c3 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01d/boundary.py b/example/1d-linear-convection/weno3/python-v1/01d/boundary.py new file mode 100644 index 000000000..2d2cfbaf8 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/boundary.py @@ -0,0 +1,104 @@ +from abc import ABC, abstractmethod +from core.registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from core.registry import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) + diff --git a/example/1d-linear-convection/weno3/python-v1/01d/config.py b/example/1d-linear-convection/weno3/python-v1/01d/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01d/core/registry.py b/example/1d-linear-convection/weno3/python-v1/01d/core/registry.py new file mode 100644 index 000000000..a6038a501 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/core/registry.py @@ -0,0 +1,79 @@ +# core/registry.py +""" +统一注册系统 + 通用工厂 +- ComponentRegistry: 组件注册表 +- register_component: 装饰器 +- BaseFactory: 通用工厂类 +""" + +from typing import Dict, Type, Any + + +# ==================== 1. 注册表核心 ==================== +class ComponentRegistry: + """组件注册表""" + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True + + @classmethod + def set_verbose(cls, verbose: bool): + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + if category not in cls._registries: + cls._registries[category] = {} + if name in cls._registries[category]: + if cls._registries[category][name] != component_class and cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + if category not in cls._registries: + raise ValueError(f"❌ 未知类别: {category} (可用: {list(cls._registries.keys())})") + if name not in cls._registries[category]: + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {list(cls._registries[category].keys())})") + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) for cat, comps in cls._registries.items()} + + +# ==================== 2. 装饰器 ==================== +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + +# ==================== 3. 通用工厂 ==================== +class BaseFactory: + """通用工厂基类""" + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + name_lower = name.lower() + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + available = ComponentRegistry.list_all().get(category, []) + if available: + error_msg = f"不支持的 {category} 类型 '{name}'。可用类型:{available}" + else: + error_msg = f"不支持的 {category} 类型 '{name}'(无已注册组件)" + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01d/domain.py b/example/1d-linear-convection/weno3/python-v1/01d/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01d/flux/__init__.py b/example/1d-linear-convection/weno3/python-v1/01d/flux/__init__.py new file mode 100644 index 000000000..432a36cb0 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/flux/__init__.py @@ -0,0 +1,7 @@ +# flux/__init__.py +from .base import InviscidFluxCalculator +from .factory import FluxCalculatorFactory + +# 确保子模块被导入以触发注册 +from . import rusanov +from . import engquist_osher \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01d/flux/base.py b/example/1d-linear-convection/weno3/python-v1/01d/flux/base.py new file mode 100644 index 000000000..7a5a134fc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/flux/base.py @@ -0,0 +1,25 @@ +# flux/base.py +""" +抽象通量计算基类 +""" + +from abc import ABC, abstractmethod + +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01d/flux/engquist_osher.py b/example/1d-linear-convection/weno3/python-v1/01d/flux/engquist_osher.py new file mode 100644 index 000000000..b65e9d7a7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/flux/engquist_osher.py @@ -0,0 +1,19 @@ +# flux/engquist_osher.py +""" +Engquist-Osher 通量计算器(线性对流专用) +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01d/flux/factory.py b/example/1d-linear-convection/weno3/python-v1/01d/flux/factory.py new file mode 100644 index 000000000..5ae77c3fa --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/flux/factory.py @@ -0,0 +1,25 @@ +# flux/factory.py +""" +通量计算器专用工厂(封装注册细节,提供清晰接口) +符合你希望“将创建逻辑封装在工厂中”的设计原则 +""" + +from core.registry import BaseFactory + +class FluxCalculatorFactory: + """通量计算器工厂:隐藏注册类别和组件名称等细节""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD 主对象,包含 config.flux_type 等配置 + + Returns: + 实现 InviscidFluxCalculator 接口的通量计算器实例 + """ + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01d/flux/rusanov.py b/example/1d-linear-convection/weno3/python-v1/01d/flux/rusanov.py new file mode 100644 index 000000000..fdc3c33df --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/flux/rusanov.py @@ -0,0 +1,21 @@ +# flux/rusanov.py +""" +Rusanov(Lax-Friedrichs)通量计算器 +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01d/initial_condition.py b/example/1d-linear-convection/weno3/python-v1/01d/initial_condition.py new file mode 100644 index 000000000..088737195 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/initial_condition.py @@ -0,0 +1,107 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from core.registry import ComponentRegistry, register_component + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def exact_solution(self, cfd): + """ + 默认解析解:线性对流 u(x, t) = u0(x - c * t) + 子类可重写以支持更复杂物理 + """ + x = cfd.domain.mesh.xcc + t = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * t + L) % L + return self.evaluate_at(x_shifted) + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from core.registry import BaseFactory + return BaseFactory.create_component('initial_condition', config.ic_type, config) + + @classmethod + def get_exact_solution(cls, cfd): + return cls.create(cfd.config).exact_solution(cfd) + diff --git a/example/1d-linear-convection/weno3/python-v1/01d/mesh.py b/example/1d-linear-convection/weno3/python-v1/01d/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01d/plotter.py b/example/1d-linear-convection/weno3/python-v1/01d/plotter.py new file mode 100644 index 000000000..e1f99ae25 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/plotter.py @@ -0,0 +1,109 @@ +# plotter.py + +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01d/problem.py b/example/1d-linear-convection/weno3/python-v1/01d/problem.py new file mode 100644 index 000000000..53719f265 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/problem.py @@ -0,0 +1,38 @@ +# problem.py +""" +问题定义模块:每个 Problem 子类代表一个完整测试用例 +包含初始条件、解析解(可选)、物理方程等 +""" + +from abc import ABC, abstractmethod +from initial_condition import InitialConditionFactory + + +class Problem(ABC): + """ + 抽象问题基类 + 每个具体问题(如线性对流、Sod 激波管)应继承此类 + """ + def __init__(self, config): + self.config = config + + @abstractmethod + def initial_condition(self): + """ + 返回 InitialCondition 实例 + """ + pass + + def exact_solution(self, cfd): + """ + 可选:返回解析解(数值数组) + 若无解析解,可抛出 NotImplementedError 或返回 None + """ + x = cfd.domain.mesh.xcc + raise NotImplementedError( + f"Problem '{self.__class__.__name__}' does not provide an exact solution." + ) + + @property + def name(self): + return self.__class__.__name__ \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01d/problems/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/01d/problems/linear_advection.py new file mode 100644 index 000000000..ea1e2df20 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/problems/linear_advection.py @@ -0,0 +1,27 @@ +# problems/linear_advection.py +""" +线性对流问题:支持任意初始条件 + 周期平移解析解 +""" + +import numpy as np +from problem import Problem +from initial_condition import InitialConditionFactory + + +class LinearAdvectionProblem(Problem): + """ + 线性对流问题 u_t + c u_x = 0 + 解析解:u(x, t) = u0(x - c * t) + """ + + def initial_condition(self): + return InitialConditionFactory.create(self.config) + + def exact_solution(self, cfd): + ic = self.initial_condition() + x = cfd.domain.mesh.xcc + t = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + x_shifted = (x - c * t + L) % L + return ic.evaluate_at(x_shifted) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01d/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python-v1/01d/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01d/reconstructor/base.py b/example/1d-linear-convection/weno3/python-v1/01d/reconstructor/base.py new file mode 100644 index 000000000..094b47120 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def compute_face_values(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01d/reconstructor/eno.py b/example/1d-linear-convection/weno3/python-v1/01d/reconstructor/eno.py new file mode 100644 index 000000000..4c8fc1e4e --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def compute_face_values(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01d/reconstructor/factory.py b/example/1d-linear-convection/weno3/python-v1/01d/reconstructor/factory.py new file mode 100644 index 000000000..d0e60a792 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from core.registry import ComponentRegistry, register_component + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from core.registry import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01d/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python-v1/01d/reconstructor/weno3.py new file mode 100644 index 000000000..cc7d82df6 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/reconstructor/weno3.py @@ -0,0 +1,59 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/python-v1/01d/residual.py b/example/1d-linear-convection/weno3/python-v1/01d/residual.py new file mode 100644 index 000000000..e0f96cd3c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux.factory import FluxCalculatorFactory +from reconstructor import ReconstructorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + + self.reconstructor = ReconstructorFactory.create(self.config, self.domain) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._compute_face_values() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _compute_face_values(self): + """私有方法:界面值重建""" + self.reconstructor.compute_face_values(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01d/result.py b/example/1d-linear-convection/weno3/python-v1/01d/result.py new file mode 100644 index 000000000..acd257b0d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/result.py @@ -0,0 +1,21 @@ +# result.py + +from initial_condition import InitialConditionFactory + +class ResultAssembler: + @staticmethod + def assemble(cfd: 'Cfd') -> dict: + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied].copy() + analytical = InitialConditionFactory.get_exact_solution(cfd) + + return { + "x": cfd.domain.mesh.xcc, + "numerical": u_numerical, + "analytical": analytical, + "config": { + "scheme": cfd.config.recon_scheme, + "order": cfd.config.spatial_order, + "rk_order": cfd.config.rk_order, + "final_time": cfd.config.final_time + } + } \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01d/run_eno_weno.py b/example/1d-linear-convection/weno3/python-v1/01d/run_eno_weno.py new file mode 100644 index 000000000..25efa62fb --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/run_eno_weno.py @@ -0,0 +1,52 @@ +# run_eno_weno.py + +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +# ✅ 新增:导入 Problem +from problems.linear_advection import LinearAdvectionProblem + + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行 ENO3 求解 + print("Running ENO3 solver...") + config_eno3 = CfdConfig() + config_eno3.with_reconstruction("eno", 3) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno3 = LinearAdvectionProblem(config_eno3) + cfd_eno3 = Cfd(problem_eno3, mesh) # ← 注入 problem + cfd_eno3.run() + + # 3. 配置并运行 WENO3 求解 + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno3 = LinearAdvectionProblem(config_weno3) + cfd_weno3 = Cfd(problem_weno3, mesh) # ← 注入 problem + cfd_weno3.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" + ) + + +if __name__ == "__main__": + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01d/solution.py b/example/1d-linear-convection/weno3/python-v1/01d/solution.py new file mode 100644 index 000000000..90666c34a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/solution.py @@ -0,0 +1,32 @@ +# solution.py +import numpy as np + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01d/solver.py b/example/1d-linear-convection/weno3/python-v1/01d/solver.py new file mode 100644 index 000000000..ee827a990 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/solver.py @@ -0,0 +1,67 @@ +# solver.py + +from mesh import Mesh +from domain import Domain +from solution import Solution +from config import CfdConfig +from result import ResultAssembler +from problem import Problem +from initial_condition import InitialConditionFactory +from boundary import BoundaryConditionFactory +from residual import ResidualCalculator +from time_integration import TimeIntegrator,TimeIntegratorFactory + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, problem: Problem, mesh): + self.problem = problem + self.config = problem.config + self.domain = Domain(problem.config, mesh) + self.solution = Solution(problem.config, self.domain) + + self.initial_condition = InitialConditionFactory.create(self.config) + self.boundary_condition = BoundaryConditionFactory.create(self) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + + # ==================== 公共接口(供 TimeIntegrator 调用) ==================== + def compute_residual(self): + """计算物理残差(封装重建→通量→散度)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件""" + self.boundary_condition.apply(self.solution.u) + + # ==================== 初始化 ==================== + def initialize(self): + """初始化全场:先 IC,再 BC,最后同步 old field""" + self.initial_condition.apply(self.solution) + # 应用边界条件到初始场 + self.apply_boundary() + # 同步 old field + self.solution.update_old_field() + + + def run(self): + self.initialize() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + self.integrator.step(dt) + t += dt + config.dt = dt_old + + self.result = ResultAssembler.assemble(self) + return self.result["numerical"] + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01d/time_integration/__init__.py b/example/1d-linear-convection/weno3/python-v1/01d/time_integration/__init__.py new file mode 100644 index 000000000..6342de3bb --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/time_integration/__init__.py @@ -0,0 +1,8 @@ +# time_integration/__init__.py + +# 导出统一接口 +from .factory import TimeIntegratorFactory +from .base import TimeIntegrator + +# 触发子模块注册(关键!) +from . import rk1, rk2, rk3 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01d/time_integration/base.py b/example/1d-linear-convection/weno3/python-v1/01d/time_integration/base.py new file mode 100644 index 000000000..0cdf29a48 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/time_integration/base.py @@ -0,0 +1,27 @@ +# time_integration/base.py + +from abc import ABC, abstractmethod + +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + + @abstractmethod + def step(self, dt): + pass + + def compute_residual(self): + """计算残差(委托给 Cfd)""" + self.cfd.compute_residual() + + def apply_boundary(self): + """应用边界条件(委托给 Cfd)""" + self.cfd.apply_boundary() + + def map_idx(self, i): + """物理网格索引 → 残差数组索引""" + return i - self.domain.ist \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01d/time_integration/factory.py b/example/1d-linear-convection/weno3/python-v1/01d/time_integration/factory.py new file mode 100644 index 000000000..94662db22 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/time_integration/factory.py @@ -0,0 +1,10 @@ +# time_integration/factory.py + +from core.registry import BaseFactory + +class TimeIntegratorFactory: + @staticmethod + def create(cfd) -> 'TimeIntegrator': + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01d/time_integration/rk1.py b/example/1d-linear-convection/weno3/python-v1/01d/time_integration/rk1.py new file mode 100644 index 000000000..b4c8a0211 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/time_integration/rk1.py @@ -0,0 +1,15 @@ +# time_integration/rk1.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01d/time_integration/rk2.py b/example/1d-linear-convection/weno3/python-v1/01d/time_integration/rk2.py new file mode 100644 index 000000000..6d2be3049 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/time_integration/rk2.py @@ -0,0 +1,29 @@ +# time_integration/rk2.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + 0.5 * self.solution.un[i] + + 0.5 * self.solution.u[i] + + 0.5 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01d/time_integration/rk3.py b/example/1d-linear-convection/weno3/python-v1/01d/time_integration/rk3.py new file mode 100644 index 000000000..c70791e13 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01d/time_integration/rk3.py @@ -0,0 +1,43 @@ +# time_integration/rk3.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # Stage 1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # Stage 2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = ( + 0.75 * self.solution.un[i] + + 0.25 * self.solution.u[i] + + 0.25 * dt * self.solution.res[j] + ) + self.solution.u[:] = u2 + self.apply_boundary() + + # Stage 3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + c1 * self.solution.un[i] + + c2 * self.solution.u[i] + + c3 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01e/boundary.py b/example/1d-linear-convection/weno3/python-v1/01e/boundary.py new file mode 100644 index 000000000..2d2cfbaf8 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/boundary.py @@ -0,0 +1,104 @@ +from abc import ABC, abstractmethod +from core.registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from core.registry import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) + diff --git a/example/1d-linear-convection/weno3/python-v1/01e/config.py b/example/1d-linear-convection/weno3/python-v1/01e/config.py new file mode 100644 index 000000000..63d05faad --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/config.py @@ -0,0 +1,42 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + #self.ic_type = "sin" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01e/core/registry.py b/example/1d-linear-convection/weno3/python-v1/01e/core/registry.py new file mode 100644 index 000000000..a6038a501 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/core/registry.py @@ -0,0 +1,79 @@ +# core/registry.py +""" +统一注册系统 + 通用工厂 +- ComponentRegistry: 组件注册表 +- register_component: 装饰器 +- BaseFactory: 通用工厂类 +""" + +from typing import Dict, Type, Any + + +# ==================== 1. 注册表核心 ==================== +class ComponentRegistry: + """组件注册表""" + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True + + @classmethod + def set_verbose(cls, verbose: bool): + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + if category not in cls._registries: + cls._registries[category] = {} + if name in cls._registries[category]: + if cls._registries[category][name] != component_class and cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + if category not in cls._registries: + raise ValueError(f"❌ 未知类别: {category} (可用: {list(cls._registries.keys())})") + if name not in cls._registries[category]: + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {list(cls._registries[category].keys())})") + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) for cat, comps in cls._registries.items()} + + +# ==================== 2. 装饰器 ==================== +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + +# ==================== 3. 通用工厂 ==================== +class BaseFactory: + """通用工厂基类""" + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + name_lower = name.lower() + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + available = ComponentRegistry.list_all().get(category, []) + if available: + error_msg = f"不支持的 {category} 类型 '{name}'。可用类型:{available}" + else: + error_msg = f"不支持的 {category} 类型 '{name}'(无已注册组件)" + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01e/domain.py b/example/1d-linear-convection/weno3/python-v1/01e/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01e/flux/__init__.py b/example/1d-linear-convection/weno3/python-v1/01e/flux/__init__.py new file mode 100644 index 000000000..432a36cb0 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/flux/__init__.py @@ -0,0 +1,7 @@ +# flux/__init__.py +from .base import InviscidFluxCalculator +from .factory import FluxCalculatorFactory + +# 确保子模块被导入以触发注册 +from . import rusanov +from . import engquist_osher \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01e/flux/base.py b/example/1d-linear-convection/weno3/python-v1/01e/flux/base.py new file mode 100644 index 000000000..7a5a134fc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/flux/base.py @@ -0,0 +1,25 @@ +# flux/base.py +""" +抽象通量计算基类 +""" + +from abc import ABC, abstractmethod + +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01e/flux/engquist_osher.py b/example/1d-linear-convection/weno3/python-v1/01e/flux/engquist_osher.py new file mode 100644 index 000000000..b65e9d7a7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/flux/engquist_osher.py @@ -0,0 +1,19 @@ +# flux/engquist_osher.py +""" +Engquist-Osher 通量计算器(线性对流专用) +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01e/flux/factory.py b/example/1d-linear-convection/weno3/python-v1/01e/flux/factory.py new file mode 100644 index 000000000..5ae77c3fa --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/flux/factory.py @@ -0,0 +1,25 @@ +# flux/factory.py +""" +通量计算器专用工厂(封装注册细节,提供清晰接口) +符合你希望“将创建逻辑封装在工厂中”的设计原则 +""" + +from core.registry import BaseFactory + +class FluxCalculatorFactory: + """通量计算器工厂:隐藏注册类别和组件名称等细节""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD 主对象,包含 config.flux_type 等配置 + + Returns: + 实现 InviscidFluxCalculator 接口的通量计算器实例 + """ + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01e/flux/rusanov.py b/example/1d-linear-convection/weno3/python-v1/01e/flux/rusanov.py new file mode 100644 index 000000000..fdc3c33df --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/flux/rusanov.py @@ -0,0 +1,21 @@ +# flux/rusanov.py +""" +Rusanov(Lax-Friedrichs)通量计算器 +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01e/initial_condition.py b/example/1d-linear-convection/weno3/python-v1/01e/initial_condition.py new file mode 100644 index 000000000..dda8a3bd4 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/initial_condition.py @@ -0,0 +1,90 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from core.registry import ComponentRegistry, register_component + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from core.registry import BaseFactory + return BaseFactory.create_component('initial_condition', config.ic_type, config) + diff --git a/example/1d-linear-convection/weno3/python-v1/01e/mesh.py b/example/1d-linear-convection/weno3/python-v1/01e/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01e/plotter.py b/example/1d-linear-convection/weno3/python-v1/01e/plotter.py new file mode 100644 index 000000000..e1f99ae25 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/plotter.py @@ -0,0 +1,109 @@ +# plotter.py + +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01e/problem.py b/example/1d-linear-convection/weno3/python-v1/01e/problem.py new file mode 100644 index 000000000..53719f265 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/problem.py @@ -0,0 +1,38 @@ +# problem.py +""" +问题定义模块:每个 Problem 子类代表一个完整测试用例 +包含初始条件、解析解(可选)、物理方程等 +""" + +from abc import ABC, abstractmethod +from initial_condition import InitialConditionFactory + + +class Problem(ABC): + """ + 抽象问题基类 + 每个具体问题(如线性对流、Sod 激波管)应继承此类 + """ + def __init__(self, config): + self.config = config + + @abstractmethod + def initial_condition(self): + """ + 返回 InitialCondition 实例 + """ + pass + + def exact_solution(self, cfd): + """ + 可选:返回解析解(数值数组) + 若无解析解,可抛出 NotImplementedError 或返回 None + """ + x = cfd.domain.mesh.xcc + raise NotImplementedError( + f"Problem '{self.__class__.__name__}' does not provide an exact solution." + ) + + @property + def name(self): + return self.__class__.__name__ \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01e/problems/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/01e/problems/linear_advection.py new file mode 100644 index 000000000..a9936df7e --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/problems/linear_advection.py @@ -0,0 +1,28 @@ +# problems/linear_advection.py +""" +线性对流问题:支持任意初始条件 + 周期平移解析解 +""" + +import numpy as np +from problem import Problem +from initial_condition import InitialConditionFactory + + +class LinearAdvectionProblem(Problem): + """ + 线性对流问题 u_t + c u_x = 0 + 解析解:u(x, t) = u0(x - c * t) + """ + + def initial_condition(self): + return InitialConditionFactory.create(self.config) + + def exact_solution(self, cfd): + ic = self.initial_condition() + x = cfd.domain.mesh.xcc + t = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + x_shifted = (x - c * t + L) % L + return ic.evaluate_at(x_shifted) + diff --git a/example/1d-linear-convection/weno3/python-v1/01e/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python-v1/01e/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01e/reconstructor/base.py b/example/1d-linear-convection/weno3/python-v1/01e/reconstructor/base.py new file mode 100644 index 000000000..094b47120 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def compute_face_values(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01e/reconstructor/eno.py b/example/1d-linear-convection/weno3/python-v1/01e/reconstructor/eno.py new file mode 100644 index 000000000..4c8fc1e4e --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def compute_face_values(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01e/reconstructor/factory.py b/example/1d-linear-convection/weno3/python-v1/01e/reconstructor/factory.py new file mode 100644 index 000000000..d0e60a792 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from core.registry import ComponentRegistry, register_component + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from core.registry import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01e/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python-v1/01e/reconstructor/weno3.py new file mode 100644 index 000000000..cc7d82df6 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/reconstructor/weno3.py @@ -0,0 +1,59 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/python-v1/01e/residual.py b/example/1d-linear-convection/weno3/python-v1/01e/residual.py new file mode 100644 index 000000000..e0f96cd3c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux.factory import FluxCalculatorFactory +from reconstructor import ReconstructorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + + self.reconstructor = ReconstructorFactory.create(self.config, self.domain) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._compute_face_values() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _compute_face_values(self): + """私有方法:界面值重建""" + self.reconstructor.compute_face_values(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01e/result.py b/example/1d-linear-convection/weno3/python-v1/01e/result.py new file mode 100644 index 000000000..5e572216d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/result.py @@ -0,0 +1,25 @@ +# result.py + +class ResultAssembler: + @staticmethod + def assemble(cfd: 'Cfd') -> dict: + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied].copy() + + # ✅ 从 problem 获取解析解(唯一正确路径) + try: + analytical = cfd.problem.exact_solution(cfd) + except NotImplementedError: + analytical = None # 或 np.full_like(u_numerical, np.nan) + + return { + "x": cfd.domain.mesh.xcc, + "numerical": u_numerical, + "analytical": analytical, + "config": { + "scheme": cfd.config.recon_scheme, + "order": cfd.config.spatial_order, + "rk_order": cfd.config.rk_order, + "final_time": cfd.config.final_time, + "problem": cfd.problem.name, + } + } \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01e/run_eno_weno.py b/example/1d-linear-convection/weno3/python-v1/01e/run_eno_weno.py new file mode 100644 index 000000000..25efa62fb --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/run_eno_weno.py @@ -0,0 +1,52 @@ +# run_eno_weno.py + +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +# ✅ 新增:导入 Problem +from problems.linear_advection import LinearAdvectionProblem + + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行 ENO3 求解 + print("Running ENO3 solver...") + config_eno3 = CfdConfig() + config_eno3.with_reconstruction("eno", 3) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno3 = LinearAdvectionProblem(config_eno3) + cfd_eno3 = Cfd(problem_eno3, mesh) # ← 注入 problem + cfd_eno3.run() + + # 3. 配置并运行 WENO3 求解 + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno3 = LinearAdvectionProblem(config_weno3) + cfd_weno3 = Cfd(problem_weno3, mesh) # ← 注入 problem + cfd_weno3.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" + ) + + +if __name__ == "__main__": + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01e/solution.py b/example/1d-linear-convection/weno3/python-v1/01e/solution.py new file mode 100644 index 000000000..90666c34a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/solution.py @@ -0,0 +1,32 @@ +# solution.py +import numpy as np + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01e/solver.py b/example/1d-linear-convection/weno3/python-v1/01e/solver.py new file mode 100644 index 000000000..ee827a990 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/solver.py @@ -0,0 +1,67 @@ +# solver.py + +from mesh import Mesh +from domain import Domain +from solution import Solution +from config import CfdConfig +from result import ResultAssembler +from problem import Problem +from initial_condition import InitialConditionFactory +from boundary import BoundaryConditionFactory +from residual import ResidualCalculator +from time_integration import TimeIntegrator,TimeIntegratorFactory + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, problem: Problem, mesh): + self.problem = problem + self.config = problem.config + self.domain = Domain(problem.config, mesh) + self.solution = Solution(problem.config, self.domain) + + self.initial_condition = InitialConditionFactory.create(self.config) + self.boundary_condition = BoundaryConditionFactory.create(self) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + + # ==================== 公共接口(供 TimeIntegrator 调用) ==================== + def compute_residual(self): + """计算物理残差(封装重建→通量→散度)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件""" + self.boundary_condition.apply(self.solution.u) + + # ==================== 初始化 ==================== + def initialize(self): + """初始化全场:先 IC,再 BC,最后同步 old field""" + self.initial_condition.apply(self.solution) + # 应用边界条件到初始场 + self.apply_boundary() + # 同步 old field + self.solution.update_old_field() + + + def run(self): + self.initialize() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + self.integrator.step(dt) + t += dt + config.dt = dt_old + + self.result = ResultAssembler.assemble(self) + return self.result["numerical"] + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01e/time_integration/__init__.py b/example/1d-linear-convection/weno3/python-v1/01e/time_integration/__init__.py new file mode 100644 index 000000000..6342de3bb --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/time_integration/__init__.py @@ -0,0 +1,8 @@ +# time_integration/__init__.py + +# 导出统一接口 +from .factory import TimeIntegratorFactory +from .base import TimeIntegrator + +# 触发子模块注册(关键!) +from . import rk1, rk2, rk3 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01e/time_integration/base.py b/example/1d-linear-convection/weno3/python-v1/01e/time_integration/base.py new file mode 100644 index 000000000..0cdf29a48 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/time_integration/base.py @@ -0,0 +1,27 @@ +# time_integration/base.py + +from abc import ABC, abstractmethod + +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + + @abstractmethod + def step(self, dt): + pass + + def compute_residual(self): + """计算残差(委托给 Cfd)""" + self.cfd.compute_residual() + + def apply_boundary(self): + """应用边界条件(委托给 Cfd)""" + self.cfd.apply_boundary() + + def map_idx(self, i): + """物理网格索引 → 残差数组索引""" + return i - self.domain.ist \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01e/time_integration/factory.py b/example/1d-linear-convection/weno3/python-v1/01e/time_integration/factory.py new file mode 100644 index 000000000..94662db22 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/time_integration/factory.py @@ -0,0 +1,10 @@ +# time_integration/factory.py + +from core.registry import BaseFactory + +class TimeIntegratorFactory: + @staticmethod + def create(cfd) -> 'TimeIntegrator': + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01e/time_integration/rk1.py b/example/1d-linear-convection/weno3/python-v1/01e/time_integration/rk1.py new file mode 100644 index 000000000..b4c8a0211 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/time_integration/rk1.py @@ -0,0 +1,15 @@ +# time_integration/rk1.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01e/time_integration/rk2.py b/example/1d-linear-convection/weno3/python-v1/01e/time_integration/rk2.py new file mode 100644 index 000000000..6d2be3049 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/time_integration/rk2.py @@ -0,0 +1,29 @@ +# time_integration/rk2.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + 0.5 * self.solution.un[i] + + 0.5 * self.solution.u[i] + + 0.5 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01e/time_integration/rk3.py b/example/1d-linear-convection/weno3/python-v1/01e/time_integration/rk3.py new file mode 100644 index 000000000..c70791e13 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01e/time_integration/rk3.py @@ -0,0 +1,43 @@ +# time_integration/rk3.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # Stage 1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # Stage 2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = ( + 0.75 * self.solution.un[i] + + 0.25 * self.solution.u[i] + + 0.25 * dt * self.solution.res[j] + ) + self.solution.u[:] = u2 + self.apply_boundary() + + # Stage 3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + c1 * self.solution.un[i] + + c2 * self.solution.u[i] + + c3 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/boundary.py b/example/1d-linear-convection/weno3/python-v1/01f/boundary.py new file mode 100644 index 000000000..2d2cfbaf8 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/boundary.py @@ -0,0 +1,104 @@ +from abc import ABC, abstractmethod +from core.registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from core.registry import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) + diff --git a/example/1d-linear-convection/weno3/python-v1/01f/config.py b/example/1d-linear-convection/weno3/python-v1/01f/config.py new file mode 100644 index 000000000..63d05faad --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/config.py @@ -0,0 +1,42 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + #self.ic_type = "sin" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/core/registry.py b/example/1d-linear-convection/weno3/python-v1/01f/core/registry.py new file mode 100644 index 000000000..a6038a501 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/core/registry.py @@ -0,0 +1,79 @@ +# core/registry.py +""" +统一注册系统 + 通用工厂 +- ComponentRegistry: 组件注册表 +- register_component: 装饰器 +- BaseFactory: 通用工厂类 +""" + +from typing import Dict, Type, Any + + +# ==================== 1. 注册表核心 ==================== +class ComponentRegistry: + """组件注册表""" + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True + + @classmethod + def set_verbose(cls, verbose: bool): + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + if category not in cls._registries: + cls._registries[category] = {} + if name in cls._registries[category]: + if cls._registries[category][name] != component_class and cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + if category not in cls._registries: + raise ValueError(f"❌ 未知类别: {category} (可用: {list(cls._registries.keys())})") + if name not in cls._registries[category]: + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {list(cls._registries[category].keys())})") + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) for cat, comps in cls._registries.items()} + + +# ==================== 2. 装饰器 ==================== +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + +# ==================== 3. 通用工厂 ==================== +class BaseFactory: + """通用工厂基类""" + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + name_lower = name.lower() + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + available = ComponentRegistry.list_all().get(category, []) + if available: + error_msg = f"不支持的 {category} 类型 '{name}'。可用类型:{available}" + else: + error_msg = f"不支持的 {category} 类型 '{name}'(无已注册组件)" + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/domain.py b/example/1d-linear-convection/weno3/python-v1/01f/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/equations/base.py b/example/1d-linear-convection/weno3/python-v1/01f/equations/base.py new file mode 100644 index 000000000..2e5c87768 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/equations/base.py @@ -0,0 +1,47 @@ +# equations/base.py + +from abc import ABC, abstractmethod +import numpy as np + +class Equation(ABC): + """ + 控制方程抽象基类 + 所有物理方程(线性对流、Euler、MHD)必须继承此类 + """ + + @property + @abstractmethod + def num_equations(self) -> int: + """返回方程组变量数(标量=1,Euler=3,MHD=8)""" + pass + + @abstractmethod + def flux(self, u): + """ + 计算通量函数 f(u) + :param u: 状态向量 (num_equations,) + :return: 通量向量 (num_equations,) + """ + pass + + @abstractmethod + def max_wave_speed(self, u): + """ + 计算最大波速(用于 CFL 条件) + :param u: 状态向量 + :return: 标量波速 + """ + pass + + def exact_solution(self, x, t, initial_condition_func): + """ + 可选:提供解析解 + 子类可重写;若无解析解,调用方应捕获 NotImplementedError + :param x: 空间坐标数组 (ncells,) + :param t: 时间 + :param initial_condition_func: 初值函数 u0(x) -> (num_equations, ncells) + :return: 解 u(x,t) -> (num_equations, ncells) + """ + raise NotImplementedError( + f"Equation '{self.__class__.__name__}' does not provide an exact solution." + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/equations/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/01f/equations/linear_advection.py new file mode 100644 index 000000000..1768a0f0d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/equations/linear_advection.py @@ -0,0 +1,38 @@ +# equations/linear_advection.py + +import numpy as np +from .base import Equation + +class LinearAdvectionEquation(Equation): + """ + 线性对流方程: u_t + c * u_x = 0 + 支持标量(num_equations=1) + """ + + def __init__(self, wave_speed: float): + self.c = wave_speed + + @property + def num_equations(self) -> int: + return 1 + + def flux(self, u): + """f(u) = c * u""" + return np.array([self.c * u[0]]) + + def max_wave_speed(self, u): + """最大波速 = |c|""" + return abs(self.c) + + def exact_solution(self, x, t, initial_condition_func): + """ + 解析解: u(x, t) = u0(x - c * t) + 支持周期边界 + """ + if len(x) == 0: + return np.zeros((1, 0)) + + L = x[-1] - x[0] # 假设均匀周期网格 + x_shifted = (x - self.c * t + L) % L + u0 = initial_condition_func(x_shifted) # (1, ncells) + return u0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/flux/__init__.py b/example/1d-linear-convection/weno3/python-v1/01f/flux/__init__.py new file mode 100644 index 000000000..432a36cb0 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/flux/__init__.py @@ -0,0 +1,7 @@ +# flux/__init__.py +from .base import InviscidFluxCalculator +from .factory import FluxCalculatorFactory + +# 确保子模块被导入以触发注册 +from . import rusanov +from . import engquist_osher \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/flux/base.py b/example/1d-linear-convection/weno3/python-v1/01f/flux/base.py new file mode 100644 index 000000000..7a5a134fc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/flux/base.py @@ -0,0 +1,25 @@ +# flux/base.py +""" +抽象通量计算基类 +""" + +from abc import ABC, abstractmethod + +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/flux/engquist_osher.py b/example/1d-linear-convection/weno3/python-v1/01f/flux/engquist_osher.py new file mode 100644 index 000000000..b65e9d7a7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/flux/engquist_osher.py @@ -0,0 +1,19 @@ +# flux/engquist_osher.py +""" +Engquist-Osher 通量计算器(线性对流专用) +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/flux/factory.py b/example/1d-linear-convection/weno3/python-v1/01f/flux/factory.py new file mode 100644 index 000000000..5ae77c3fa --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/flux/factory.py @@ -0,0 +1,25 @@ +# flux/factory.py +""" +通量计算器专用工厂(封装注册细节,提供清晰接口) +符合你希望“将创建逻辑封装在工厂中”的设计原则 +""" + +from core.registry import BaseFactory + +class FluxCalculatorFactory: + """通量计算器工厂:隐藏注册类别和组件名称等细节""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD 主对象,包含 config.flux_type 等配置 + + Returns: + 实现 InviscidFluxCalculator 接口的通量计算器实例 + """ + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/flux/rusanov.py b/example/1d-linear-convection/weno3/python-v1/01f/flux/rusanov.py new file mode 100644 index 000000000..fdc3c33df --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/flux/rusanov.py @@ -0,0 +1,21 @@ +# flux/rusanov.py +""" +Rusanov(Lax-Friedrichs)通量计算器 +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/initial_condition.py b/example/1d-linear-convection/weno3/python-v1/01f/initial_condition.py new file mode 100644 index 000000000..dda8a3bd4 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/initial_condition.py @@ -0,0 +1,90 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from core.registry import ComponentRegistry, register_component + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from core.registry import BaseFactory + return BaseFactory.create_component('initial_condition', config.ic_type, config) + diff --git a/example/1d-linear-convection/weno3/python-v1/01f/mesh.py b/example/1d-linear-convection/weno3/python-v1/01f/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/plotter.py b/example/1d-linear-convection/weno3/python-v1/01f/plotter.py new file mode 100644 index 000000000..e1f99ae25 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/plotter.py @@ -0,0 +1,109 @@ +# plotter.py + +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/problem.py b/example/1d-linear-convection/weno3/python-v1/01f/problem.py new file mode 100644 index 000000000..53719f265 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/problem.py @@ -0,0 +1,38 @@ +# problem.py +""" +问题定义模块:每个 Problem 子类代表一个完整测试用例 +包含初始条件、解析解(可选)、物理方程等 +""" + +from abc import ABC, abstractmethod +from initial_condition import InitialConditionFactory + + +class Problem(ABC): + """ + 抽象问题基类 + 每个具体问题(如线性对流、Sod 激波管)应继承此类 + """ + def __init__(self, config): + self.config = config + + @abstractmethod + def initial_condition(self): + """ + 返回 InitialCondition 实例 + """ + pass + + def exact_solution(self, cfd): + """ + 可选:返回解析解(数值数组) + 若无解析解,可抛出 NotImplementedError 或返回 None + """ + x = cfd.domain.mesh.xcc + raise NotImplementedError( + f"Problem '{self.__class__.__name__}' does not provide an exact solution." + ) + + @property + def name(self): + return self.__class__.__name__ \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/problems/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/01f/problems/linear_advection.py new file mode 100644 index 000000000..b6d548f51 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/problems/linear_advection.py @@ -0,0 +1,39 @@ +# problems/linear_advection.py + +import numpy as np +from problem import Problem +from equations.linear_advection import LinearAdvectionEquation +from initial_condition import InitialConditionFactory + +class LinearAdvectionProblem(Problem): + """ + 线性对流问题:u_t + c u_x = 0 + 使用周期边界 + 任意初始条件 + """ + + def __init__(self, config): + super().__init__(config) + # 持有物理方程实例 + self.equation = LinearAdvectionEquation(wave_speed=config.wave_speed) + self._ic_cache = None # 缓存 IC 实例 + + def initial_condition(self): + """返回初始条件实例""" + if self._ic_cache is None: + self._ic_cache = InitialConditionFactory.create(self.config) + return self._ic_cache + + def exact_solution(self, cfd): + """ + 委托给 Equation 计算解析解 + """ + ic = self.initial_condition() + # 包装 evaluate_at 以返回 (1, ncells) + def vectorized_ic(x): + u0_scalar = ic.evaluate_at(x) # (ncells,) + return u0_scalar[np.newaxis, :] # (1, ncells) + + x = cfd.domain.mesh.xcc + t = cfd.config.final_time + u_exact = self.equation.exact_solution(x, t, vectorized_ic) + return u_exact[0] # 返回标量数组 (ncells,) 以兼容现有绘图逻辑 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python-v1/01f/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/reconstructor/base.py b/example/1d-linear-convection/weno3/python-v1/01f/reconstructor/base.py new file mode 100644 index 000000000..094b47120 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def compute_face_values(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/reconstructor/eno.py b/example/1d-linear-convection/weno3/python-v1/01f/reconstructor/eno.py new file mode 100644 index 000000000..4c8fc1e4e --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def compute_face_values(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/reconstructor/factory.py b/example/1d-linear-convection/weno3/python-v1/01f/reconstructor/factory.py new file mode 100644 index 000000000..d0e60a792 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from core.registry import ComponentRegistry, register_component + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from core.registry import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python-v1/01f/reconstructor/weno3.py new file mode 100644 index 000000000..cc7d82df6 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/reconstructor/weno3.py @@ -0,0 +1,59 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/python-v1/01f/residual.py b/example/1d-linear-convection/weno3/python-v1/01f/residual.py new file mode 100644 index 000000000..e0f96cd3c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux.factory import FluxCalculatorFactory +from reconstructor import ReconstructorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + + self.reconstructor = ReconstructorFactory.create(self.config, self.domain) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._compute_face_values() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _compute_face_values(self): + """私有方法:界面值重建""" + self.reconstructor.compute_face_values(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/result.py b/example/1d-linear-convection/weno3/python-v1/01f/result.py new file mode 100644 index 000000000..5e572216d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/result.py @@ -0,0 +1,25 @@ +# result.py + +class ResultAssembler: + @staticmethod + def assemble(cfd: 'Cfd') -> dict: + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied].copy() + + # ✅ 从 problem 获取解析解(唯一正确路径) + try: + analytical = cfd.problem.exact_solution(cfd) + except NotImplementedError: + analytical = None # 或 np.full_like(u_numerical, np.nan) + + return { + "x": cfd.domain.mesh.xcc, + "numerical": u_numerical, + "analytical": analytical, + "config": { + "scheme": cfd.config.recon_scheme, + "order": cfd.config.spatial_order, + "rk_order": cfd.config.rk_order, + "final_time": cfd.config.final_time, + "problem": cfd.problem.name, + } + } \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/run_eno_weno.py b/example/1d-linear-convection/weno3/python-v1/01f/run_eno_weno.py new file mode 100644 index 000000000..4088266ec --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/run_eno_weno.py @@ -0,0 +1,51 @@ +# run_eno_weno.py + +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +from problems.linear_advection import LinearAdvectionProblem + + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行 ENO3 求解 + print("Running ENO3 solver...") + config_eno3 = CfdConfig() + config_eno3.with_reconstruction("eno", 3) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno3 = LinearAdvectionProblem(config_eno3) + cfd_eno3 = Cfd(problem_eno3, mesh) # ← 注入 problem + cfd_eno3.run() + + # 3. 配置并运行 WENO3 求解 + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno3 = LinearAdvectionProblem(config_weno3) + cfd_weno3 = Cfd(problem_weno3, mesh) # ← 注入 problem + cfd_weno3.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" + ) + + +if __name__ == "__main__": + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/solution.py b/example/1d-linear-convection/weno3/python-v1/01f/solution.py new file mode 100644 index 000000000..90666c34a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/solution.py @@ -0,0 +1,32 @@ +# solution.py +import numpy as np + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/solver.py b/example/1d-linear-convection/weno3/python-v1/01f/solver.py new file mode 100644 index 000000000..ee827a990 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/solver.py @@ -0,0 +1,67 @@ +# solver.py + +from mesh import Mesh +from domain import Domain +from solution import Solution +from config import CfdConfig +from result import ResultAssembler +from problem import Problem +from initial_condition import InitialConditionFactory +from boundary import BoundaryConditionFactory +from residual import ResidualCalculator +from time_integration import TimeIntegrator,TimeIntegratorFactory + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, problem: Problem, mesh): + self.problem = problem + self.config = problem.config + self.domain = Domain(problem.config, mesh) + self.solution = Solution(problem.config, self.domain) + + self.initial_condition = InitialConditionFactory.create(self.config) + self.boundary_condition = BoundaryConditionFactory.create(self) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + + # ==================== 公共接口(供 TimeIntegrator 调用) ==================== + def compute_residual(self): + """计算物理残差(封装重建→通量→散度)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件""" + self.boundary_condition.apply(self.solution.u) + + # ==================== 初始化 ==================== + def initialize(self): + """初始化全场:先 IC,再 BC,最后同步 old field""" + self.initial_condition.apply(self.solution) + # 应用边界条件到初始场 + self.apply_boundary() + # 同步 old field + self.solution.update_old_field() + + + def run(self): + self.initialize() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + self.integrator.step(dt) + t += dt + config.dt = dt_old + + self.result = ResultAssembler.assemble(self) + return self.result["numerical"] + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/time_integration/__init__.py b/example/1d-linear-convection/weno3/python-v1/01f/time_integration/__init__.py new file mode 100644 index 000000000..6342de3bb --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/time_integration/__init__.py @@ -0,0 +1,8 @@ +# time_integration/__init__.py + +# 导出统一接口 +from .factory import TimeIntegratorFactory +from .base import TimeIntegrator + +# 触发子模块注册(关键!) +from . import rk1, rk2, rk3 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/time_integration/base.py b/example/1d-linear-convection/weno3/python-v1/01f/time_integration/base.py new file mode 100644 index 000000000..0cdf29a48 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/time_integration/base.py @@ -0,0 +1,27 @@ +# time_integration/base.py + +from abc import ABC, abstractmethod + +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + + @abstractmethod + def step(self, dt): + pass + + def compute_residual(self): + """计算残差(委托给 Cfd)""" + self.cfd.compute_residual() + + def apply_boundary(self): + """应用边界条件(委托给 Cfd)""" + self.cfd.apply_boundary() + + def map_idx(self, i): + """物理网格索引 → 残差数组索引""" + return i - self.domain.ist \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/time_integration/factory.py b/example/1d-linear-convection/weno3/python-v1/01f/time_integration/factory.py new file mode 100644 index 000000000..94662db22 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/time_integration/factory.py @@ -0,0 +1,10 @@ +# time_integration/factory.py + +from core.registry import BaseFactory + +class TimeIntegratorFactory: + @staticmethod + def create(cfd) -> 'TimeIntegrator': + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/time_integration/rk1.py b/example/1d-linear-convection/weno3/python-v1/01f/time_integration/rk1.py new file mode 100644 index 000000000..b4c8a0211 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/time_integration/rk1.py @@ -0,0 +1,15 @@ +# time_integration/rk1.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/time_integration/rk2.py b/example/1d-linear-convection/weno3/python-v1/01f/time_integration/rk2.py new file mode 100644 index 000000000..6d2be3049 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/time_integration/rk2.py @@ -0,0 +1,29 @@ +# time_integration/rk2.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + 0.5 * self.solution.un[i] + + 0.5 * self.solution.u[i] + + 0.5 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01f/time_integration/rk3.py b/example/1d-linear-convection/weno3/python-v1/01f/time_integration/rk3.py new file mode 100644 index 000000000..c70791e13 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01f/time_integration/rk3.py @@ -0,0 +1,43 @@ +# time_integration/rk3.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # Stage 1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # Stage 2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = ( + 0.75 * self.solution.un[i] + + 0.25 * self.solution.u[i] + + 0.25 * dt * self.solution.res[j] + ) + self.solution.u[:] = u2 + self.apply_boundary() + + # Stage 3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + c1 * self.solution.un[i] + + c2 * self.solution.u[i] + + c3 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/boundary.py b/example/1d-linear-convection/weno3/python-v1/01g/boundary.py new file mode 100644 index 000000000..2d2cfbaf8 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/boundary.py @@ -0,0 +1,104 @@ +from abc import ABC, abstractmethod +from core.registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from core.registry import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) + diff --git a/example/1d-linear-convection/weno3/python-v1/01g/config.py b/example/1d-linear-convection/weno3/python-v1/01g/config.py new file mode 100644 index 000000000..6e33950f2 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/config.py @@ -0,0 +1,43 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + #self.ic_type = "sin" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # "rusanov", "engquist-osher" + #self.flux_type = "engquist-osher" # "rusanov", "engquist-osher" + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/core/registry.py b/example/1d-linear-convection/weno3/python-v1/01g/core/registry.py new file mode 100644 index 000000000..a6038a501 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/core/registry.py @@ -0,0 +1,79 @@ +# core/registry.py +""" +统一注册系统 + 通用工厂 +- ComponentRegistry: 组件注册表 +- register_component: 装饰器 +- BaseFactory: 通用工厂类 +""" + +from typing import Dict, Type, Any + + +# ==================== 1. 注册表核心 ==================== +class ComponentRegistry: + """组件注册表""" + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True + + @classmethod + def set_verbose(cls, verbose: bool): + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + if category not in cls._registries: + cls._registries[category] = {} + if name in cls._registries[category]: + if cls._registries[category][name] != component_class and cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + if category not in cls._registries: + raise ValueError(f"❌ 未知类别: {category} (可用: {list(cls._registries.keys())})") + if name not in cls._registries[category]: + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {list(cls._registries[category].keys())})") + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) for cat, comps in cls._registries.items()} + + +# ==================== 2. 装饰器 ==================== +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + +# ==================== 3. 通用工厂 ==================== +class BaseFactory: + """通用工厂基类""" + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + name_lower = name.lower() + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + available = ComponentRegistry.list_all().get(category, []) + if available: + error_msg = f"不支持的 {category} 类型 '{name}'。可用类型:{available}" + else: + error_msg = f"不支持的 {category} 类型 '{name}'(无已注册组件)" + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/domain.py b/example/1d-linear-convection/weno3/python-v1/01g/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/equations/base.py b/example/1d-linear-convection/weno3/python-v1/01g/equations/base.py new file mode 100644 index 000000000..92e54f126 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/equations/base.py @@ -0,0 +1,56 @@ +# equations/base.py + +from abc import ABC, abstractmethod +import numpy as np + +class Equation(ABC): + """ + 控制方程抽象基类 + 所有物理方程(线性对流、Euler、MHD)必须继承此类 + """ + + @property + @abstractmethod + def num_equations(self) -> int: + """返回方程组变量数(标量=1,Euler=3,MHD=8)""" + pass + + @abstractmethod + def flux(self, u): + """ + 计算通量函数 f(u) + :param u: 状态向量 (num_equations,) + :return: 通量向量 (num_equations,) + """ + pass + + @abstractmethod + def max_wave_speed(self, u): + """ + 计算最大波速(用于 CFL 条件) + :param u: 状态向量 + :return: 标量波速 + """ + pass + + @abstractmethod + def wave_speed(self): + """ + 计算波速(用于 CFL 条件) + :param u: 状态向量 + :return: 标量波速 + """ + pass + + def exact_solution(self, x, t, initial_condition_func): + """ + 可选:提供解析解 + 子类可重写;若无解析解,调用方应捕获 NotImplementedError + :param x: 空间坐标数组 (ncells,) + :param t: 时间 + :param initial_condition_func: 初值函数 u0(x) -> (num_equations, ncells) + :return: 解 u(x,t) -> (num_equations, ncells) + """ + raise NotImplementedError( + f"Equation '{self.__class__.__name__}' does not provide an exact solution." + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/equations/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/01g/equations/linear_advection.py new file mode 100644 index 000000000..26aa026be --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/equations/linear_advection.py @@ -0,0 +1,42 @@ +# equations/linear_advection.py + +import numpy as np +from .base import Equation + +class LinearAdvectionEquation(Equation): + """ + 线性对流方程: u_t + c * u_x = 0 + 支持标量(num_equations=1) + """ + + def __init__(self, wave_speed: float): + self.c = wave_speed + + @property + def num_equations(self) -> int: + return 1 + + def flux(self, u): + """f(u) = c * u""" + return self.c * u + + def max_wave_speed(self, u): + """最大波速 = |c|""" + return abs(self.c) + + def wave_speed(self): + return self.c + + + def exact_solution(self, x, t, initial_condition_func): + """ + 解析解: u(x, t) = u0(x - c * t) + 支持周期边界 + """ + if len(x) == 0: + return np.zeros((1, 0)) + + L = x[-1] - x[0] # 假设均匀周期网格 + x_shifted = (x - self.c * t + L) % L + u0 = initial_condition_func(x_shifted) # (1, ncells) + return u0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/flux/__init__.py b/example/1d-linear-convection/weno3/python-v1/01g/flux/__init__.py new file mode 100644 index 000000000..432a36cb0 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/flux/__init__.py @@ -0,0 +1,7 @@ +# flux/__init__.py +from .base import InviscidFluxCalculator +from .factory import FluxCalculatorFactory + +# 确保子模块被导入以触发注册 +from . import rusanov +from . import engquist_osher \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/flux/base.py b/example/1d-linear-convection/weno3/python-v1/01g/flux/base.py new file mode 100644 index 000000000..7a5a134fc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/flux/base.py @@ -0,0 +1,25 @@ +# flux/base.py +""" +抽象通量计算基类 +""" + +from abc import ABC, abstractmethod + +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/flux/engquist_osher.py b/example/1d-linear-convection/weno3/python-v1/01g/flux/engquist_osher.py new file mode 100644 index 000000000..eb7b8cdf7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/flux/engquist_osher.py @@ -0,0 +1,20 @@ +# flux/engquist_osher.py +""" +Engquist-Osher 通量计算器(线性对流专用) +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + eq = self.cfd.problem.equation + for i in range(self.mesh.nnodes): + c = eq.wave_speed() + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/flux/factory.py b/example/1d-linear-convection/weno3/python-v1/01g/flux/factory.py new file mode 100644 index 000000000..5ae77c3fa --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/flux/factory.py @@ -0,0 +1,25 @@ +# flux/factory.py +""" +通量计算器专用工厂(封装注册细节,提供清晰接口) +符合你希望“将创建逻辑封装在工厂中”的设计原则 +""" + +from core.registry import BaseFactory + +class FluxCalculatorFactory: + """通量计算器工厂:隐藏注册类别和组件名称等细节""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD 主对象,包含 config.flux_type 等配置 + + Returns: + 实现 InviscidFluxCalculator 接口的通量计算器实例 + """ + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/flux/rusanov.py b/example/1d-linear-convection/weno3/python-v1/01g/flux/rusanov.py new file mode 100644 index 000000000..baa7c62c5 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/flux/rusanov.py @@ -0,0 +1,25 @@ +# flux/rusanov.py +""" +Rusanov(Lax-Friedrichs)通量计算器 +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量,使用 Equation 解耦物理参数""" + + def compute(self, q_face_left, q_face_right, flux): + # 从 cfd 获取 equation(与 Julia 对齐) + eq = self.cfd.problem.equation + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = eq.wave_speed() + c_R = eq.wave_speed() + # 通过 equation 计算通量和波速 + F_L = eq.flux(u_L) + F_R = eq.flux(u_R) + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/initial_condition.py b/example/1d-linear-convection/weno3/python-v1/01g/initial_condition.py new file mode 100644 index 000000000..dda8a3bd4 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/initial_condition.py @@ -0,0 +1,90 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from core.registry import ComponentRegistry, register_component + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from core.registry import BaseFactory + return BaseFactory.create_component('initial_condition', config.ic_type, config) + diff --git a/example/1d-linear-convection/weno3/python-v1/01g/mesh.py b/example/1d-linear-convection/weno3/python-v1/01g/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/plotter.py b/example/1d-linear-convection/weno3/python-v1/01g/plotter.py new file mode 100644 index 000000000..e1f99ae25 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/plotter.py @@ -0,0 +1,109 @@ +# plotter.py + +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/problem.py b/example/1d-linear-convection/weno3/python-v1/01g/problem.py new file mode 100644 index 000000000..53719f265 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/problem.py @@ -0,0 +1,38 @@ +# problem.py +""" +问题定义模块:每个 Problem 子类代表一个完整测试用例 +包含初始条件、解析解(可选)、物理方程等 +""" + +from abc import ABC, abstractmethod +from initial_condition import InitialConditionFactory + + +class Problem(ABC): + """ + 抽象问题基类 + 每个具体问题(如线性对流、Sod 激波管)应继承此类 + """ + def __init__(self, config): + self.config = config + + @abstractmethod + def initial_condition(self): + """ + 返回 InitialCondition 实例 + """ + pass + + def exact_solution(self, cfd): + """ + 可选:返回解析解(数值数组) + 若无解析解,可抛出 NotImplementedError 或返回 None + """ + x = cfd.domain.mesh.xcc + raise NotImplementedError( + f"Problem '{self.__class__.__name__}' does not provide an exact solution." + ) + + @property + def name(self): + return self.__class__.__name__ \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/problems/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/01g/problems/linear_advection.py new file mode 100644 index 000000000..b6d548f51 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/problems/linear_advection.py @@ -0,0 +1,39 @@ +# problems/linear_advection.py + +import numpy as np +from problem import Problem +from equations.linear_advection import LinearAdvectionEquation +from initial_condition import InitialConditionFactory + +class LinearAdvectionProblem(Problem): + """ + 线性对流问题:u_t + c u_x = 0 + 使用周期边界 + 任意初始条件 + """ + + def __init__(self, config): + super().__init__(config) + # 持有物理方程实例 + self.equation = LinearAdvectionEquation(wave_speed=config.wave_speed) + self._ic_cache = None # 缓存 IC 实例 + + def initial_condition(self): + """返回初始条件实例""" + if self._ic_cache is None: + self._ic_cache = InitialConditionFactory.create(self.config) + return self._ic_cache + + def exact_solution(self, cfd): + """ + 委托给 Equation 计算解析解 + """ + ic = self.initial_condition() + # 包装 evaluate_at 以返回 (1, ncells) + def vectorized_ic(x): + u0_scalar = ic.evaluate_at(x) # (ncells,) + return u0_scalar[np.newaxis, :] # (1, ncells) + + x = cfd.domain.mesh.xcc + t = cfd.config.final_time + u_exact = self.equation.exact_solution(x, t, vectorized_ic) + return u_exact[0] # 返回标量数组 (ncells,) 以兼容现有绘图逻辑 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python-v1/01g/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/reconstructor/base.py b/example/1d-linear-convection/weno3/python-v1/01g/reconstructor/base.py new file mode 100644 index 000000000..094b47120 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def compute_face_values(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/reconstructor/eno.py b/example/1d-linear-convection/weno3/python-v1/01g/reconstructor/eno.py new file mode 100644 index 000000000..4c8fc1e4e --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def compute_face_values(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/reconstructor/factory.py b/example/1d-linear-convection/weno3/python-v1/01g/reconstructor/factory.py new file mode 100644 index 000000000..d0e60a792 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from core.registry import ComponentRegistry, register_component + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from core.registry import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python-v1/01g/reconstructor/weno3.py new file mode 100644 index 000000000..cc7d82df6 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/reconstructor/weno3.py @@ -0,0 +1,59 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/python-v1/01g/reconstructor/weno5.py b/example/1d-linear-convection/weno3/python-v1/01g/reconstructor/weno5.py new file mode 100644 index 000000000..415fcfc35 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/reconstructor/weno5.py @@ -0,0 +1,68 @@ +# reconstructor/weno5.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno5') +class Weno5Reconstructor(Reconstructor): + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-2], u[i-1], u[i], u[i+1], u[i+2] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3, v4, v5) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-2], u[i-1], u[i], u[i+1], u[i+2] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3, v4, v5) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3, v4, v5): + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)**2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)**2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)**2 + (1.0/4.0)*(v2 - v4)**2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)**2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)**2 + d0 = 1.0/10.0 + d1 = 3.0/5.0 + d2 = 3.0/10.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha2 = d2 / (eps + beta2)**2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = 1.0/3.0*v1-7.0/6.0*v2+11.0/6.0*v3 # r=2 + q1 = -1.0/6.0*v2+5.0/6.0*v3+1.0/3.0*v4 # r=1 + q2 = 1.0/3.0*v3+5.0/6.0*v4-1.0/6.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3, v4, v5): + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)**2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)**2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)**2 + (1.0/4.0)*(v2 - v4)**2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)**2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)**2 + d0 = 3.0/10.0 + d1 = 3.0/5.0 + d2 = 1.0/10.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha2 = d2 / (eps + beta2)**2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = -1.0/6.0*v1+5.0/6.0*v2+1.0/3.0*v3 # r=2 + q1 = 1.0/3.0*v2+5.0/6.0*v3-1.0/6.0*v4 # r=1 + q2 = 11.0/6.0*v3-7.0/6.0*v4+1.0/3.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 diff --git a/example/1d-linear-convection/weno3/python-v1/01g/residual.py b/example/1d-linear-convection/weno3/python-v1/01g/residual.py new file mode 100644 index 000000000..e0f96cd3c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux.factory import FluxCalculatorFactory +from reconstructor import ReconstructorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + + self.reconstructor = ReconstructorFactory.create(self.config, self.domain) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._compute_face_values() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _compute_face_values(self): + """私有方法:界面值重建""" + self.reconstructor.compute_face_values(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/result.py b/example/1d-linear-convection/weno3/python-v1/01g/result.py new file mode 100644 index 000000000..5e572216d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/result.py @@ -0,0 +1,25 @@ +# result.py + +class ResultAssembler: + @staticmethod + def assemble(cfd: 'Cfd') -> dict: + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied].copy() + + # ✅ 从 problem 获取解析解(唯一正确路径) + try: + analytical = cfd.problem.exact_solution(cfd) + except NotImplementedError: + analytical = None # 或 np.full_like(u_numerical, np.nan) + + return { + "x": cfd.domain.mesh.xcc, + "numerical": u_numerical, + "analytical": analytical, + "config": { + "scheme": cfd.config.recon_scheme, + "order": cfd.config.spatial_order, + "rk_order": cfd.config.rk_order, + "final_time": cfd.config.final_time, + "problem": cfd.problem.name, + } + } \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/run_eno_weno.py b/example/1d-linear-convection/weno3/python-v1/01g/run_eno_weno.py new file mode 100644 index 000000000..4088266ec --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/run_eno_weno.py @@ -0,0 +1,51 @@ +# run_eno_weno.py + +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +from problems.linear_advection import LinearAdvectionProblem + + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行 ENO3 求解 + print("Running ENO3 solver...") + config_eno3 = CfdConfig() + config_eno3.with_reconstruction("eno", 3) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno3 = LinearAdvectionProblem(config_eno3) + cfd_eno3 = Cfd(problem_eno3, mesh) # ← 注入 problem + cfd_eno3.run() + + # 3. 配置并运行 WENO3 求解 + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno3 = LinearAdvectionProblem(config_weno3) + cfd_weno3 = Cfd(problem_weno3, mesh) # ← 注入 problem + cfd_weno3.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" + ) + + +if __name__ == "__main__": + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/solution.py b/example/1d-linear-convection/weno3/python-v1/01g/solution.py new file mode 100644 index 000000000..90666c34a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/solution.py @@ -0,0 +1,32 @@ +# solution.py +import numpy as np + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/solver.py b/example/1d-linear-convection/weno3/python-v1/01g/solver.py new file mode 100644 index 000000000..316be231e --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/solver.py @@ -0,0 +1,67 @@ +# solver.py + +from mesh import Mesh +from domain import Domain +from solution import Solution +from config import CfdConfig +from result import ResultAssembler +from problem import Problem +from initial_condition import InitialConditionFactory +from boundary import BoundaryConditionFactory +from residual import ResidualCalculator +from time_integration import TimeIntegrator,TimeIntegratorFactory + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, problem: Problem, mesh): + self.problem = problem + self.config = problem.config + self.domain = Domain(problem.config, mesh) + self.solution = Solution(problem.config, self.domain) + + self.initial_condition = InitialConditionFactory.create(self.config) + self.boundary_condition = BoundaryConditionFactory.create(self) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + + # ==================== 公共接口(供 TimeIntegrator 调用) ==================== + def compute_residual(self): + """计算物理残差(封装重建→通量→散度)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件""" + self.boundary_condition.apply(self.solution.u) + + # ==================== 初始化 ==================== + def initialize(self): + """初始化全场:先 IC,再 BC,最后同步 old field""" + self.initial_condition.apply(self.solution) + # 应用边界条件到初始场 + self.apply_boundary() + # 同步 old field + self.solution.update_old_field() + + + def run(self): + self.initialize() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + self.integrator.step(dt) + t += dt + config.dt = dt_old + + self.result = ResultAssembler.assemble(self) + return self.result["numerical"] + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/time_integration/__init__.py b/example/1d-linear-convection/weno3/python-v1/01g/time_integration/__init__.py new file mode 100644 index 000000000..6342de3bb --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/time_integration/__init__.py @@ -0,0 +1,8 @@ +# time_integration/__init__.py + +# 导出统一接口 +from .factory import TimeIntegratorFactory +from .base import TimeIntegrator + +# 触发子模块注册(关键!) +from . import rk1, rk2, rk3 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/time_integration/base.py b/example/1d-linear-convection/weno3/python-v1/01g/time_integration/base.py new file mode 100644 index 000000000..0cdf29a48 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/time_integration/base.py @@ -0,0 +1,27 @@ +# time_integration/base.py + +from abc import ABC, abstractmethod + +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + + @abstractmethod + def step(self, dt): + pass + + def compute_residual(self): + """计算残差(委托给 Cfd)""" + self.cfd.compute_residual() + + def apply_boundary(self): + """应用边界条件(委托给 Cfd)""" + self.cfd.apply_boundary() + + def map_idx(self, i): + """物理网格索引 → 残差数组索引""" + return i - self.domain.ist \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/time_integration/factory.py b/example/1d-linear-convection/weno3/python-v1/01g/time_integration/factory.py new file mode 100644 index 000000000..94662db22 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/time_integration/factory.py @@ -0,0 +1,10 @@ +# time_integration/factory.py + +from core.registry import BaseFactory + +class TimeIntegratorFactory: + @staticmethod + def create(cfd) -> 'TimeIntegrator': + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/time_integration/rk1.py b/example/1d-linear-convection/weno3/python-v1/01g/time_integration/rk1.py new file mode 100644 index 000000000..b4c8a0211 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/time_integration/rk1.py @@ -0,0 +1,15 @@ +# time_integration/rk1.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/time_integration/rk2.py b/example/1d-linear-convection/weno3/python-v1/01g/time_integration/rk2.py new file mode 100644 index 000000000..6d2be3049 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/time_integration/rk2.py @@ -0,0 +1,29 @@ +# time_integration/rk2.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + 0.5 * self.solution.un[i] + + 0.5 * self.solution.u[i] + + 0.5 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01g/time_integration/rk3.py b/example/1d-linear-convection/weno3/python-v1/01g/time_integration/rk3.py new file mode 100644 index 000000000..c70791e13 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01g/time_integration/rk3.py @@ -0,0 +1,43 @@ +# time_integration/rk3.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # Stage 1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # Stage 2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = ( + 0.75 * self.solution.un[i] + + 0.25 * self.solution.u[i] + + 0.25 * dt * self.solution.res[j] + ) + self.solution.u[:] = u2 + self.apply_boundary() + + # Stage 3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + c1 * self.solution.un[i] + + c2 * self.solution.u[i] + + c3 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/boundary.py b/example/1d-linear-convection/weno3/python-v1/01h/boundary.py new file mode 100644 index 000000000..2d2cfbaf8 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/boundary.py @@ -0,0 +1,104 @@ +from abc import ABC, abstractmethod +from core.registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from core.registry import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) + diff --git a/example/1d-linear-convection/weno3/python-v1/01h/config.py b/example/1d-linear-convection/weno3/python-v1/01h/config.py new file mode 100644 index 000000000..6432c80e9 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/config.py @@ -0,0 +1,45 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + #self.ic_type = "sin" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # "rusanov", "engquist-osher" + #self.flux_type = "engquist-osher" # "rusanov", "engquist-osher" + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + print(f"scheme={scheme}") + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/core/registry.py b/example/1d-linear-convection/weno3/python-v1/01h/core/registry.py new file mode 100644 index 000000000..4a52afc26 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/core/registry.py @@ -0,0 +1,83 @@ +# core/registry.py +""" +统一注册系统 + 通用工厂 +- ComponentRegistry: 组件注册表 +- register_component: 装饰器 +- BaseFactory: 通用工厂类 +""" + +from typing import Dict, Type, Any + + +# ==================== 1. 注册表核心 ==================== +class ComponentRegistry: + """组件注册表""" + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True + + @classmethod + def set_verbose(cls, verbose: bool): + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + #print(f"ComponentRegistry register cls={cls}") + #print(f"ComponentRegistry register name={name}") + #print(f"ComponentRegistry register component_class={component_class}") + if category not in cls._registries: + cls._registries[category] = {} + if name in cls._registries[category]: + if cls._registries[category][name] != component_class and cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + if category not in cls._registries: + raise ValueError(f"❌ 未知类别: {category} (可用: {list(cls._registries.keys())})") + if name not in cls._registries[category]: + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {list(cls._registries[category].keys())})") + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) for cat, comps in cls._registries.items()} + + +# ==================== 2. 装饰器 ==================== +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + #print(f"register_component decorator name={name}") + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + +# ==================== 3. 通用工厂 ==================== +class BaseFactory: + """通用工厂基类""" + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + name_lower = name.lower() + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + available = ComponentRegistry.list_all().get(category, []) + if available: + error_msg = f"不支持的 {category} 类型 '{name}'。可用类型:{available}" + else: + error_msg = f"不支持的 {category} 类型 '{name}'(无已注册组件)" + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/domain.py b/example/1d-linear-convection/weno3/python-v1/01h/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/equations/base.py b/example/1d-linear-convection/weno3/python-v1/01h/equations/base.py new file mode 100644 index 000000000..92e54f126 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/equations/base.py @@ -0,0 +1,56 @@ +# equations/base.py + +from abc import ABC, abstractmethod +import numpy as np + +class Equation(ABC): + """ + 控制方程抽象基类 + 所有物理方程(线性对流、Euler、MHD)必须继承此类 + """ + + @property + @abstractmethod + def num_equations(self) -> int: + """返回方程组变量数(标量=1,Euler=3,MHD=8)""" + pass + + @abstractmethod + def flux(self, u): + """ + 计算通量函数 f(u) + :param u: 状态向量 (num_equations,) + :return: 通量向量 (num_equations,) + """ + pass + + @abstractmethod + def max_wave_speed(self, u): + """ + 计算最大波速(用于 CFL 条件) + :param u: 状态向量 + :return: 标量波速 + """ + pass + + @abstractmethod + def wave_speed(self): + """ + 计算波速(用于 CFL 条件) + :param u: 状态向量 + :return: 标量波速 + """ + pass + + def exact_solution(self, x, t, initial_condition_func): + """ + 可选:提供解析解 + 子类可重写;若无解析解,调用方应捕获 NotImplementedError + :param x: 空间坐标数组 (ncells,) + :param t: 时间 + :param initial_condition_func: 初值函数 u0(x) -> (num_equations, ncells) + :return: 解 u(x,t) -> (num_equations, ncells) + """ + raise NotImplementedError( + f"Equation '{self.__class__.__name__}' does not provide an exact solution." + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/equations/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/01h/equations/linear_advection.py new file mode 100644 index 000000000..26aa026be --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/equations/linear_advection.py @@ -0,0 +1,42 @@ +# equations/linear_advection.py + +import numpy as np +from .base import Equation + +class LinearAdvectionEquation(Equation): + """ + 线性对流方程: u_t + c * u_x = 0 + 支持标量(num_equations=1) + """ + + def __init__(self, wave_speed: float): + self.c = wave_speed + + @property + def num_equations(self) -> int: + return 1 + + def flux(self, u): + """f(u) = c * u""" + return self.c * u + + def max_wave_speed(self, u): + """最大波速 = |c|""" + return abs(self.c) + + def wave_speed(self): + return self.c + + + def exact_solution(self, x, t, initial_condition_func): + """ + 解析解: u(x, t) = u0(x - c * t) + 支持周期边界 + """ + if len(x) == 0: + return np.zeros((1, 0)) + + L = x[-1] - x[0] # 假设均匀周期网格 + x_shifted = (x - self.c * t + L) % L + u0 = initial_condition_func(x_shifted) # (1, ncells) + return u0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/flux/__init__.py b/example/1d-linear-convection/weno3/python-v1/01h/flux/__init__.py new file mode 100644 index 000000000..432a36cb0 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/flux/__init__.py @@ -0,0 +1,7 @@ +# flux/__init__.py +from .base import InviscidFluxCalculator +from .factory import FluxCalculatorFactory + +# 确保子模块被导入以触发注册 +from . import rusanov +from . import engquist_osher \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/flux/base.py b/example/1d-linear-convection/weno3/python-v1/01h/flux/base.py new file mode 100644 index 000000000..7a5a134fc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/flux/base.py @@ -0,0 +1,25 @@ +# flux/base.py +""" +抽象通量计算基类 +""" + +from abc import ABC, abstractmethod + +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/flux/engquist_osher.py b/example/1d-linear-convection/weno3/python-v1/01h/flux/engquist_osher.py new file mode 100644 index 000000000..eb7b8cdf7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/flux/engquist_osher.py @@ -0,0 +1,20 @@ +# flux/engquist_osher.py +""" +Engquist-Osher 通量计算器(线性对流专用) +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + eq = self.cfd.problem.equation + for i in range(self.mesh.nnodes): + c = eq.wave_speed() + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/flux/factory.py b/example/1d-linear-convection/weno3/python-v1/01h/flux/factory.py new file mode 100644 index 000000000..5ae77c3fa --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/flux/factory.py @@ -0,0 +1,25 @@ +# flux/factory.py +""" +通量计算器专用工厂(封装注册细节,提供清晰接口) +符合你希望“将创建逻辑封装在工厂中”的设计原则 +""" + +from core.registry import BaseFactory + +class FluxCalculatorFactory: + """通量计算器工厂:隐藏注册类别和组件名称等细节""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD 主对象,包含 config.flux_type 等配置 + + Returns: + 实现 InviscidFluxCalculator 接口的通量计算器实例 + """ + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/flux/rusanov.py b/example/1d-linear-convection/weno3/python-v1/01h/flux/rusanov.py new file mode 100644 index 000000000..baa7c62c5 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/flux/rusanov.py @@ -0,0 +1,25 @@ +# flux/rusanov.py +""" +Rusanov(Lax-Friedrichs)通量计算器 +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量,使用 Equation 解耦物理参数""" + + def compute(self, q_face_left, q_face_right, flux): + # 从 cfd 获取 equation(与 Julia 对齐) + eq = self.cfd.problem.equation + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = eq.wave_speed() + c_R = eq.wave_speed() + # 通过 equation 计算通量和波速 + F_L = eq.flux(u_L) + F_R = eq.flux(u_R) + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/initial_condition.py b/example/1d-linear-convection/weno3/python-v1/01h/initial_condition.py new file mode 100644 index 000000000..dda8a3bd4 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/initial_condition.py @@ -0,0 +1,90 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from core.registry import ComponentRegistry, register_component + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from core.registry import BaseFactory + return BaseFactory.create_component('initial_condition', config.ic_type, config) + diff --git a/example/1d-linear-convection/weno3/python-v1/01h/mesh.py b/example/1d-linear-convection/weno3/python-v1/01h/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/plotter.py b/example/1d-linear-convection/weno3/python-v1/01h/plotter.py new file mode 100644 index 000000000..e1f99ae25 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/plotter.py @@ -0,0 +1,109 @@ +# plotter.py + +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/problem.py b/example/1d-linear-convection/weno3/python-v1/01h/problem.py new file mode 100644 index 000000000..53719f265 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/problem.py @@ -0,0 +1,38 @@ +# problem.py +""" +问题定义模块:每个 Problem 子类代表一个完整测试用例 +包含初始条件、解析解(可选)、物理方程等 +""" + +from abc import ABC, abstractmethod +from initial_condition import InitialConditionFactory + + +class Problem(ABC): + """ + 抽象问题基类 + 每个具体问题(如线性对流、Sod 激波管)应继承此类 + """ + def __init__(self, config): + self.config = config + + @abstractmethod + def initial_condition(self): + """ + 返回 InitialCondition 实例 + """ + pass + + def exact_solution(self, cfd): + """ + 可选:返回解析解(数值数组) + 若无解析解,可抛出 NotImplementedError 或返回 None + """ + x = cfd.domain.mesh.xcc + raise NotImplementedError( + f"Problem '{self.__class__.__name__}' does not provide an exact solution." + ) + + @property + def name(self): + return self.__class__.__name__ \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/problems/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/01h/problems/linear_advection.py new file mode 100644 index 000000000..b6d548f51 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/problems/linear_advection.py @@ -0,0 +1,39 @@ +# problems/linear_advection.py + +import numpy as np +from problem import Problem +from equations.linear_advection import LinearAdvectionEquation +from initial_condition import InitialConditionFactory + +class LinearAdvectionProblem(Problem): + """ + 线性对流问题:u_t + c u_x = 0 + 使用周期边界 + 任意初始条件 + """ + + def __init__(self, config): + super().__init__(config) + # 持有物理方程实例 + self.equation = LinearAdvectionEquation(wave_speed=config.wave_speed) + self._ic_cache = None # 缓存 IC 实例 + + def initial_condition(self): + """返回初始条件实例""" + if self._ic_cache is None: + self._ic_cache = InitialConditionFactory.create(self.config) + return self._ic_cache + + def exact_solution(self, cfd): + """ + 委托给 Equation 计算解析解 + """ + ic = self.initial_condition() + # 包装 evaluate_at 以返回 (1, ncells) + def vectorized_ic(x): + u0_scalar = ic.evaluate_at(x) # (ncells,) + return u0_scalar[np.newaxis, :] # (1, ncells) + + x = cfd.domain.mesh.xcc + t = cfd.config.final_time + u_exact = self.equation.exact_solution(x, t, vectorized_ic) + return u_exact[0] # 返回标量数组 (ncells,) 以兼容现有绘图逻辑 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python-v1/01h/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/reconstructor/base.py b/example/1d-linear-convection/weno3/python-v1/01h/reconstructor/base.py new file mode 100644 index 000000000..094b47120 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def compute_face_values(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/reconstructor/eno.py b/example/1d-linear-convection/weno3/python-v1/01h/reconstructor/eno.py new file mode 100644 index 000000000..4c8fc1e4e --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def compute_face_values(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/reconstructor/factory.py b/example/1d-linear-convection/weno3/python-v1/01h/reconstructor/factory.py new file mode 100644 index 000000000..9e7f46804 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/reconstructor/factory.py @@ -0,0 +1,36 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +#from .weno3 import Weno3Reconstructor +from .weno5 import Weno5Reconstructor +from core.registry import ComponentRegistry, register_component + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + print(f"ReconstructorFactory scheme={scheme}") + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + print(f"ReconstructorFactory scheme={scheme}") + # 使用BaseFactory,但处理特殊参数 + from core.registry import BaseFactory + + print(f"scheme={scheme}") + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3" or scheme == "weno5": + # WENO3,WENO5无参数 + print(f"scheme == 'eno3' or scheme == 'weno5' scheme={scheme}") + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python-v1/01h/reconstructor/weno3.py new file mode 100644 index 000000000..cc7d82df6 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/reconstructor/weno3.py @@ -0,0 +1,59 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/python-v1/01h/reconstructor/weno5.py b/example/1d-linear-convection/weno3/python-v1/01h/reconstructor/weno5.py new file mode 100644 index 000000000..af5ca8099 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/reconstructor/weno5.py @@ -0,0 +1,68 @@ +# reconstructor/weno5.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno5') +class Weno5Reconstructor(Reconstructor): + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3, v4, v5 = u[i-2], u[i-1], u[i], u[i+1], u[i+2] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3, v4, v5) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3, v4, v5 = u[i-2], u[i-1], u[i], u[i+1], u[i+2] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3, v4, v5) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3, v4, v5): + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)**2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)**2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)**2 + (1.0/4.0)*(v2 - v4)**2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)**2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)**2 + d0 = 1.0/10.0 + d1 = 3.0/5.0 + d2 = 3.0/10.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha2 = d2 / (eps + beta2)**2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = 1.0/3.0*v1-7.0/6.0*v2+11.0/6.0*v3 # r=2 + q1 = -1.0/6.0*v2+5.0/6.0*v3+1.0/3.0*v4 # r=1 + q2 = 1.0/3.0*v3+5.0/6.0*v4-1.0/6.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3, v4, v5): + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)**2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)**2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)**2 + (1.0/4.0)*(v2 - v4)**2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)**2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)**2 + d0 = 3.0/10.0 + d1 = 3.0/5.0 + d2 = 1.0/10.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha2 = d2 / (eps + beta2)**2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = -1.0/6.0*v1+5.0/6.0*v2+1.0/3.0*v3 # r=2 + q1 = 1.0/3.0*v2+5.0/6.0*v3-1.0/6.0*v4 # r=1 + q2 = 11.0/6.0*v3-7.0/6.0*v4+1.0/3.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 diff --git a/example/1d-linear-convection/weno3/python-v1/01h/residual.py b/example/1d-linear-convection/weno3/python-v1/01h/residual.py new file mode 100644 index 000000000..e0f96cd3c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux.factory import FluxCalculatorFactory +from reconstructor import ReconstructorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + + self.reconstructor = ReconstructorFactory.create(self.config, self.domain) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._compute_face_values() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _compute_face_values(self): + """私有方法:界面值重建""" + self.reconstructor.compute_face_values(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/result.py b/example/1d-linear-convection/weno3/python-v1/01h/result.py new file mode 100644 index 000000000..5e572216d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/result.py @@ -0,0 +1,25 @@ +# result.py + +class ResultAssembler: + @staticmethod + def assemble(cfd: 'Cfd') -> dict: + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied].copy() + + # ✅ 从 problem 获取解析解(唯一正确路径) + try: + analytical = cfd.problem.exact_solution(cfd) + except NotImplementedError: + analytical = None # 或 np.full_like(u_numerical, np.nan) + + return { + "x": cfd.domain.mesh.xcc, + "numerical": u_numerical, + "analytical": analytical, + "config": { + "scheme": cfd.config.recon_scheme, + "order": cfd.config.spatial_order, + "rk_order": cfd.config.rk_order, + "final_time": cfd.config.final_time, + "problem": cfd.problem.name, + } + } \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/run_eno_weno.py b/example/1d-linear-convection/weno3/python-v1/01h/run_eno_weno.py new file mode 100644 index 000000000..ea83cbe84 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/run_eno_weno.py @@ -0,0 +1,88 @@ +# run_eno_weno.py + +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +from problems.linear_advection import LinearAdvectionProblem + + +def performEnoWenoAnalysisBAK(): + # 1. 初始化网格 + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行 ENO3 求解 + print("Running ENO3 solver...") + config_eno3 = CfdConfig() + config_eno3.with_reconstruction("eno", 3) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno3 = LinearAdvectionProblem(config_eno3) + cfd_eno3 = Cfd(problem_eno3, mesh) # ← 注入 problem + cfd_eno3.run() + + # 3. 配置并运行 WENO3 求解 + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno3 = LinearAdvectionProblem(config_weno3) + cfd_weno3 = Cfd(problem_weno3, mesh) # ← 注入 problem + cfd_weno3.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" + ) + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行 ENO5 求解 + print("Running ENO5 solver...") + config_eno5 = CfdConfig() + config_eno5.with_reconstruction("eno", 5) + config_eno5.dt = 0.0025 + config_eno5.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno5 = LinearAdvectionProblem(config_eno5) + cfd_eno5 = Cfd(problem_eno5, mesh) # ← 注入 problem + cfd_eno5.run() + + # 3. 配置并运行 WENO5求解 + print("Running WENO5 solver...") + config_weno5 = CfdConfig() + config_weno5.with_reconstruction("weno", 5) + config_weno5.dt = 0.0025 + config_weno5.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno5 = LinearAdvectionProblem(config_weno5) + cfd_weno5 = Cfd(problem_weno5, mesh) # ← 注入 problem + cfd_weno5.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno5.result, + weno_result=cfd_weno5.result, + save_path="eno_weno_comparison.png" + ) + + +if __name__ == "__main__": + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/solution.py b/example/1d-linear-convection/weno3/python-v1/01h/solution.py new file mode 100644 index 000000000..90666c34a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/solution.py @@ -0,0 +1,32 @@ +# solution.py +import numpy as np + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/solver.py b/example/1d-linear-convection/weno3/python-v1/01h/solver.py new file mode 100644 index 000000000..316be231e --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/solver.py @@ -0,0 +1,67 @@ +# solver.py + +from mesh import Mesh +from domain import Domain +from solution import Solution +from config import CfdConfig +from result import ResultAssembler +from problem import Problem +from initial_condition import InitialConditionFactory +from boundary import BoundaryConditionFactory +from residual import ResidualCalculator +from time_integration import TimeIntegrator,TimeIntegratorFactory + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, problem: Problem, mesh): + self.problem = problem + self.config = problem.config + self.domain = Domain(problem.config, mesh) + self.solution = Solution(problem.config, self.domain) + + self.initial_condition = InitialConditionFactory.create(self.config) + self.boundary_condition = BoundaryConditionFactory.create(self) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + + # ==================== 公共接口(供 TimeIntegrator 调用) ==================== + def compute_residual(self): + """计算物理残差(封装重建→通量→散度)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件""" + self.boundary_condition.apply(self.solution.u) + + # ==================== 初始化 ==================== + def initialize(self): + """初始化全场:先 IC,再 BC,最后同步 old field""" + self.initial_condition.apply(self.solution) + # 应用边界条件到初始场 + self.apply_boundary() + # 同步 old field + self.solution.update_old_field() + + + def run(self): + self.initialize() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + self.integrator.step(dt) + t += dt + config.dt = dt_old + + self.result = ResultAssembler.assemble(self) + return self.result["numerical"] + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/time_integration/__init__.py b/example/1d-linear-convection/weno3/python-v1/01h/time_integration/__init__.py new file mode 100644 index 000000000..6342de3bb --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/time_integration/__init__.py @@ -0,0 +1,8 @@ +# time_integration/__init__.py + +# 导出统一接口 +from .factory import TimeIntegratorFactory +from .base import TimeIntegrator + +# 触发子模块注册(关键!) +from . import rk1, rk2, rk3 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/time_integration/base.py b/example/1d-linear-convection/weno3/python-v1/01h/time_integration/base.py new file mode 100644 index 000000000..0cdf29a48 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/time_integration/base.py @@ -0,0 +1,27 @@ +# time_integration/base.py + +from abc import ABC, abstractmethod + +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + + @abstractmethod + def step(self, dt): + pass + + def compute_residual(self): + """计算残差(委托给 Cfd)""" + self.cfd.compute_residual() + + def apply_boundary(self): + """应用边界条件(委托给 Cfd)""" + self.cfd.apply_boundary() + + def map_idx(self, i): + """物理网格索引 → 残差数组索引""" + return i - self.domain.ist \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/time_integration/factory.py b/example/1d-linear-convection/weno3/python-v1/01h/time_integration/factory.py new file mode 100644 index 000000000..94662db22 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/time_integration/factory.py @@ -0,0 +1,10 @@ +# time_integration/factory.py + +from core.registry import BaseFactory + +class TimeIntegratorFactory: + @staticmethod + def create(cfd) -> 'TimeIntegrator': + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/time_integration/rk1.py b/example/1d-linear-convection/weno3/python-v1/01h/time_integration/rk1.py new file mode 100644 index 000000000..b4c8a0211 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/time_integration/rk1.py @@ -0,0 +1,15 @@ +# time_integration/rk1.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/time_integration/rk2.py b/example/1d-linear-convection/weno3/python-v1/01h/time_integration/rk2.py new file mode 100644 index 000000000..6d2be3049 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/time_integration/rk2.py @@ -0,0 +1,29 @@ +# time_integration/rk2.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + 0.5 * self.solution.un[i] + + 0.5 * self.solution.u[i] + + 0.5 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01h/time_integration/rk3.py b/example/1d-linear-convection/weno3/python-v1/01h/time_integration/rk3.py new file mode 100644 index 000000000..c70791e13 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01h/time_integration/rk3.py @@ -0,0 +1,43 @@ +# time_integration/rk3.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # Stage 1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # Stage 2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = ( + 0.75 * self.solution.un[i] + + 0.25 * self.solution.u[i] + + 0.25 * dt * self.solution.res[j] + ) + self.solution.u[:] = u2 + self.apply_boundary() + + # Stage 3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + c1 * self.solution.un[i] + + c2 * self.solution.u[i] + + c3 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01i/boundary.py b/example/1d-linear-convection/weno3/python-v1/01i/boundary.py new file mode 100644 index 000000000..2d2cfbaf8 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/boundary.py @@ -0,0 +1,104 @@ +from abc import ABC, abstractmethod +from core.registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from core.registry import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) + diff --git a/example/1d-linear-convection/weno3/python-v1/01i/config.py b/example/1d-linear-convection/weno3/python-v1/01i/config.py new file mode 100644 index 000000000..6432c80e9 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/config.py @@ -0,0 +1,45 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + #self.ic_type = "sin" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # "rusanov", "engquist-osher" + #self.flux_type = "engquist-osher" # "rusanov", "engquist-osher" + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + print(f"scheme={scheme}") + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01i/core/registry.py b/example/1d-linear-convection/weno3/python-v1/01i/core/registry.py new file mode 100644 index 000000000..4a52afc26 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/core/registry.py @@ -0,0 +1,83 @@ +# core/registry.py +""" +统一注册系统 + 通用工厂 +- ComponentRegistry: 组件注册表 +- register_component: 装饰器 +- BaseFactory: 通用工厂类 +""" + +from typing import Dict, Type, Any + + +# ==================== 1. 注册表核心 ==================== +class ComponentRegistry: + """组件注册表""" + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True + + @classmethod + def set_verbose(cls, verbose: bool): + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + #print(f"ComponentRegistry register cls={cls}") + #print(f"ComponentRegistry register name={name}") + #print(f"ComponentRegistry register component_class={component_class}") + if category not in cls._registries: + cls._registries[category] = {} + if name in cls._registries[category]: + if cls._registries[category][name] != component_class and cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + if category not in cls._registries: + raise ValueError(f"❌ 未知类别: {category} (可用: {list(cls._registries.keys())})") + if name not in cls._registries[category]: + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {list(cls._registries[category].keys())})") + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) for cat, comps in cls._registries.items()} + + +# ==================== 2. 装饰器 ==================== +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + #print(f"register_component decorator name={name}") + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + +# ==================== 3. 通用工厂 ==================== +class BaseFactory: + """通用工厂基类""" + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + name_lower = name.lower() + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + available = ComponentRegistry.list_all().get(category, []) + if available: + error_msg = f"不支持的 {category} 类型 '{name}'。可用类型:{available}" + else: + error_msg = f"不支持的 {category} 类型 '{name}'(无已注册组件)" + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01i/domain.py b/example/1d-linear-convection/weno3/python-v1/01i/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01i/equations/base.py b/example/1d-linear-convection/weno3/python-v1/01i/equations/base.py new file mode 100644 index 000000000..92e54f126 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/equations/base.py @@ -0,0 +1,56 @@ +# equations/base.py + +from abc import ABC, abstractmethod +import numpy as np + +class Equation(ABC): + """ + 控制方程抽象基类 + 所有物理方程(线性对流、Euler、MHD)必须继承此类 + """ + + @property + @abstractmethod + def num_equations(self) -> int: + """返回方程组变量数(标量=1,Euler=3,MHD=8)""" + pass + + @abstractmethod + def flux(self, u): + """ + 计算通量函数 f(u) + :param u: 状态向量 (num_equations,) + :return: 通量向量 (num_equations,) + """ + pass + + @abstractmethod + def max_wave_speed(self, u): + """ + 计算最大波速(用于 CFL 条件) + :param u: 状态向量 + :return: 标量波速 + """ + pass + + @abstractmethod + def wave_speed(self): + """ + 计算波速(用于 CFL 条件) + :param u: 状态向量 + :return: 标量波速 + """ + pass + + def exact_solution(self, x, t, initial_condition_func): + """ + 可选:提供解析解 + 子类可重写;若无解析解,调用方应捕获 NotImplementedError + :param x: 空间坐标数组 (ncells,) + :param t: 时间 + :param initial_condition_func: 初值函数 u0(x) -> (num_equations, ncells) + :return: 解 u(x,t) -> (num_equations, ncells) + """ + raise NotImplementedError( + f"Equation '{self.__class__.__name__}' does not provide an exact solution." + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01i/equations/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/01i/equations/linear_advection.py new file mode 100644 index 000000000..26aa026be --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/equations/linear_advection.py @@ -0,0 +1,42 @@ +# equations/linear_advection.py + +import numpy as np +from .base import Equation + +class LinearAdvectionEquation(Equation): + """ + 线性对流方程: u_t + c * u_x = 0 + 支持标量(num_equations=1) + """ + + def __init__(self, wave_speed: float): + self.c = wave_speed + + @property + def num_equations(self) -> int: + return 1 + + def flux(self, u): + """f(u) = c * u""" + return self.c * u + + def max_wave_speed(self, u): + """最大波速 = |c|""" + return abs(self.c) + + def wave_speed(self): + return self.c + + + def exact_solution(self, x, t, initial_condition_func): + """ + 解析解: u(x, t) = u0(x - c * t) + 支持周期边界 + """ + if len(x) == 0: + return np.zeros((1, 0)) + + L = x[-1] - x[0] # 假设均匀周期网格 + x_shifted = (x - self.c * t + L) % L + u0 = initial_condition_func(x_shifted) # (1, ncells) + return u0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01i/flux/__init__.py b/example/1d-linear-convection/weno3/python-v1/01i/flux/__init__.py new file mode 100644 index 000000000..432a36cb0 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/flux/__init__.py @@ -0,0 +1,7 @@ +# flux/__init__.py +from .base import InviscidFluxCalculator +from .factory import FluxCalculatorFactory + +# 确保子模块被导入以触发注册 +from . import rusanov +from . import engquist_osher \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01i/flux/base.py b/example/1d-linear-convection/weno3/python-v1/01i/flux/base.py new file mode 100644 index 000000000..7a5a134fc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/flux/base.py @@ -0,0 +1,25 @@ +# flux/base.py +""" +抽象通量计算基类 +""" + +from abc import ABC, abstractmethod + +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01i/flux/engquist_osher.py b/example/1d-linear-convection/weno3/python-v1/01i/flux/engquist_osher.py new file mode 100644 index 000000000..eb7b8cdf7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/flux/engquist_osher.py @@ -0,0 +1,20 @@ +# flux/engquist_osher.py +""" +Engquist-Osher 通量计算器(线性对流专用) +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + eq = self.cfd.problem.equation + for i in range(self.mesh.nnodes): + c = eq.wave_speed() + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01i/flux/factory.py b/example/1d-linear-convection/weno3/python-v1/01i/flux/factory.py new file mode 100644 index 000000000..5ae77c3fa --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/flux/factory.py @@ -0,0 +1,25 @@ +# flux/factory.py +""" +通量计算器专用工厂(封装注册细节,提供清晰接口) +符合你希望“将创建逻辑封装在工厂中”的设计原则 +""" + +from core.registry import BaseFactory + +class FluxCalculatorFactory: + """通量计算器工厂:隐藏注册类别和组件名称等细节""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD 主对象,包含 config.flux_type 等配置 + + Returns: + 实现 InviscidFluxCalculator 接口的通量计算器实例 + """ + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01i/flux/rusanov.py b/example/1d-linear-convection/weno3/python-v1/01i/flux/rusanov.py new file mode 100644 index 000000000..baa7c62c5 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/flux/rusanov.py @@ -0,0 +1,25 @@ +# flux/rusanov.py +""" +Rusanov(Lax-Friedrichs)通量计算器 +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量,使用 Equation 解耦物理参数""" + + def compute(self, q_face_left, q_face_right, flux): + # 从 cfd 获取 equation(与 Julia 对齐) + eq = self.cfd.problem.equation + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = eq.wave_speed() + c_R = eq.wave_speed() + # 通过 equation 计算通量和波速 + F_L = eq.flux(u_L) + F_R = eq.flux(u_R) + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01i/initial_condition.py b/example/1d-linear-convection/weno3/python-v1/01i/initial_condition.py new file mode 100644 index 000000000..dda8a3bd4 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/initial_condition.py @@ -0,0 +1,90 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from core.registry import ComponentRegistry, register_component + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from core.registry import BaseFactory + return BaseFactory.create_component('initial_condition', config.ic_type, config) + diff --git a/example/1d-linear-convection/weno3/python-v1/01i/mesh.py b/example/1d-linear-convection/weno3/python-v1/01i/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01i/plotter.py b/example/1d-linear-convection/weno3/python-v1/01i/plotter.py new file mode 100644 index 000000000..e1f99ae25 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/plotter.py @@ -0,0 +1,109 @@ +# plotter.py + +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01i/problem.py b/example/1d-linear-convection/weno3/python-v1/01i/problem.py new file mode 100644 index 000000000..53719f265 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/problem.py @@ -0,0 +1,38 @@ +# problem.py +""" +问题定义模块:每个 Problem 子类代表一个完整测试用例 +包含初始条件、解析解(可选)、物理方程等 +""" + +from abc import ABC, abstractmethod +from initial_condition import InitialConditionFactory + + +class Problem(ABC): + """ + 抽象问题基类 + 每个具体问题(如线性对流、Sod 激波管)应继承此类 + """ + def __init__(self, config): + self.config = config + + @abstractmethod + def initial_condition(self): + """ + 返回 InitialCondition 实例 + """ + pass + + def exact_solution(self, cfd): + """ + 可选:返回解析解(数值数组) + 若无解析解,可抛出 NotImplementedError 或返回 None + """ + x = cfd.domain.mesh.xcc + raise NotImplementedError( + f"Problem '{self.__class__.__name__}' does not provide an exact solution." + ) + + @property + def name(self): + return self.__class__.__name__ \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01i/problems/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/01i/problems/linear_advection.py new file mode 100644 index 000000000..b6d548f51 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/problems/linear_advection.py @@ -0,0 +1,39 @@ +# problems/linear_advection.py + +import numpy as np +from problem import Problem +from equations.linear_advection import LinearAdvectionEquation +from initial_condition import InitialConditionFactory + +class LinearAdvectionProblem(Problem): + """ + 线性对流问题:u_t + c u_x = 0 + 使用周期边界 + 任意初始条件 + """ + + def __init__(self, config): + super().__init__(config) + # 持有物理方程实例 + self.equation = LinearAdvectionEquation(wave_speed=config.wave_speed) + self._ic_cache = None # 缓存 IC 实例 + + def initial_condition(self): + """返回初始条件实例""" + if self._ic_cache is None: + self._ic_cache = InitialConditionFactory.create(self.config) + return self._ic_cache + + def exact_solution(self, cfd): + """ + 委托给 Equation 计算解析解 + """ + ic = self.initial_condition() + # 包装 evaluate_at 以返回 (1, ncells) + def vectorized_ic(x): + u0_scalar = ic.evaluate_at(x) # (ncells,) + return u0_scalar[np.newaxis, :] # (1, ncells) + + x = cfd.domain.mesh.xcc + t = cfd.config.final_time + u_exact = self.equation.exact_solution(x, t, vectorized_ic) + return u_exact[0] # 返回标量数组 (ncells,) 以兼容现有绘图逻辑 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01i/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python-v1/01i/reconstructor/__init__.py new file mode 100644 index 000000000..d705bb98a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/reconstructor/__init__.py @@ -0,0 +1,6 @@ +# reconstructor/__init__.py +from .base import Reconstructor +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from .weno5 import Weno5Reconstructor +from .factory import ReconstructorFactory diff --git a/example/1d-linear-convection/weno3/python-v1/01i/reconstructor/base.py b/example/1d-linear-convection/weno3/python-v1/01i/reconstructor/base.py new file mode 100644 index 000000000..094b47120 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def compute_face_values(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01i/reconstructor/eno.py b/example/1d-linear-convection/weno3/python-v1/01i/reconstructor/eno.py new file mode 100644 index 000000000..4c8fc1e4e --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def compute_face_values(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01i/reconstructor/factory.py b/example/1d-linear-convection/weno3/python-v1/01i/reconstructor/factory.py new file mode 100644 index 000000000..25e755e0a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/reconstructor/factory.py @@ -0,0 +1,28 @@ +# reconstructor/factory.py +from core.registry import BaseFactory + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + print(f"ReconstructorFactory scheme={scheme}") + + print(f"scheme={scheme}") + if scheme == "eno": + if order is None: + order = 3 + return BaseFactory.create_component('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3" or scheme == "weno5": + # WENO3,WENO5无参数 + print(f"scheme == 'eno3' or scheme == 'weno5' scheme={scheme}") + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01i/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python-v1/01i/reconstructor/weno3.py new file mode 100644 index 000000000..cc7d82df6 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/reconstructor/weno3.py @@ -0,0 +1,59 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/python-v1/01i/reconstructor/weno5.py b/example/1d-linear-convection/weno3/python-v1/01i/reconstructor/weno5.py new file mode 100644 index 000000000..af5ca8099 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/reconstructor/weno5.py @@ -0,0 +1,68 @@ +# reconstructor/weno5.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno5') +class Weno5Reconstructor(Reconstructor): + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3, v4, v5 = u[i-2], u[i-1], u[i], u[i+1], u[i+2] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3, v4, v5) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3, v4, v5 = u[i-2], u[i-1], u[i], u[i+1], u[i+2] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3, v4, v5) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3, v4, v5): + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)**2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)**2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)**2 + (1.0/4.0)*(v2 - v4)**2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)**2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)**2 + d0 = 1.0/10.0 + d1 = 3.0/5.0 + d2 = 3.0/10.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha2 = d2 / (eps + beta2)**2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = 1.0/3.0*v1-7.0/6.0*v2+11.0/6.0*v3 # r=2 + q1 = -1.0/6.0*v2+5.0/6.0*v3+1.0/3.0*v4 # r=1 + q2 = 1.0/3.0*v3+5.0/6.0*v4-1.0/6.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3, v4, v5): + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)**2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)**2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)**2 + (1.0/4.0)*(v2 - v4)**2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)**2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)**2 + d0 = 3.0/10.0 + d1 = 3.0/5.0 + d2 = 1.0/10.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha2 = d2 / (eps + beta2)**2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = -1.0/6.0*v1+5.0/6.0*v2+1.0/3.0*v3 # r=2 + q1 = 1.0/3.0*v2+5.0/6.0*v3-1.0/6.0*v4 # r=1 + q2 = 11.0/6.0*v3-7.0/6.0*v4+1.0/3.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 diff --git a/example/1d-linear-convection/weno3/python-v1/01i/residual.py b/example/1d-linear-convection/weno3/python-v1/01i/residual.py new file mode 100644 index 000000000..e0f96cd3c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux.factory import FluxCalculatorFactory +from reconstructor import ReconstructorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + + self.reconstructor = ReconstructorFactory.create(self.config, self.domain) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._compute_face_values() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _compute_face_values(self): + """私有方法:界面值重建""" + self.reconstructor.compute_face_values(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01i/result.py b/example/1d-linear-convection/weno3/python-v1/01i/result.py new file mode 100644 index 000000000..5e572216d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/result.py @@ -0,0 +1,25 @@ +# result.py + +class ResultAssembler: + @staticmethod + def assemble(cfd: 'Cfd') -> dict: + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied].copy() + + # ✅ 从 problem 获取解析解(唯一正确路径) + try: + analytical = cfd.problem.exact_solution(cfd) + except NotImplementedError: + analytical = None # 或 np.full_like(u_numerical, np.nan) + + return { + "x": cfd.domain.mesh.xcc, + "numerical": u_numerical, + "analytical": analytical, + "config": { + "scheme": cfd.config.recon_scheme, + "order": cfd.config.spatial_order, + "rk_order": cfd.config.rk_order, + "final_time": cfd.config.final_time, + "problem": cfd.problem.name, + } + } \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01i/run_eno_weno.py b/example/1d-linear-convection/weno3/python-v1/01i/run_eno_weno.py new file mode 100644 index 000000000..ea83cbe84 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/run_eno_weno.py @@ -0,0 +1,88 @@ +# run_eno_weno.py + +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +from problems.linear_advection import LinearAdvectionProblem + + +def performEnoWenoAnalysisBAK(): + # 1. 初始化网格 + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行 ENO3 求解 + print("Running ENO3 solver...") + config_eno3 = CfdConfig() + config_eno3.with_reconstruction("eno", 3) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno3 = LinearAdvectionProblem(config_eno3) + cfd_eno3 = Cfd(problem_eno3, mesh) # ← 注入 problem + cfd_eno3.run() + + # 3. 配置并运行 WENO3 求解 + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno3 = LinearAdvectionProblem(config_weno3) + cfd_weno3 = Cfd(problem_weno3, mesh) # ← 注入 problem + cfd_weno3.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" + ) + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行 ENO5 求解 + print("Running ENO5 solver...") + config_eno5 = CfdConfig() + config_eno5.with_reconstruction("eno", 5) + config_eno5.dt = 0.0025 + config_eno5.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno5 = LinearAdvectionProblem(config_eno5) + cfd_eno5 = Cfd(problem_eno5, mesh) # ← 注入 problem + cfd_eno5.run() + + # 3. 配置并运行 WENO5求解 + print("Running WENO5 solver...") + config_weno5 = CfdConfig() + config_weno5.with_reconstruction("weno", 5) + config_weno5.dt = 0.0025 + config_weno5.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno5 = LinearAdvectionProblem(config_weno5) + cfd_weno5 = Cfd(problem_weno5, mesh) # ← 注入 problem + cfd_weno5.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno5.result, + weno_result=cfd_weno5.result, + save_path="eno_weno_comparison.png" + ) + + +if __name__ == "__main__": + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01i/solution.py b/example/1d-linear-convection/weno3/python-v1/01i/solution.py new file mode 100644 index 000000000..90666c34a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/solution.py @@ -0,0 +1,32 @@ +# solution.py +import numpy as np + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01i/solver.py b/example/1d-linear-convection/weno3/python-v1/01i/solver.py new file mode 100644 index 000000000..316be231e --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/solver.py @@ -0,0 +1,67 @@ +# solver.py + +from mesh import Mesh +from domain import Domain +from solution import Solution +from config import CfdConfig +from result import ResultAssembler +from problem import Problem +from initial_condition import InitialConditionFactory +from boundary import BoundaryConditionFactory +from residual import ResidualCalculator +from time_integration import TimeIntegrator,TimeIntegratorFactory + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, problem: Problem, mesh): + self.problem = problem + self.config = problem.config + self.domain = Domain(problem.config, mesh) + self.solution = Solution(problem.config, self.domain) + + self.initial_condition = InitialConditionFactory.create(self.config) + self.boundary_condition = BoundaryConditionFactory.create(self) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + + # ==================== 公共接口(供 TimeIntegrator 调用) ==================== + def compute_residual(self): + """计算物理残差(封装重建→通量→散度)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件""" + self.boundary_condition.apply(self.solution.u) + + # ==================== 初始化 ==================== + def initialize(self): + """初始化全场:先 IC,再 BC,最后同步 old field""" + self.initial_condition.apply(self.solution) + # 应用边界条件到初始场 + self.apply_boundary() + # 同步 old field + self.solution.update_old_field() + + + def run(self): + self.initialize() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + self.integrator.step(dt) + t += dt + config.dt = dt_old + + self.result = ResultAssembler.assemble(self) + return self.result["numerical"] + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01i/time_integration/__init__.py b/example/1d-linear-convection/weno3/python-v1/01i/time_integration/__init__.py new file mode 100644 index 000000000..6342de3bb --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/time_integration/__init__.py @@ -0,0 +1,8 @@ +# time_integration/__init__.py + +# 导出统一接口 +from .factory import TimeIntegratorFactory +from .base import TimeIntegrator + +# 触发子模块注册(关键!) +from . import rk1, rk2, rk3 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01i/time_integration/base.py b/example/1d-linear-convection/weno3/python-v1/01i/time_integration/base.py new file mode 100644 index 000000000..0cdf29a48 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/time_integration/base.py @@ -0,0 +1,27 @@ +# time_integration/base.py + +from abc import ABC, abstractmethod + +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + + @abstractmethod + def step(self, dt): + pass + + def compute_residual(self): + """计算残差(委托给 Cfd)""" + self.cfd.compute_residual() + + def apply_boundary(self): + """应用边界条件(委托给 Cfd)""" + self.cfd.apply_boundary() + + def map_idx(self, i): + """物理网格索引 → 残差数组索引""" + return i - self.domain.ist \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01i/time_integration/factory.py b/example/1d-linear-convection/weno3/python-v1/01i/time_integration/factory.py new file mode 100644 index 000000000..94662db22 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/time_integration/factory.py @@ -0,0 +1,10 @@ +# time_integration/factory.py + +from core.registry import BaseFactory + +class TimeIntegratorFactory: + @staticmethod + def create(cfd) -> 'TimeIntegrator': + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01i/time_integration/rk1.py b/example/1d-linear-convection/weno3/python-v1/01i/time_integration/rk1.py new file mode 100644 index 000000000..b4c8a0211 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/time_integration/rk1.py @@ -0,0 +1,15 @@ +# time_integration/rk1.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01i/time_integration/rk2.py b/example/1d-linear-convection/weno3/python-v1/01i/time_integration/rk2.py new file mode 100644 index 000000000..6d2be3049 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/time_integration/rk2.py @@ -0,0 +1,29 @@ +# time_integration/rk2.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + 0.5 * self.solution.un[i] + + 0.5 * self.solution.u[i] + + 0.5 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/01i/time_integration/rk3.py b/example/1d-linear-convection/weno3/python-v1/01i/time_integration/rk3.py new file mode 100644 index 000000000..c70791e13 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/01i/time_integration/rk3.py @@ -0,0 +1,43 @@ +# time_integration/rk3.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # Stage 1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # Stage 2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = ( + 0.75 * self.solution.un[i] + + 0.25 * self.solution.u[i] + + 0.25 * dt * self.solution.res[j] + ) + self.solution.u[:] = u2 + self.apply_boundary() + + # Stage 3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + c1 * self.solution.un[i] + + c2 * self.solution.u[i] + + c3 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02/example/run_eno_weno.py b/example/1d-linear-convection/weno3/python-v1/02/example/run_eno_weno.py new file mode 100644 index 000000000..c4063cb75 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/example/run_eno_weno.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +# example/run_eno_weno.py + +import sys +import os +src_path = os.path.join(os.path.dirname(__file__), '..', 'src') +if src_path not in sys.path: + sys.path.insert(0, src_path) + +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +from problems.linear_advection import LinearAdvectionProblem + + +def performEnoWenoAnalysisBAK(): + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO3 求解 + print("Running ENO3 solver...") + config_eno3 = CfdConfig() + config_eno3.with_reconstruction("eno", 3) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno3 = LinearAdvectionProblem(config_eno3) + cfd_eno3 = Cfd(problem_eno3, mesh) # ← 注入 problem + cfd_eno3.run() + + # 3. 配置并运行 WENO3 求解 + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno3 = LinearAdvectionProblem(config_weno3) + cfd_weno3 = Cfd(problem_weno3, mesh) # ← 注入 problem + cfd_weno3.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" + ) + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO5 求解 + print("Running ENO5 solver...") + config_eno5 = CfdConfig() + config_eno5.with_reconstruction("eno", 5) + config_eno5.dt = 0.0025 + config_eno5.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno5 = LinearAdvectionProblem(config_eno5) + cfd_eno5 = Cfd(problem_eno5, mesh) # ← 注入 problem + cfd_eno5.run() + + # 3. 配置并运行 WENO5求解 + print("Running WENO5 solver...") + config_weno5 = CfdConfig() + config_weno5.with_reconstruction("weno", 5) + config_weno5.dt = 0.0025 + config_weno5.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno5 = LinearAdvectionProblem(config_weno5) + cfd_weno5 = Cfd(problem_weno5, mesh) # ← 注入 problem + cfd_weno5.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno5.result, + weno_result=cfd_weno5.result, + save_path="eno_weno_comparison.png" + ) + + +if __name__ == "__main__": + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/boundary.py b/example/1d-linear-convection/weno3/python-v1/02/src/boundary.py new file mode 100644 index 000000000..2d2cfbaf8 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/boundary.py @@ -0,0 +1,104 @@ +from abc import ABC, abstractmethod +from core.registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from core.registry import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) + diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/config.py b/example/1d-linear-convection/weno3/python-v1/02/src/config.py new file mode 100644 index 000000000..6432c80e9 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/config.py @@ -0,0 +1,45 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + #self.ic_type = "sin" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # "rusanov", "engquist-osher" + #self.flux_type = "engquist-osher" # "rusanov", "engquist-osher" + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + print(f"scheme={scheme}") + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/core/registry.py b/example/1d-linear-convection/weno3/python-v1/02/src/core/registry.py new file mode 100644 index 000000000..4a52afc26 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/core/registry.py @@ -0,0 +1,83 @@ +# core/registry.py +""" +统一注册系统 + 通用工厂 +- ComponentRegistry: 组件注册表 +- register_component: 装饰器 +- BaseFactory: 通用工厂类 +""" + +from typing import Dict, Type, Any + + +# ==================== 1. 注册表核心 ==================== +class ComponentRegistry: + """组件注册表""" + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True + + @classmethod + def set_verbose(cls, verbose: bool): + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + #print(f"ComponentRegistry register cls={cls}") + #print(f"ComponentRegistry register name={name}") + #print(f"ComponentRegistry register component_class={component_class}") + if category not in cls._registries: + cls._registries[category] = {} + if name in cls._registries[category]: + if cls._registries[category][name] != component_class and cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + if category not in cls._registries: + raise ValueError(f"❌ 未知类别: {category} (可用: {list(cls._registries.keys())})") + if name not in cls._registries[category]: + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {list(cls._registries[category].keys())})") + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) for cat, comps in cls._registries.items()} + + +# ==================== 2. 装饰器 ==================== +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + #print(f"register_component decorator name={name}") + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + +# ==================== 3. 通用工厂 ==================== +class BaseFactory: + """通用工厂基类""" + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + name_lower = name.lower() + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + available = ComponentRegistry.list_all().get(category, []) + if available: + error_msg = f"不支持的 {category} 类型 '{name}'。可用类型:{available}" + else: + error_msg = f"不支持的 {category} 类型 '{name}'(无已注册组件)" + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/domain.py b/example/1d-linear-convection/weno3/python-v1/02/src/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/equations/base.py b/example/1d-linear-convection/weno3/python-v1/02/src/equations/base.py new file mode 100644 index 000000000..92e54f126 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/equations/base.py @@ -0,0 +1,56 @@ +# equations/base.py + +from abc import ABC, abstractmethod +import numpy as np + +class Equation(ABC): + """ + 控制方程抽象基类 + 所有物理方程(线性对流、Euler、MHD)必须继承此类 + """ + + @property + @abstractmethod + def num_equations(self) -> int: + """返回方程组变量数(标量=1,Euler=3,MHD=8)""" + pass + + @abstractmethod + def flux(self, u): + """ + 计算通量函数 f(u) + :param u: 状态向量 (num_equations,) + :return: 通量向量 (num_equations,) + """ + pass + + @abstractmethod + def max_wave_speed(self, u): + """ + 计算最大波速(用于 CFL 条件) + :param u: 状态向量 + :return: 标量波速 + """ + pass + + @abstractmethod + def wave_speed(self): + """ + 计算波速(用于 CFL 条件) + :param u: 状态向量 + :return: 标量波速 + """ + pass + + def exact_solution(self, x, t, initial_condition_func): + """ + 可选:提供解析解 + 子类可重写;若无解析解,调用方应捕获 NotImplementedError + :param x: 空间坐标数组 (ncells,) + :param t: 时间 + :param initial_condition_func: 初值函数 u0(x) -> (num_equations, ncells) + :return: 解 u(x,t) -> (num_equations, ncells) + """ + raise NotImplementedError( + f"Equation '{self.__class__.__name__}' does not provide an exact solution." + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/equations/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/02/src/equations/linear_advection.py new file mode 100644 index 000000000..26aa026be --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/equations/linear_advection.py @@ -0,0 +1,42 @@ +# equations/linear_advection.py + +import numpy as np +from .base import Equation + +class LinearAdvectionEquation(Equation): + """ + 线性对流方程: u_t + c * u_x = 0 + 支持标量(num_equations=1) + """ + + def __init__(self, wave_speed: float): + self.c = wave_speed + + @property + def num_equations(self) -> int: + return 1 + + def flux(self, u): + """f(u) = c * u""" + return self.c * u + + def max_wave_speed(self, u): + """最大波速 = |c|""" + return abs(self.c) + + def wave_speed(self): + return self.c + + + def exact_solution(self, x, t, initial_condition_func): + """ + 解析解: u(x, t) = u0(x - c * t) + 支持周期边界 + """ + if len(x) == 0: + return np.zeros((1, 0)) + + L = x[-1] - x[0] # 假设均匀周期网格 + x_shifted = (x - self.c * t + L) % L + u0 = initial_condition_func(x_shifted) # (1, ncells) + return u0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/flux/__init__.py b/example/1d-linear-convection/weno3/python-v1/02/src/flux/__init__.py new file mode 100644 index 000000000..432a36cb0 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/flux/__init__.py @@ -0,0 +1,7 @@ +# flux/__init__.py +from .base import InviscidFluxCalculator +from .factory import FluxCalculatorFactory + +# 确保子模块被导入以触发注册 +from . import rusanov +from . import engquist_osher \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/flux/base.py b/example/1d-linear-convection/weno3/python-v1/02/src/flux/base.py new file mode 100644 index 000000000..7a5a134fc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/flux/base.py @@ -0,0 +1,25 @@ +# flux/base.py +""" +抽象通量计算基类 +""" + +from abc import ABC, abstractmethod + +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/flux/engquist_osher.py b/example/1d-linear-convection/weno3/python-v1/02/src/flux/engquist_osher.py new file mode 100644 index 000000000..eb7b8cdf7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/flux/engquist_osher.py @@ -0,0 +1,20 @@ +# flux/engquist_osher.py +""" +Engquist-Osher 通量计算器(线性对流专用) +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + eq = self.cfd.problem.equation + for i in range(self.mesh.nnodes): + c = eq.wave_speed() + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/flux/factory.py b/example/1d-linear-convection/weno3/python-v1/02/src/flux/factory.py new file mode 100644 index 000000000..5ae77c3fa --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/flux/factory.py @@ -0,0 +1,25 @@ +# flux/factory.py +""" +通量计算器专用工厂(封装注册细节,提供清晰接口) +符合你希望“将创建逻辑封装在工厂中”的设计原则 +""" + +from core.registry import BaseFactory + +class FluxCalculatorFactory: + """通量计算器工厂:隐藏注册类别和组件名称等细节""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD 主对象,包含 config.flux_type 等配置 + + Returns: + 实现 InviscidFluxCalculator 接口的通量计算器实例 + """ + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/flux/rusanov.py b/example/1d-linear-convection/weno3/python-v1/02/src/flux/rusanov.py new file mode 100644 index 000000000..baa7c62c5 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/flux/rusanov.py @@ -0,0 +1,25 @@ +# flux/rusanov.py +""" +Rusanov(Lax-Friedrichs)通量计算器 +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量,使用 Equation 解耦物理参数""" + + def compute(self, q_face_left, q_face_right, flux): + # 从 cfd 获取 equation(与 Julia 对齐) + eq = self.cfd.problem.equation + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = eq.wave_speed() + c_R = eq.wave_speed() + # 通过 equation 计算通量和波速 + F_L = eq.flux(u_L) + F_R = eq.flux(u_R) + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/initial_condition.py b/example/1d-linear-convection/weno3/python-v1/02/src/initial_condition.py new file mode 100644 index 000000000..dda8a3bd4 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/initial_condition.py @@ -0,0 +1,90 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from core.registry import ComponentRegistry, register_component + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from core.registry import BaseFactory + return BaseFactory.create_component('initial_condition', config.ic_type, config) + diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/mesh.py b/example/1d-linear-convection/weno3/python-v1/02/src/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/plotter.py b/example/1d-linear-convection/weno3/python-v1/02/src/plotter.py new file mode 100644 index 000000000..e1f99ae25 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/plotter.py @@ -0,0 +1,109 @@ +# plotter.py + +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/problem.py b/example/1d-linear-convection/weno3/python-v1/02/src/problem.py new file mode 100644 index 000000000..53719f265 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/problem.py @@ -0,0 +1,38 @@ +# problem.py +""" +问题定义模块:每个 Problem 子类代表一个完整测试用例 +包含初始条件、解析解(可选)、物理方程等 +""" + +from abc import ABC, abstractmethod +from initial_condition import InitialConditionFactory + + +class Problem(ABC): + """ + 抽象问题基类 + 每个具体问题(如线性对流、Sod 激波管)应继承此类 + """ + def __init__(self, config): + self.config = config + + @abstractmethod + def initial_condition(self): + """ + 返回 InitialCondition 实例 + """ + pass + + def exact_solution(self, cfd): + """ + 可选:返回解析解(数值数组) + 若无解析解,可抛出 NotImplementedError 或返回 None + """ + x = cfd.domain.mesh.xcc + raise NotImplementedError( + f"Problem '{self.__class__.__name__}' does not provide an exact solution." + ) + + @property + def name(self): + return self.__class__.__name__ \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/problems/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/02/src/problems/linear_advection.py new file mode 100644 index 000000000..b6d548f51 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/problems/linear_advection.py @@ -0,0 +1,39 @@ +# problems/linear_advection.py + +import numpy as np +from problem import Problem +from equations.linear_advection import LinearAdvectionEquation +from initial_condition import InitialConditionFactory + +class LinearAdvectionProblem(Problem): + """ + 线性对流问题:u_t + c u_x = 0 + 使用周期边界 + 任意初始条件 + """ + + def __init__(self, config): + super().__init__(config) + # 持有物理方程实例 + self.equation = LinearAdvectionEquation(wave_speed=config.wave_speed) + self._ic_cache = None # 缓存 IC 实例 + + def initial_condition(self): + """返回初始条件实例""" + if self._ic_cache is None: + self._ic_cache = InitialConditionFactory.create(self.config) + return self._ic_cache + + def exact_solution(self, cfd): + """ + 委托给 Equation 计算解析解 + """ + ic = self.initial_condition() + # 包装 evaluate_at 以返回 (1, ncells) + def vectorized_ic(x): + u0_scalar = ic.evaluate_at(x) # (ncells,) + return u0_scalar[np.newaxis, :] # (1, ncells) + + x = cfd.domain.mesh.xcc + t = cfd.config.final_time + u_exact = self.equation.exact_solution(x, t, vectorized_ic) + return u_exact[0] # 返回标量数组 (ncells,) 以兼容现有绘图逻辑 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python-v1/02/src/reconstructor/__init__.py new file mode 100644 index 000000000..d705bb98a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/reconstructor/__init__.py @@ -0,0 +1,6 @@ +# reconstructor/__init__.py +from .base import Reconstructor +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from .weno5 import Weno5Reconstructor +from .factory import ReconstructorFactory diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/reconstructor/base.py b/example/1d-linear-convection/weno3/python-v1/02/src/reconstructor/base.py new file mode 100644 index 000000000..094b47120 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def compute_face_values(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/reconstructor/eno.py b/example/1d-linear-convection/weno3/python-v1/02/src/reconstructor/eno.py new file mode 100644 index 000000000..4c8fc1e4e --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def compute_face_values(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/reconstructor/factory.py b/example/1d-linear-convection/weno3/python-v1/02/src/reconstructor/factory.py new file mode 100644 index 000000000..25e755e0a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/reconstructor/factory.py @@ -0,0 +1,28 @@ +# reconstructor/factory.py +from core.registry import BaseFactory + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + print(f"ReconstructorFactory scheme={scheme}") + + print(f"scheme={scheme}") + if scheme == "eno": + if order is None: + order = 3 + return BaseFactory.create_component('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3" or scheme == "weno5": + # WENO3,WENO5无参数 + print(f"scheme == 'eno3' or scheme == 'weno5' scheme={scheme}") + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python-v1/02/src/reconstructor/weno3.py new file mode 100644 index 000000000..cc7d82df6 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/reconstructor/weno3.py @@ -0,0 +1,59 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/reconstructor/weno5.py b/example/1d-linear-convection/weno3/python-v1/02/src/reconstructor/weno5.py new file mode 100644 index 000000000..af5ca8099 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/reconstructor/weno5.py @@ -0,0 +1,68 @@ +# reconstructor/weno5.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno5') +class Weno5Reconstructor(Reconstructor): + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3, v4, v5 = u[i-2], u[i-1], u[i], u[i+1], u[i+2] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3, v4, v5) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3, v4, v5 = u[i-2], u[i-1], u[i], u[i+1], u[i+2] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3, v4, v5) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3, v4, v5): + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)**2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)**2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)**2 + (1.0/4.0)*(v2 - v4)**2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)**2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)**2 + d0 = 1.0/10.0 + d1 = 3.0/5.0 + d2 = 3.0/10.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha2 = d2 / (eps + beta2)**2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = 1.0/3.0*v1-7.0/6.0*v2+11.0/6.0*v3 # r=2 + q1 = -1.0/6.0*v2+5.0/6.0*v3+1.0/3.0*v4 # r=1 + q2 = 1.0/3.0*v3+5.0/6.0*v4-1.0/6.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3, v4, v5): + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)**2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)**2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)**2 + (1.0/4.0)*(v2 - v4)**2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)**2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)**2 + d0 = 3.0/10.0 + d1 = 3.0/5.0 + d2 = 1.0/10.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha2 = d2 / (eps + beta2)**2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = -1.0/6.0*v1+5.0/6.0*v2+1.0/3.0*v3 # r=2 + q1 = 1.0/3.0*v2+5.0/6.0*v3-1.0/6.0*v4 # r=1 + q2 = 11.0/6.0*v3-7.0/6.0*v4+1.0/3.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/residual.py b/example/1d-linear-convection/weno3/python-v1/02/src/residual.py new file mode 100644 index 000000000..e0f96cd3c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux.factory import FluxCalculatorFactory +from reconstructor import ReconstructorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + + self.reconstructor = ReconstructorFactory.create(self.config, self.domain) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._compute_face_values() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _compute_face_values(self): + """私有方法:界面值重建""" + self.reconstructor.compute_face_values(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/result.py b/example/1d-linear-convection/weno3/python-v1/02/src/result.py new file mode 100644 index 000000000..5e572216d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/result.py @@ -0,0 +1,25 @@ +# result.py + +class ResultAssembler: + @staticmethod + def assemble(cfd: 'Cfd') -> dict: + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied].copy() + + # ✅ 从 problem 获取解析解(唯一正确路径) + try: + analytical = cfd.problem.exact_solution(cfd) + except NotImplementedError: + analytical = None # 或 np.full_like(u_numerical, np.nan) + + return { + "x": cfd.domain.mesh.xcc, + "numerical": u_numerical, + "analytical": analytical, + "config": { + "scheme": cfd.config.recon_scheme, + "order": cfd.config.spatial_order, + "rk_order": cfd.config.rk_order, + "final_time": cfd.config.final_time, + "problem": cfd.problem.name, + } + } \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/solution.py b/example/1d-linear-convection/weno3/python-v1/02/src/solution.py new file mode 100644 index 000000000..90666c34a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/solution.py @@ -0,0 +1,32 @@ +# solution.py +import numpy as np + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/solver.py b/example/1d-linear-convection/weno3/python-v1/02/src/solver.py new file mode 100644 index 000000000..316be231e --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/solver.py @@ -0,0 +1,67 @@ +# solver.py + +from mesh import Mesh +from domain import Domain +from solution import Solution +from config import CfdConfig +from result import ResultAssembler +from problem import Problem +from initial_condition import InitialConditionFactory +from boundary import BoundaryConditionFactory +from residual import ResidualCalculator +from time_integration import TimeIntegrator,TimeIntegratorFactory + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, problem: Problem, mesh): + self.problem = problem + self.config = problem.config + self.domain = Domain(problem.config, mesh) + self.solution = Solution(problem.config, self.domain) + + self.initial_condition = InitialConditionFactory.create(self.config) + self.boundary_condition = BoundaryConditionFactory.create(self) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + + # ==================== 公共接口(供 TimeIntegrator 调用) ==================== + def compute_residual(self): + """计算物理残差(封装重建→通量→散度)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件""" + self.boundary_condition.apply(self.solution.u) + + # ==================== 初始化 ==================== + def initialize(self): + """初始化全场:先 IC,再 BC,最后同步 old field""" + self.initial_condition.apply(self.solution) + # 应用边界条件到初始场 + self.apply_boundary() + # 同步 old field + self.solution.update_old_field() + + + def run(self): + self.initialize() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + self.integrator.step(dt) + t += dt + config.dt = dt_old + + self.result = ResultAssembler.assemble(self) + return self.result["numerical"] + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/time_integration/__init__.py b/example/1d-linear-convection/weno3/python-v1/02/src/time_integration/__init__.py new file mode 100644 index 000000000..6342de3bb --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/time_integration/__init__.py @@ -0,0 +1,8 @@ +# time_integration/__init__.py + +# 导出统一接口 +from .factory import TimeIntegratorFactory +from .base import TimeIntegrator + +# 触发子模块注册(关键!) +from . import rk1, rk2, rk3 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/time_integration/base.py b/example/1d-linear-convection/weno3/python-v1/02/src/time_integration/base.py new file mode 100644 index 000000000..0cdf29a48 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/time_integration/base.py @@ -0,0 +1,27 @@ +# time_integration/base.py + +from abc import ABC, abstractmethod + +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + + @abstractmethod + def step(self, dt): + pass + + def compute_residual(self): + """计算残差(委托给 Cfd)""" + self.cfd.compute_residual() + + def apply_boundary(self): + """应用边界条件(委托给 Cfd)""" + self.cfd.apply_boundary() + + def map_idx(self, i): + """物理网格索引 → 残差数组索引""" + return i - self.domain.ist \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/time_integration/factory.py b/example/1d-linear-convection/weno3/python-v1/02/src/time_integration/factory.py new file mode 100644 index 000000000..94662db22 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/time_integration/factory.py @@ -0,0 +1,10 @@ +# time_integration/factory.py + +from core.registry import BaseFactory + +class TimeIntegratorFactory: + @staticmethod + def create(cfd) -> 'TimeIntegrator': + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/time_integration/rk1.py b/example/1d-linear-convection/weno3/python-v1/02/src/time_integration/rk1.py new file mode 100644 index 000000000..b4c8a0211 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/time_integration/rk1.py @@ -0,0 +1,15 @@ +# time_integration/rk1.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/time_integration/rk2.py b/example/1d-linear-convection/weno3/python-v1/02/src/time_integration/rk2.py new file mode 100644 index 000000000..6d2be3049 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/time_integration/rk2.py @@ -0,0 +1,29 @@ +# time_integration/rk2.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + 0.5 * self.solution.un[i] + + 0.5 * self.solution.u[i] + + 0.5 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02/src/time_integration/rk3.py b/example/1d-linear-convection/weno3/python-v1/02/src/time_integration/rk3.py new file mode 100644 index 000000000..c70791e13 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02/src/time_integration/rk3.py @@ -0,0 +1,43 @@ +# time_integration/rk3.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # Stage 1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # Stage 2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = ( + 0.75 * self.solution.un[i] + + 0.25 * self.solution.u[i] + + 0.25 * dt * self.solution.res[j] + ) + self.solution.u[:] = u2 + self.apply_boundary() + + # Stage 3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + c1 * self.solution.un[i] + + c2 * self.solution.u[i] + + c3 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02a/example/run_eno_weno.py b/example/1d-linear-convection/weno3/python-v1/02a/example/run_eno_weno.py new file mode 100644 index 000000000..c4063cb75 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/example/run_eno_weno.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +# example/run_eno_weno.py + +import sys +import os +src_path = os.path.join(os.path.dirname(__file__), '..', 'src') +if src_path not in sys.path: + sys.path.insert(0, src_path) + +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +from problems.linear_advection import LinearAdvectionProblem + + +def performEnoWenoAnalysisBAK(): + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO3 求解 + print("Running ENO3 solver...") + config_eno3 = CfdConfig() + config_eno3.with_reconstruction("eno", 3) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno3 = LinearAdvectionProblem(config_eno3) + cfd_eno3 = Cfd(problem_eno3, mesh) # ← 注入 problem + cfd_eno3.run() + + # 3. 配置并运行 WENO3 求解 + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno3 = LinearAdvectionProblem(config_weno3) + cfd_weno3 = Cfd(problem_weno3, mesh) # ← 注入 problem + cfd_weno3.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" + ) + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO5 求解 + print("Running ENO5 solver...") + config_eno5 = CfdConfig() + config_eno5.with_reconstruction("eno", 5) + config_eno5.dt = 0.0025 + config_eno5.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno5 = LinearAdvectionProblem(config_eno5) + cfd_eno5 = Cfd(problem_eno5, mesh) # ← 注入 problem + cfd_eno5.run() + + # 3. 配置并运行 WENO5求解 + print("Running WENO5 solver...") + config_weno5 = CfdConfig() + config_weno5.with_reconstruction("weno", 5) + config_weno5.dt = 0.0025 + config_weno5.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno5 = LinearAdvectionProblem(config_weno5) + cfd_weno5 = Cfd(problem_weno5, mesh) # ← 注入 problem + cfd_weno5.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno5.result, + weno_result=cfd_weno5.result, + save_path="eno_weno_comparison.png" + ) + + +if __name__ == "__main__": + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/boundary.py b/example/1d-linear-convection/weno3/python-v1/02a/src/boundary.py new file mode 100644 index 000000000..2d2cfbaf8 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/boundary.py @@ -0,0 +1,104 @@ +from abc import ABC, abstractmethod +from core.registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from core.registry import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) + diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/config.py b/example/1d-linear-convection/weno3/python-v1/02a/src/config.py new file mode 100644 index 000000000..6432c80e9 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/config.py @@ -0,0 +1,45 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + #self.ic_type = "sin" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # "rusanov", "engquist-osher" + #self.flux_type = "engquist-osher" # "rusanov", "engquist-osher" + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + print(f"scheme={scheme}") + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/core/registry.py b/example/1d-linear-convection/weno3/python-v1/02a/src/core/registry.py new file mode 100644 index 000000000..4a52afc26 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/core/registry.py @@ -0,0 +1,83 @@ +# core/registry.py +""" +统一注册系统 + 通用工厂 +- ComponentRegistry: 组件注册表 +- register_component: 装饰器 +- BaseFactory: 通用工厂类 +""" + +from typing import Dict, Type, Any + + +# ==================== 1. 注册表核心 ==================== +class ComponentRegistry: + """组件注册表""" + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True + + @classmethod + def set_verbose(cls, verbose: bool): + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + #print(f"ComponentRegistry register cls={cls}") + #print(f"ComponentRegistry register name={name}") + #print(f"ComponentRegistry register component_class={component_class}") + if category not in cls._registries: + cls._registries[category] = {} + if name in cls._registries[category]: + if cls._registries[category][name] != component_class and cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + if category not in cls._registries: + raise ValueError(f"❌ 未知类别: {category} (可用: {list(cls._registries.keys())})") + if name not in cls._registries[category]: + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {list(cls._registries[category].keys())})") + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) for cat, comps in cls._registries.items()} + + +# ==================== 2. 装饰器 ==================== +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + #print(f"register_component decorator name={name}") + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + +# ==================== 3. 通用工厂 ==================== +class BaseFactory: + """通用工厂基类""" + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + name_lower = name.lower() + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + available = ComponentRegistry.list_all().get(category, []) + if available: + error_msg = f"不支持的 {category} 类型 '{name}'。可用类型:{available}" + else: + error_msg = f"不支持的 {category} 类型 '{name}'(无已注册组件)" + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/domain.py b/example/1d-linear-convection/weno3/python-v1/02a/src/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/equations/base.py b/example/1d-linear-convection/weno3/python-v1/02a/src/equations/base.py new file mode 100644 index 000000000..92e54f126 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/equations/base.py @@ -0,0 +1,56 @@ +# equations/base.py + +from abc import ABC, abstractmethod +import numpy as np + +class Equation(ABC): + """ + 控制方程抽象基类 + 所有物理方程(线性对流、Euler、MHD)必须继承此类 + """ + + @property + @abstractmethod + def num_equations(self) -> int: + """返回方程组变量数(标量=1,Euler=3,MHD=8)""" + pass + + @abstractmethod + def flux(self, u): + """ + 计算通量函数 f(u) + :param u: 状态向量 (num_equations,) + :return: 通量向量 (num_equations,) + """ + pass + + @abstractmethod + def max_wave_speed(self, u): + """ + 计算最大波速(用于 CFL 条件) + :param u: 状态向量 + :return: 标量波速 + """ + pass + + @abstractmethod + def wave_speed(self): + """ + 计算波速(用于 CFL 条件) + :param u: 状态向量 + :return: 标量波速 + """ + pass + + def exact_solution(self, x, t, initial_condition_func): + """ + 可选:提供解析解 + 子类可重写;若无解析解,调用方应捕获 NotImplementedError + :param x: 空间坐标数组 (ncells,) + :param t: 时间 + :param initial_condition_func: 初值函数 u0(x) -> (num_equations, ncells) + :return: 解 u(x,t) -> (num_equations, ncells) + """ + raise NotImplementedError( + f"Equation '{self.__class__.__name__}' does not provide an exact solution." + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/equations/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/02a/src/equations/linear_advection.py new file mode 100644 index 000000000..26aa026be --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/equations/linear_advection.py @@ -0,0 +1,42 @@ +# equations/linear_advection.py + +import numpy as np +from .base import Equation + +class LinearAdvectionEquation(Equation): + """ + 线性对流方程: u_t + c * u_x = 0 + 支持标量(num_equations=1) + """ + + def __init__(self, wave_speed: float): + self.c = wave_speed + + @property + def num_equations(self) -> int: + return 1 + + def flux(self, u): + """f(u) = c * u""" + return self.c * u + + def max_wave_speed(self, u): + """最大波速 = |c|""" + return abs(self.c) + + def wave_speed(self): + return self.c + + + def exact_solution(self, x, t, initial_condition_func): + """ + 解析解: u(x, t) = u0(x - c * t) + 支持周期边界 + """ + if len(x) == 0: + return np.zeros((1, 0)) + + L = x[-1] - x[0] # 假设均匀周期网格 + x_shifted = (x - self.c * t + L) % L + u0 = initial_condition_func(x_shifted) # (1, ncells) + return u0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/flux/__init__.py b/example/1d-linear-convection/weno3/python-v1/02a/src/flux/__init__.py new file mode 100644 index 000000000..432a36cb0 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/flux/__init__.py @@ -0,0 +1,7 @@ +# flux/__init__.py +from .base import InviscidFluxCalculator +from .factory import FluxCalculatorFactory + +# 确保子模块被导入以触发注册 +from . import rusanov +from . import engquist_osher \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/flux/base.py b/example/1d-linear-convection/weno3/python-v1/02a/src/flux/base.py new file mode 100644 index 000000000..7a5a134fc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/flux/base.py @@ -0,0 +1,25 @@ +# flux/base.py +""" +抽象通量计算基类 +""" + +from abc import ABC, abstractmethod + +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/flux/engquist_osher.py b/example/1d-linear-convection/weno3/python-v1/02a/src/flux/engquist_osher.py new file mode 100644 index 000000000..eb7b8cdf7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/flux/engquist_osher.py @@ -0,0 +1,20 @@ +# flux/engquist_osher.py +""" +Engquist-Osher 通量计算器(线性对流专用) +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + eq = self.cfd.problem.equation + for i in range(self.mesh.nnodes): + c = eq.wave_speed() + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/flux/factory.py b/example/1d-linear-convection/weno3/python-v1/02a/src/flux/factory.py new file mode 100644 index 000000000..5ae77c3fa --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/flux/factory.py @@ -0,0 +1,25 @@ +# flux/factory.py +""" +通量计算器专用工厂(封装注册细节,提供清晰接口) +符合你希望“将创建逻辑封装在工厂中”的设计原则 +""" + +from core.registry import BaseFactory + +class FluxCalculatorFactory: + """通量计算器工厂:隐藏注册类别和组件名称等细节""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD 主对象,包含 config.flux_type 等配置 + + Returns: + 实现 InviscidFluxCalculator 接口的通量计算器实例 + """ + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/flux/rusanov.py b/example/1d-linear-convection/weno3/python-v1/02a/src/flux/rusanov.py new file mode 100644 index 000000000..baa7c62c5 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/flux/rusanov.py @@ -0,0 +1,25 @@ +# flux/rusanov.py +""" +Rusanov(Lax-Friedrichs)通量计算器 +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量,使用 Equation 解耦物理参数""" + + def compute(self, q_face_left, q_face_right, flux): + # 从 cfd 获取 equation(与 Julia 对齐) + eq = self.cfd.problem.equation + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = eq.wave_speed() + c_R = eq.wave_speed() + # 通过 equation 计算通量和波速 + F_L = eq.flux(u_L) + F_R = eq.flux(u_R) + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/initial_condition.py b/example/1d-linear-convection/weno3/python-v1/02a/src/initial_condition.py new file mode 100644 index 000000000..dda8a3bd4 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/initial_condition.py @@ -0,0 +1,90 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from core.registry import ComponentRegistry, register_component + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from core.registry import BaseFactory + return BaseFactory.create_component('initial_condition', config.ic_type, config) + diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/mesh.py b/example/1d-linear-convection/weno3/python-v1/02a/src/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/plotter.py b/example/1d-linear-convection/weno3/python-v1/02a/src/plotter.py new file mode 100644 index 000000000..e1f99ae25 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/plotter.py @@ -0,0 +1,109 @@ +# plotter.py + +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/problem.py b/example/1d-linear-convection/weno3/python-v1/02a/src/problem.py new file mode 100644 index 000000000..53719f265 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/problem.py @@ -0,0 +1,38 @@ +# problem.py +""" +问题定义模块:每个 Problem 子类代表一个完整测试用例 +包含初始条件、解析解(可选)、物理方程等 +""" + +from abc import ABC, abstractmethod +from initial_condition import InitialConditionFactory + + +class Problem(ABC): + """ + 抽象问题基类 + 每个具体问题(如线性对流、Sod 激波管)应继承此类 + """ + def __init__(self, config): + self.config = config + + @abstractmethod + def initial_condition(self): + """ + 返回 InitialCondition 实例 + """ + pass + + def exact_solution(self, cfd): + """ + 可选:返回解析解(数值数组) + 若无解析解,可抛出 NotImplementedError 或返回 None + """ + x = cfd.domain.mesh.xcc + raise NotImplementedError( + f"Problem '{self.__class__.__name__}' does not provide an exact solution." + ) + + @property + def name(self): + return self.__class__.__name__ \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/problems/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/02a/src/problems/linear_advection.py new file mode 100644 index 000000000..b6d548f51 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/problems/linear_advection.py @@ -0,0 +1,39 @@ +# problems/linear_advection.py + +import numpy as np +from problem import Problem +from equations.linear_advection import LinearAdvectionEquation +from initial_condition import InitialConditionFactory + +class LinearAdvectionProblem(Problem): + """ + 线性对流问题:u_t + c u_x = 0 + 使用周期边界 + 任意初始条件 + """ + + def __init__(self, config): + super().__init__(config) + # 持有物理方程实例 + self.equation = LinearAdvectionEquation(wave_speed=config.wave_speed) + self._ic_cache = None # 缓存 IC 实例 + + def initial_condition(self): + """返回初始条件实例""" + if self._ic_cache is None: + self._ic_cache = InitialConditionFactory.create(self.config) + return self._ic_cache + + def exact_solution(self, cfd): + """ + 委托给 Equation 计算解析解 + """ + ic = self.initial_condition() + # 包装 evaluate_at 以返回 (1, ncells) + def vectorized_ic(x): + u0_scalar = ic.evaluate_at(x) # (ncells,) + return u0_scalar[np.newaxis, :] # (1, ncells) + + x = cfd.domain.mesh.xcc + t = cfd.config.final_time + u_exact = self.equation.exact_solution(x, t, vectorized_ic) + return u_exact[0] # 返回标量数组 (ncells,) 以兼容现有绘图逻辑 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python-v1/02a/src/reconstructor/__init__.py new file mode 100644 index 000000000..d705bb98a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/reconstructor/__init__.py @@ -0,0 +1,6 @@ +# reconstructor/__init__.py +from .base import Reconstructor +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from .weno5 import Weno5Reconstructor +from .factory import ReconstructorFactory diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/reconstructor/base.py b/example/1d-linear-convection/weno3/python-v1/02a/src/reconstructor/base.py new file mode 100644 index 000000000..de00327cc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def compute_face_values(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/reconstructor/eno.py b/example/1d-linear-convection/weno3/python-v1/02a/src/reconstructor/eno.py new file mode 100644 index 000000000..60699adf6 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/reconstructor/eno.py @@ -0,0 +1,96 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.initialize(self.config.spatial_order, self.domain.ntcells) + + def initialize(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def compute_face_values(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/reconstructor/factory.py b/example/1d-linear-convection/weno3/python-v1/02a/src/reconstructor/factory.py new file mode 100644 index 000000000..e287ab84b --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/reconstructor/factory.py @@ -0,0 +1,14 @@ +# reconstructor/factory.py +from core.registry import BaseFactory + +class ReconstructorFactory: + @staticmethod + def create(cfd): + config = cfd.config + scheme = config.recon_scheme.lower() + + if scheme.startswith("weno"): + if scheme == "weno": + order = getattr(config, 'spatial_order', None) + scheme = f"weno{order}" + return BaseFactory.create_component('reconstructor', scheme, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python-v1/02a/src/reconstructor/weno3.py new file mode 100644 index 000000000..f45eb35f2 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/reconstructor/weno3.py @@ -0,0 +1,62 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def __init__(self, cfd): + self.cfd = cfd + + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/reconstructor/weno5.py b/example/1d-linear-convection/weno3/python-v1/02a/src/reconstructor/weno5.py new file mode 100644 index 000000000..9d6652752 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/reconstructor/weno5.py @@ -0,0 +1,71 @@ +# reconstructor/weno5.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno5') +class Weno5Reconstructor(Reconstructor): + def __init__(self, cfd): + self.cfd = cfd + + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3, v4, v5 = u[i-2], u[i-1], u[i], u[i+1], u[i+2] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3, v4, v5) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3, v4, v5 = u[i-2], u[i-1], u[i], u[i+1], u[i+2] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3, v4, v5) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3, v4, v5): + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)**2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)**2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)**2 + (1.0/4.0)*(v2 - v4)**2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)**2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)**2 + d0 = 1.0/10.0 + d1 = 3.0/5.0 + d2 = 3.0/10.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha2 = d2 / (eps + beta2)**2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = 1.0/3.0*v1-7.0/6.0*v2+11.0/6.0*v3 # r=2 + q1 = -1.0/6.0*v2+5.0/6.0*v3+1.0/3.0*v4 # r=1 + q2 = 1.0/3.0*v3+5.0/6.0*v4-1.0/6.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3, v4, v5): + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)**2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)**2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)**2 + (1.0/4.0)*(v2 - v4)**2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)**2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)**2 + d0 = 3.0/10.0 + d1 = 3.0/5.0 + d2 = 1.0/10.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha2 = d2 / (eps + beta2)**2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = -1.0/6.0*v1+5.0/6.0*v2+1.0/3.0*v3 # r=2 + q1 = 1.0/3.0*v2+5.0/6.0*v3-1.0/6.0*v4 # r=1 + q2 = 11.0/6.0*v3-7.0/6.0*v4+1.0/3.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/residual.py b/example/1d-linear-convection/weno3/python-v1/02a/src/residual.py new file mode 100644 index 000000000..27d5a0c41 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/residual.py @@ -0,0 +1,41 @@ +# residual.py + +from flux.factory import FluxCalculatorFactory +from reconstructor import ReconstructorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + + #self.reconstructor = ReconstructorFactory.create(self.config, self.domain) + self.reconstructor = ReconstructorFactory.create(cfd) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._compute_face_values() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _compute_face_values(self): + """私有方法:界面值重建""" + self.reconstructor.compute_face_values(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/result.py b/example/1d-linear-convection/weno3/python-v1/02a/src/result.py new file mode 100644 index 000000000..5e572216d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/result.py @@ -0,0 +1,25 @@ +# result.py + +class ResultAssembler: + @staticmethod + def assemble(cfd: 'Cfd') -> dict: + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied].copy() + + # ✅ 从 problem 获取解析解(唯一正确路径) + try: + analytical = cfd.problem.exact_solution(cfd) + except NotImplementedError: + analytical = None # 或 np.full_like(u_numerical, np.nan) + + return { + "x": cfd.domain.mesh.xcc, + "numerical": u_numerical, + "analytical": analytical, + "config": { + "scheme": cfd.config.recon_scheme, + "order": cfd.config.spatial_order, + "rk_order": cfd.config.rk_order, + "final_time": cfd.config.final_time, + "problem": cfd.problem.name, + } + } \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/solution.py b/example/1d-linear-convection/weno3/python-v1/02a/src/solution.py new file mode 100644 index 000000000..90666c34a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/solution.py @@ -0,0 +1,32 @@ +# solution.py +import numpy as np + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/solver.py b/example/1d-linear-convection/weno3/python-v1/02a/src/solver.py new file mode 100644 index 000000000..316be231e --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/solver.py @@ -0,0 +1,67 @@ +# solver.py + +from mesh import Mesh +from domain import Domain +from solution import Solution +from config import CfdConfig +from result import ResultAssembler +from problem import Problem +from initial_condition import InitialConditionFactory +from boundary import BoundaryConditionFactory +from residual import ResidualCalculator +from time_integration import TimeIntegrator,TimeIntegratorFactory + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, problem: Problem, mesh): + self.problem = problem + self.config = problem.config + self.domain = Domain(problem.config, mesh) + self.solution = Solution(problem.config, self.domain) + + self.initial_condition = InitialConditionFactory.create(self.config) + self.boundary_condition = BoundaryConditionFactory.create(self) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + + # ==================== 公共接口(供 TimeIntegrator 调用) ==================== + def compute_residual(self): + """计算物理残差(封装重建→通量→散度)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件""" + self.boundary_condition.apply(self.solution.u) + + # ==================== 初始化 ==================== + def initialize(self): + """初始化全场:先 IC,再 BC,最后同步 old field""" + self.initial_condition.apply(self.solution) + # 应用边界条件到初始场 + self.apply_boundary() + # 同步 old field + self.solution.update_old_field() + + + def run(self): + self.initialize() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + self.integrator.step(dt) + t += dt + config.dt = dt_old + + self.result = ResultAssembler.assemble(self) + return self.result["numerical"] + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/time_integration/__init__.py b/example/1d-linear-convection/weno3/python-v1/02a/src/time_integration/__init__.py new file mode 100644 index 000000000..6342de3bb --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/time_integration/__init__.py @@ -0,0 +1,8 @@ +# time_integration/__init__.py + +# 导出统一接口 +from .factory import TimeIntegratorFactory +from .base import TimeIntegrator + +# 触发子模块注册(关键!) +from . import rk1, rk2, rk3 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/time_integration/base.py b/example/1d-linear-convection/weno3/python-v1/02a/src/time_integration/base.py new file mode 100644 index 000000000..0cdf29a48 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/time_integration/base.py @@ -0,0 +1,27 @@ +# time_integration/base.py + +from abc import ABC, abstractmethod + +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + + @abstractmethod + def step(self, dt): + pass + + def compute_residual(self): + """计算残差(委托给 Cfd)""" + self.cfd.compute_residual() + + def apply_boundary(self): + """应用边界条件(委托给 Cfd)""" + self.cfd.apply_boundary() + + def map_idx(self, i): + """物理网格索引 → 残差数组索引""" + return i - self.domain.ist \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/time_integration/factory.py b/example/1d-linear-convection/weno3/python-v1/02a/src/time_integration/factory.py new file mode 100644 index 000000000..94662db22 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/time_integration/factory.py @@ -0,0 +1,10 @@ +# time_integration/factory.py + +from core.registry import BaseFactory + +class TimeIntegratorFactory: + @staticmethod + def create(cfd) -> 'TimeIntegrator': + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/time_integration/rk1.py b/example/1d-linear-convection/weno3/python-v1/02a/src/time_integration/rk1.py new file mode 100644 index 000000000..b4c8a0211 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/time_integration/rk1.py @@ -0,0 +1,15 @@ +# time_integration/rk1.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/time_integration/rk2.py b/example/1d-linear-convection/weno3/python-v1/02a/src/time_integration/rk2.py new file mode 100644 index 000000000..6d2be3049 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/time_integration/rk2.py @@ -0,0 +1,29 @@ +# time_integration/rk2.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + 0.5 * self.solution.un[i] + + 0.5 * self.solution.u[i] + + 0.5 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02a/src/time_integration/rk3.py b/example/1d-linear-convection/weno3/python-v1/02a/src/time_integration/rk3.py new file mode 100644 index 000000000..c70791e13 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02a/src/time_integration/rk3.py @@ -0,0 +1,43 @@ +# time_integration/rk3.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # Stage 1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # Stage 2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = ( + 0.75 * self.solution.un[i] + + 0.25 * self.solution.u[i] + + 0.25 * dt * self.solution.res[j] + ) + self.solution.u[:] = u2 + self.apply_boundary() + + # Stage 3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + c1 * self.solution.un[i] + + c2 * self.solution.u[i] + + c3 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/example/run_eno_weno.py b/example/1d-linear-convection/weno3/python-v1/02b/example/run_eno_weno.py new file mode 100644 index 000000000..c4063cb75 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/example/run_eno_weno.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +# example/run_eno_weno.py + +import sys +import os +src_path = os.path.join(os.path.dirname(__file__), '..', 'src') +if src_path not in sys.path: + sys.path.insert(0, src_path) + +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +from problems.linear_advection import LinearAdvectionProblem + + +def performEnoWenoAnalysisBAK(): + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO3 求解 + print("Running ENO3 solver...") + config_eno3 = CfdConfig() + config_eno3.with_reconstruction("eno", 3) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno3 = LinearAdvectionProblem(config_eno3) + cfd_eno3 = Cfd(problem_eno3, mesh) # ← 注入 problem + cfd_eno3.run() + + # 3. 配置并运行 WENO3 求解 + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno3 = LinearAdvectionProblem(config_weno3) + cfd_weno3 = Cfd(problem_weno3, mesh) # ← 注入 problem + cfd_weno3.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" + ) + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO5 求解 + print("Running ENO5 solver...") + config_eno5 = CfdConfig() + config_eno5.with_reconstruction("eno", 5) + config_eno5.dt = 0.0025 + config_eno5.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno5 = LinearAdvectionProblem(config_eno5) + cfd_eno5 = Cfd(problem_eno5, mesh) # ← 注入 problem + cfd_eno5.run() + + # 3. 配置并运行 WENO5求解 + print("Running WENO5 solver...") + config_weno5 = CfdConfig() + config_weno5.with_reconstruction("weno", 5) + config_weno5.dt = 0.0025 + config_weno5.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno5 = LinearAdvectionProblem(config_weno5) + cfd_weno5 = Cfd(problem_weno5, mesh) # ← 注入 problem + cfd_weno5.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno5.result, + weno_result=cfd_weno5.result, + save_path="eno_weno_comparison.png" + ) + + +if __name__ == "__main__": + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/boundary.py b/example/1d-linear-convection/weno3/python-v1/02b/src/boundary.py new file mode 100644 index 000000000..2d2cfbaf8 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/boundary.py @@ -0,0 +1,104 @@ +from abc import ABC, abstractmethod +from core.registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from core.registry import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) + diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/config.py b/example/1d-linear-convection/weno3/python-v1/02b/src/config.py new file mode 100644 index 000000000..6432c80e9 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/config.py @@ -0,0 +1,45 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + #self.ic_type = "sin" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # "rusanov", "engquist-osher" + #self.flux_type = "engquist-osher" # "rusanov", "engquist-osher" + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + print(f"scheme={scheme}") + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/core/registry.py b/example/1d-linear-convection/weno3/python-v1/02b/src/core/registry.py new file mode 100644 index 000000000..4a52afc26 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/core/registry.py @@ -0,0 +1,83 @@ +# core/registry.py +""" +统一注册系统 + 通用工厂 +- ComponentRegistry: 组件注册表 +- register_component: 装饰器 +- BaseFactory: 通用工厂类 +""" + +from typing import Dict, Type, Any + + +# ==================== 1. 注册表核心 ==================== +class ComponentRegistry: + """组件注册表""" + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True + + @classmethod + def set_verbose(cls, verbose: bool): + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + #print(f"ComponentRegistry register cls={cls}") + #print(f"ComponentRegistry register name={name}") + #print(f"ComponentRegistry register component_class={component_class}") + if category not in cls._registries: + cls._registries[category] = {} + if name in cls._registries[category]: + if cls._registries[category][name] != component_class and cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + if category not in cls._registries: + raise ValueError(f"❌ 未知类别: {category} (可用: {list(cls._registries.keys())})") + if name not in cls._registries[category]: + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {list(cls._registries[category].keys())})") + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) for cat, comps in cls._registries.items()} + + +# ==================== 2. 装饰器 ==================== +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + #print(f"register_component decorator name={name}") + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + +# ==================== 3. 通用工厂 ==================== +class BaseFactory: + """通用工厂基类""" + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + name_lower = name.lower() + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + available = ComponentRegistry.list_all().get(category, []) + if available: + error_msg = f"不支持的 {category} 类型 '{name}'。可用类型:{available}" + else: + error_msg = f"不支持的 {category} 类型 '{name}'(无已注册组件)" + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/domain.py b/example/1d-linear-convection/weno3/python-v1/02b/src/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/equations/base.py b/example/1d-linear-convection/weno3/python-v1/02b/src/equations/base.py new file mode 100644 index 000000000..92e54f126 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/equations/base.py @@ -0,0 +1,56 @@ +# equations/base.py + +from abc import ABC, abstractmethod +import numpy as np + +class Equation(ABC): + """ + 控制方程抽象基类 + 所有物理方程(线性对流、Euler、MHD)必须继承此类 + """ + + @property + @abstractmethod + def num_equations(self) -> int: + """返回方程组变量数(标量=1,Euler=3,MHD=8)""" + pass + + @abstractmethod + def flux(self, u): + """ + 计算通量函数 f(u) + :param u: 状态向量 (num_equations,) + :return: 通量向量 (num_equations,) + """ + pass + + @abstractmethod + def max_wave_speed(self, u): + """ + 计算最大波速(用于 CFL 条件) + :param u: 状态向量 + :return: 标量波速 + """ + pass + + @abstractmethod + def wave_speed(self): + """ + 计算波速(用于 CFL 条件) + :param u: 状态向量 + :return: 标量波速 + """ + pass + + def exact_solution(self, x, t, initial_condition_func): + """ + 可选:提供解析解 + 子类可重写;若无解析解,调用方应捕获 NotImplementedError + :param x: 空间坐标数组 (ncells,) + :param t: 时间 + :param initial_condition_func: 初值函数 u0(x) -> (num_equations, ncells) + :return: 解 u(x,t) -> (num_equations, ncells) + """ + raise NotImplementedError( + f"Equation '{self.__class__.__name__}' does not provide an exact solution." + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/equations/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/02b/src/equations/linear_advection.py new file mode 100644 index 000000000..26aa026be --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/equations/linear_advection.py @@ -0,0 +1,42 @@ +# equations/linear_advection.py + +import numpy as np +from .base import Equation + +class LinearAdvectionEquation(Equation): + """ + 线性对流方程: u_t + c * u_x = 0 + 支持标量(num_equations=1) + """ + + def __init__(self, wave_speed: float): + self.c = wave_speed + + @property + def num_equations(self) -> int: + return 1 + + def flux(self, u): + """f(u) = c * u""" + return self.c * u + + def max_wave_speed(self, u): + """最大波速 = |c|""" + return abs(self.c) + + def wave_speed(self): + return self.c + + + def exact_solution(self, x, t, initial_condition_func): + """ + 解析解: u(x, t) = u0(x - c * t) + 支持周期边界 + """ + if len(x) == 0: + return np.zeros((1, 0)) + + L = x[-1] - x[0] # 假设均匀周期网格 + x_shifted = (x - self.c * t + L) % L + u0 = initial_condition_func(x_shifted) # (1, ncells) + return u0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/flux/__init__.py b/example/1d-linear-convection/weno3/python-v1/02b/src/flux/__init__.py new file mode 100644 index 000000000..432a36cb0 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/flux/__init__.py @@ -0,0 +1,7 @@ +# flux/__init__.py +from .base import InviscidFluxCalculator +from .factory import FluxCalculatorFactory + +# 确保子模块被导入以触发注册 +from . import rusanov +from . import engquist_osher \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/flux/base.py b/example/1d-linear-convection/weno3/python-v1/02b/src/flux/base.py new file mode 100644 index 000000000..7a5a134fc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/flux/base.py @@ -0,0 +1,25 @@ +# flux/base.py +""" +抽象通量计算基类 +""" + +from abc import ABC, abstractmethod + +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/flux/engquist_osher.py b/example/1d-linear-convection/weno3/python-v1/02b/src/flux/engquist_osher.py new file mode 100644 index 000000000..eb7b8cdf7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/flux/engquist_osher.py @@ -0,0 +1,20 @@ +# flux/engquist_osher.py +""" +Engquist-Osher 通量计算器(线性对流专用) +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + eq = self.cfd.problem.equation + for i in range(self.mesh.nnodes): + c = eq.wave_speed() + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/flux/factory.py b/example/1d-linear-convection/weno3/python-v1/02b/src/flux/factory.py new file mode 100644 index 000000000..5ae77c3fa --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/flux/factory.py @@ -0,0 +1,25 @@ +# flux/factory.py +""" +通量计算器专用工厂(封装注册细节,提供清晰接口) +符合你希望“将创建逻辑封装在工厂中”的设计原则 +""" + +from core.registry import BaseFactory + +class FluxCalculatorFactory: + """通量计算器工厂:隐藏注册类别和组件名称等细节""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD 主对象,包含 config.flux_type 等配置 + + Returns: + 实现 InviscidFluxCalculator 接口的通量计算器实例 + """ + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/flux/rusanov.py b/example/1d-linear-convection/weno3/python-v1/02b/src/flux/rusanov.py new file mode 100644 index 000000000..baa7c62c5 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/flux/rusanov.py @@ -0,0 +1,25 @@ +# flux/rusanov.py +""" +Rusanov(Lax-Friedrichs)通量计算器 +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量,使用 Equation 解耦物理参数""" + + def compute(self, q_face_left, q_face_right, flux): + # 从 cfd 获取 equation(与 Julia 对齐) + eq = self.cfd.problem.equation + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = eq.wave_speed() + c_R = eq.wave_speed() + # 通过 equation 计算通量和波速 + F_L = eq.flux(u_L) + F_R = eq.flux(u_R) + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/initial_condition/__init__.py b/example/1d-linear-convection/weno3/python-v1/02b/src/initial_condition/__init__.py new file mode 100644 index 000000000..9eab71d5e --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/initial_condition/__init__.py @@ -0,0 +1,12 @@ +# src/initial_conditions/__init__.py +""" +初始条件模块 +提供各种初始条件实现和工厂创建接口 +""" + +from .base import InitialCondition +from .factory import InitialConditionFactory + +from .step import StepFunctionIC +from .sine import SineWaveIC +from .gaussian import GaussianPulseIC diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/initial_condition/base.py b/example/1d-linear-convection/weno3/python-v1/02b/src/initial_condition/base.py new file mode 100644 index 000000000..8a33a0d56 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/initial_condition/base.py @@ -0,0 +1,25 @@ +# src/initial_conditions/base.py +from abc import ABC, abstractmethod +import numpy as np + +class InitialCondition(ABC): + """初始条件抽象基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + """内部辅助方法:将值应用到物理区域""" + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/initial_condition/factory.py b/example/1d-linear-convection/weno3/python-v1/02b/src/initial_condition/factory.py new file mode 100644 index 000000000..2a7558b2c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/initial_condition/factory.py @@ -0,0 +1,24 @@ +# src/initial_conditions/factory.py +from core.registry import BaseFactory +from .base import InitialCondition + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + config: 配置对象,包含ic_type属性 + + Returns: + 初始条件实例 + """ + return BaseFactory.create_component('initial_condition', config.ic_type, config) + + @classmethod + def get_available_types(cls): + """获取所有可用的初始条件类型""" + return BaseFactory.get_available_components('initial_condition') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/initial_condition/gaussian.py b/example/1d-linear-convection/weno3/python-v1/02b/src/initial_condition/gaussian.py new file mode 100644 index 000000000..1dad463be --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/initial_condition/gaussian.py @@ -0,0 +1,16 @@ +# src/initial_conditions/gaussian.py +import numpy as np +from .base import InitialCondition +from core.registry import register_component + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/initial_condition/sine.py b/example/1d-linear-convection/weno3/python-v1/02b/src/initial_condition/sine.py new file mode 100644 index 000000000..c061fa5a9 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/initial_condition/sine.py @@ -0,0 +1,15 @@ +# src/initial_conditions/sine.py +import numpy as np +from .base import InitialCondition +from core.registry import register_component + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/initial_condition/step.py b/example/1d-linear-convection/weno3/python-v1/02b/src/initial_condition/step.py new file mode 100644 index 000000000..ff1120b2c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/initial_condition/step.py @@ -0,0 +1,17 @@ +# src/initial_conditions/step.py +import numpy as np +from .base import InitialCondition +from core.registry import register_component + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/mesh.py b/example/1d-linear-convection/weno3/python-v1/02b/src/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/plotter.py b/example/1d-linear-convection/weno3/python-v1/02b/src/plotter.py new file mode 100644 index 000000000..e1f99ae25 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/plotter.py @@ -0,0 +1,109 @@ +# plotter.py + +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/problem.py b/example/1d-linear-convection/weno3/python-v1/02b/src/problem.py new file mode 100644 index 000000000..53719f265 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/problem.py @@ -0,0 +1,38 @@ +# problem.py +""" +问题定义模块:每个 Problem 子类代表一个完整测试用例 +包含初始条件、解析解(可选)、物理方程等 +""" + +from abc import ABC, abstractmethod +from initial_condition import InitialConditionFactory + + +class Problem(ABC): + """ + 抽象问题基类 + 每个具体问题(如线性对流、Sod 激波管)应继承此类 + """ + def __init__(self, config): + self.config = config + + @abstractmethod + def initial_condition(self): + """ + 返回 InitialCondition 实例 + """ + pass + + def exact_solution(self, cfd): + """ + 可选:返回解析解(数值数组) + 若无解析解,可抛出 NotImplementedError 或返回 None + """ + x = cfd.domain.mesh.xcc + raise NotImplementedError( + f"Problem '{self.__class__.__name__}' does not provide an exact solution." + ) + + @property + def name(self): + return self.__class__.__name__ \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/problems/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/02b/src/problems/linear_advection.py new file mode 100644 index 000000000..b6d548f51 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/problems/linear_advection.py @@ -0,0 +1,39 @@ +# problems/linear_advection.py + +import numpy as np +from problem import Problem +from equations.linear_advection import LinearAdvectionEquation +from initial_condition import InitialConditionFactory + +class LinearAdvectionProblem(Problem): + """ + 线性对流问题:u_t + c u_x = 0 + 使用周期边界 + 任意初始条件 + """ + + def __init__(self, config): + super().__init__(config) + # 持有物理方程实例 + self.equation = LinearAdvectionEquation(wave_speed=config.wave_speed) + self._ic_cache = None # 缓存 IC 实例 + + def initial_condition(self): + """返回初始条件实例""" + if self._ic_cache is None: + self._ic_cache = InitialConditionFactory.create(self.config) + return self._ic_cache + + def exact_solution(self, cfd): + """ + 委托给 Equation 计算解析解 + """ + ic = self.initial_condition() + # 包装 evaluate_at 以返回 (1, ncells) + def vectorized_ic(x): + u0_scalar = ic.evaluate_at(x) # (ncells,) + return u0_scalar[np.newaxis, :] # (1, ncells) + + x = cfd.domain.mesh.xcc + t = cfd.config.final_time + u_exact = self.equation.exact_solution(x, t, vectorized_ic) + return u_exact[0] # 返回标量数组 (ncells,) 以兼容现有绘图逻辑 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python-v1/02b/src/reconstructor/__init__.py new file mode 100644 index 000000000..d705bb98a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/reconstructor/__init__.py @@ -0,0 +1,6 @@ +# reconstructor/__init__.py +from .base import Reconstructor +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from .weno5 import Weno5Reconstructor +from .factory import ReconstructorFactory diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/reconstructor/base.py b/example/1d-linear-convection/weno3/python-v1/02b/src/reconstructor/base.py new file mode 100644 index 000000000..de00327cc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def compute_face_values(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/reconstructor/eno.py b/example/1d-linear-convection/weno3/python-v1/02b/src/reconstructor/eno.py new file mode 100644 index 000000000..60699adf6 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/reconstructor/eno.py @@ -0,0 +1,96 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.initialize(self.config.spatial_order, self.domain.ntcells) + + def initialize(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def compute_face_values(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/reconstructor/factory.py b/example/1d-linear-convection/weno3/python-v1/02b/src/reconstructor/factory.py new file mode 100644 index 000000000..e287ab84b --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/reconstructor/factory.py @@ -0,0 +1,14 @@ +# reconstructor/factory.py +from core.registry import BaseFactory + +class ReconstructorFactory: + @staticmethod + def create(cfd): + config = cfd.config + scheme = config.recon_scheme.lower() + + if scheme.startswith("weno"): + if scheme == "weno": + order = getattr(config, 'spatial_order', None) + scheme = f"weno{order}" + return BaseFactory.create_component('reconstructor', scheme, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python-v1/02b/src/reconstructor/weno3.py new file mode 100644 index 000000000..f45eb35f2 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/reconstructor/weno3.py @@ -0,0 +1,62 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def __init__(self, cfd): + self.cfd = cfd + + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/reconstructor/weno5.py b/example/1d-linear-convection/weno3/python-v1/02b/src/reconstructor/weno5.py new file mode 100644 index 000000000..9d6652752 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/reconstructor/weno5.py @@ -0,0 +1,71 @@ +# reconstructor/weno5.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno5') +class Weno5Reconstructor(Reconstructor): + def __init__(self, cfd): + self.cfd = cfd + + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3, v4, v5 = u[i-2], u[i-1], u[i], u[i+1], u[i+2] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3, v4, v5) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3, v4, v5 = u[i-2], u[i-1], u[i], u[i+1], u[i+2] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3, v4, v5) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3, v4, v5): + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)**2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)**2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)**2 + (1.0/4.0)*(v2 - v4)**2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)**2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)**2 + d0 = 1.0/10.0 + d1 = 3.0/5.0 + d2 = 3.0/10.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha2 = d2 / (eps + beta2)**2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = 1.0/3.0*v1-7.0/6.0*v2+11.0/6.0*v3 # r=2 + q1 = -1.0/6.0*v2+5.0/6.0*v3+1.0/3.0*v4 # r=1 + q2 = 1.0/3.0*v3+5.0/6.0*v4-1.0/6.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3, v4, v5): + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)**2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)**2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)**2 + (1.0/4.0)*(v2 - v4)**2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)**2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)**2 + d0 = 3.0/10.0 + d1 = 3.0/5.0 + d2 = 1.0/10.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha2 = d2 / (eps + beta2)**2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = -1.0/6.0*v1+5.0/6.0*v2+1.0/3.0*v3 # r=2 + q1 = 1.0/3.0*v2+5.0/6.0*v3-1.0/6.0*v4 # r=1 + q2 = 11.0/6.0*v3-7.0/6.0*v4+1.0/3.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/residual.py b/example/1d-linear-convection/weno3/python-v1/02b/src/residual.py new file mode 100644 index 000000000..27d5a0c41 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/residual.py @@ -0,0 +1,41 @@ +# residual.py + +from flux.factory import FluxCalculatorFactory +from reconstructor import ReconstructorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + + #self.reconstructor = ReconstructorFactory.create(self.config, self.domain) + self.reconstructor = ReconstructorFactory.create(cfd) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._compute_face_values() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _compute_face_values(self): + """私有方法:界面值重建""" + self.reconstructor.compute_face_values(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/result.py b/example/1d-linear-convection/weno3/python-v1/02b/src/result.py new file mode 100644 index 000000000..5e572216d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/result.py @@ -0,0 +1,25 @@ +# result.py + +class ResultAssembler: + @staticmethod + def assemble(cfd: 'Cfd') -> dict: + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied].copy() + + # ✅ 从 problem 获取解析解(唯一正确路径) + try: + analytical = cfd.problem.exact_solution(cfd) + except NotImplementedError: + analytical = None # 或 np.full_like(u_numerical, np.nan) + + return { + "x": cfd.domain.mesh.xcc, + "numerical": u_numerical, + "analytical": analytical, + "config": { + "scheme": cfd.config.recon_scheme, + "order": cfd.config.spatial_order, + "rk_order": cfd.config.rk_order, + "final_time": cfd.config.final_time, + "problem": cfd.problem.name, + } + } \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/solution.py b/example/1d-linear-convection/weno3/python-v1/02b/src/solution.py new file mode 100644 index 000000000..90666c34a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/solution.py @@ -0,0 +1,32 @@ +# solution.py +import numpy as np + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/solver.py b/example/1d-linear-convection/weno3/python-v1/02b/src/solver.py new file mode 100644 index 000000000..316be231e --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/solver.py @@ -0,0 +1,67 @@ +# solver.py + +from mesh import Mesh +from domain import Domain +from solution import Solution +from config import CfdConfig +from result import ResultAssembler +from problem import Problem +from initial_condition import InitialConditionFactory +from boundary import BoundaryConditionFactory +from residual import ResidualCalculator +from time_integration import TimeIntegrator,TimeIntegratorFactory + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, problem: Problem, mesh): + self.problem = problem + self.config = problem.config + self.domain = Domain(problem.config, mesh) + self.solution = Solution(problem.config, self.domain) + + self.initial_condition = InitialConditionFactory.create(self.config) + self.boundary_condition = BoundaryConditionFactory.create(self) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + + # ==================== 公共接口(供 TimeIntegrator 调用) ==================== + def compute_residual(self): + """计算物理残差(封装重建→通量→散度)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件""" + self.boundary_condition.apply(self.solution.u) + + # ==================== 初始化 ==================== + def initialize(self): + """初始化全场:先 IC,再 BC,最后同步 old field""" + self.initial_condition.apply(self.solution) + # 应用边界条件到初始场 + self.apply_boundary() + # 同步 old field + self.solution.update_old_field() + + + def run(self): + self.initialize() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + self.integrator.step(dt) + t += dt + config.dt = dt_old + + self.result = ResultAssembler.assemble(self) + return self.result["numerical"] + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/time_integration/__init__.py b/example/1d-linear-convection/weno3/python-v1/02b/src/time_integration/__init__.py new file mode 100644 index 000000000..6342de3bb --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/time_integration/__init__.py @@ -0,0 +1,8 @@ +# time_integration/__init__.py + +# 导出统一接口 +from .factory import TimeIntegratorFactory +from .base import TimeIntegrator + +# 触发子模块注册(关键!) +from . import rk1, rk2, rk3 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/time_integration/base.py b/example/1d-linear-convection/weno3/python-v1/02b/src/time_integration/base.py new file mode 100644 index 000000000..0cdf29a48 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/time_integration/base.py @@ -0,0 +1,27 @@ +# time_integration/base.py + +from abc import ABC, abstractmethod + +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + + @abstractmethod + def step(self, dt): + pass + + def compute_residual(self): + """计算残差(委托给 Cfd)""" + self.cfd.compute_residual() + + def apply_boundary(self): + """应用边界条件(委托给 Cfd)""" + self.cfd.apply_boundary() + + def map_idx(self, i): + """物理网格索引 → 残差数组索引""" + return i - self.domain.ist \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/time_integration/factory.py b/example/1d-linear-convection/weno3/python-v1/02b/src/time_integration/factory.py new file mode 100644 index 000000000..94662db22 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/time_integration/factory.py @@ -0,0 +1,10 @@ +# time_integration/factory.py + +from core.registry import BaseFactory + +class TimeIntegratorFactory: + @staticmethod + def create(cfd) -> 'TimeIntegrator': + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/time_integration/rk1.py b/example/1d-linear-convection/weno3/python-v1/02b/src/time_integration/rk1.py new file mode 100644 index 000000000..b4c8a0211 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/time_integration/rk1.py @@ -0,0 +1,15 @@ +# time_integration/rk1.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/time_integration/rk2.py b/example/1d-linear-convection/weno3/python-v1/02b/src/time_integration/rk2.py new file mode 100644 index 000000000..6d2be3049 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/time_integration/rk2.py @@ -0,0 +1,29 @@ +# time_integration/rk2.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + 0.5 * self.solution.un[i] + + 0.5 * self.solution.u[i] + + 0.5 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02b/src/time_integration/rk3.py b/example/1d-linear-convection/weno3/python-v1/02b/src/time_integration/rk3.py new file mode 100644 index 000000000..c70791e13 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02b/src/time_integration/rk3.py @@ -0,0 +1,43 @@ +# time_integration/rk3.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # Stage 1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # Stage 2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = ( + 0.75 * self.solution.un[i] + + 0.25 * self.solution.u[i] + + 0.25 * dt * self.solution.res[j] + ) + self.solution.u[:] = u2 + self.apply_boundary() + + # Stage 3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + c1 * self.solution.un[i] + + c2 * self.solution.u[i] + + c3 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/example/run_eno_weno.py b/example/1d-linear-convection/weno3/python-v1/02c/example/run_eno_weno.py new file mode 100644 index 000000000..c4063cb75 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/example/run_eno_weno.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +# example/run_eno_weno.py + +import sys +import os +src_path = os.path.join(os.path.dirname(__file__), '..', 'src') +if src_path not in sys.path: + sys.path.insert(0, src_path) + +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +from problems.linear_advection import LinearAdvectionProblem + + +def performEnoWenoAnalysisBAK(): + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO3 求解 + print("Running ENO3 solver...") + config_eno3 = CfdConfig() + config_eno3.with_reconstruction("eno", 3) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno3 = LinearAdvectionProblem(config_eno3) + cfd_eno3 = Cfd(problem_eno3, mesh) # ← 注入 problem + cfd_eno3.run() + + # 3. 配置并运行 WENO3 求解 + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno3 = LinearAdvectionProblem(config_weno3) + cfd_weno3 = Cfd(problem_weno3, mesh) # ← 注入 problem + cfd_weno3.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" + ) + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO5 求解 + print("Running ENO5 solver...") + config_eno5 = CfdConfig() + config_eno5.with_reconstruction("eno", 5) + config_eno5.dt = 0.0025 + config_eno5.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno5 = LinearAdvectionProblem(config_eno5) + cfd_eno5 = Cfd(problem_eno5, mesh) # ← 注入 problem + cfd_eno5.run() + + # 3. 配置并运行 WENO5求解 + print("Running WENO5 solver...") + config_weno5 = CfdConfig() + config_weno5.with_reconstruction("weno", 5) + config_weno5.dt = 0.0025 + config_weno5.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno5 = LinearAdvectionProblem(config_weno5) + cfd_weno5 = Cfd(problem_weno5, mesh) # ← 注入 problem + cfd_weno5.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno5.result, + weno_result=cfd_weno5.result, + save_path="eno_weno_comparison.png" + ) + + +if __name__ == "__main__": + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/boundary/__init__.py b/example/1d-linear-convection/weno3/python-v1/02c/src/boundary/__init__.py new file mode 100644 index 000000000..20120e8a5 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/boundary/__init__.py @@ -0,0 +1,21 @@ +# src/boundary/__init__.py +""" +边界条件模块 +提供各种边界条件实现和工厂创建接口 +""" + +from .base import BoundaryCondition +from .factory import BoundaryConditionFactory + +# 导入具体类以触发注册 +from .periodic import PeriodicBoundary +from .dirichlet import DirichletBoundary +from .neumann import NeumannBoundary + +__all__ = [ + 'BoundaryCondition', + 'BoundaryConditionFactory', + 'PeriodicBoundary', + 'DirichletBoundary', + 'NeumannBoundary', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/boundary/base.py b/example/1d-linear-convection/weno3/python-v1/02c/src/boundary/base.py new file mode 100644 index 000000000..cddf649dd --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/boundary/base.py @@ -0,0 +1,18 @@ +# src/boundary/base.py +from abc import ABC, abstractmethod + +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/boundary/dirichlet.py b/example/1d-linear-convection/weno3/python-v1/02c/src/boundary/dirichlet.py new file mode 100644 index 000000000..1eff36994 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/boundary/dirichlet.py @@ -0,0 +1,27 @@ +# src/boundary/dirichlet.py +from .base import BoundaryCondition +from core.registry import register_component + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/boundary/factory.py b/example/1d-linear-convection/weno3/python-v1/02c/src/boundary/factory.py new file mode 100644 index 000000000..4bd7ae55a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/boundary/factory.py @@ -0,0 +1,24 @@ +# src/boundary/factory.py +from core.registry import BaseFactory + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) + + @staticmethod + def get_available_types(): + """获取所有可用的边界条件类型""" + return BaseFactory.get_available_components('boundary') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/boundary/neumann.py b/example/1d-linear-convection/weno3/python-v1/02c/src/boundary/neumann.py new file mode 100644 index 000000000..6eb72f644 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/boundary/neumann.py @@ -0,0 +1,23 @@ +# src/boundary/neumann.py +from .base import BoundaryCondition +from core.registry import register_component + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/boundary/periodic.py b/example/1d-linear-convection/weno3/python-v1/02c/src/boundary/periodic.py new file mode 100644 index 000000000..bd601d95d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/boundary/periodic.py @@ -0,0 +1,19 @@ +# src/boundary/periodic.py +from .base import BoundaryCondition +from core.registry import register_component + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/config.py b/example/1d-linear-convection/weno3/python-v1/02c/src/config.py new file mode 100644 index 000000000..6432c80e9 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/config.py @@ -0,0 +1,45 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + #self.ic_type = "sin" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # "rusanov", "engquist-osher" + #self.flux_type = "engquist-osher" # "rusanov", "engquist-osher" + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + print(f"scheme={scheme}") + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/core/registry.py b/example/1d-linear-convection/weno3/python-v1/02c/src/core/registry.py new file mode 100644 index 000000000..4a52afc26 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/core/registry.py @@ -0,0 +1,83 @@ +# core/registry.py +""" +统一注册系统 + 通用工厂 +- ComponentRegistry: 组件注册表 +- register_component: 装饰器 +- BaseFactory: 通用工厂类 +""" + +from typing import Dict, Type, Any + + +# ==================== 1. 注册表核心 ==================== +class ComponentRegistry: + """组件注册表""" + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True + + @classmethod + def set_verbose(cls, verbose: bool): + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + #print(f"ComponentRegistry register cls={cls}") + #print(f"ComponentRegistry register name={name}") + #print(f"ComponentRegistry register component_class={component_class}") + if category not in cls._registries: + cls._registries[category] = {} + if name in cls._registries[category]: + if cls._registries[category][name] != component_class and cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + if category not in cls._registries: + raise ValueError(f"❌ 未知类别: {category} (可用: {list(cls._registries.keys())})") + if name not in cls._registries[category]: + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {list(cls._registries[category].keys())})") + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) for cat, comps in cls._registries.items()} + + +# ==================== 2. 装饰器 ==================== +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + #print(f"register_component decorator name={name}") + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + +# ==================== 3. 通用工厂 ==================== +class BaseFactory: + """通用工厂基类""" + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + name_lower = name.lower() + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + available = ComponentRegistry.list_all().get(category, []) + if available: + error_msg = f"不支持的 {category} 类型 '{name}'。可用类型:{available}" + else: + error_msg = f"不支持的 {category} 类型 '{name}'(无已注册组件)" + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/domain.py b/example/1d-linear-convection/weno3/python-v1/02c/src/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/equations/base.py b/example/1d-linear-convection/weno3/python-v1/02c/src/equations/base.py new file mode 100644 index 000000000..92e54f126 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/equations/base.py @@ -0,0 +1,56 @@ +# equations/base.py + +from abc import ABC, abstractmethod +import numpy as np + +class Equation(ABC): + """ + 控制方程抽象基类 + 所有物理方程(线性对流、Euler、MHD)必须继承此类 + """ + + @property + @abstractmethod + def num_equations(self) -> int: + """返回方程组变量数(标量=1,Euler=3,MHD=8)""" + pass + + @abstractmethod + def flux(self, u): + """ + 计算通量函数 f(u) + :param u: 状态向量 (num_equations,) + :return: 通量向量 (num_equations,) + """ + pass + + @abstractmethod + def max_wave_speed(self, u): + """ + 计算最大波速(用于 CFL 条件) + :param u: 状态向量 + :return: 标量波速 + """ + pass + + @abstractmethod + def wave_speed(self): + """ + 计算波速(用于 CFL 条件) + :param u: 状态向量 + :return: 标量波速 + """ + pass + + def exact_solution(self, x, t, initial_condition_func): + """ + 可选:提供解析解 + 子类可重写;若无解析解,调用方应捕获 NotImplementedError + :param x: 空间坐标数组 (ncells,) + :param t: 时间 + :param initial_condition_func: 初值函数 u0(x) -> (num_equations, ncells) + :return: 解 u(x,t) -> (num_equations, ncells) + """ + raise NotImplementedError( + f"Equation '{self.__class__.__name__}' does not provide an exact solution." + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/equations/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/02c/src/equations/linear_advection.py new file mode 100644 index 000000000..26aa026be --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/equations/linear_advection.py @@ -0,0 +1,42 @@ +# equations/linear_advection.py + +import numpy as np +from .base import Equation + +class LinearAdvectionEquation(Equation): + """ + 线性对流方程: u_t + c * u_x = 0 + 支持标量(num_equations=1) + """ + + def __init__(self, wave_speed: float): + self.c = wave_speed + + @property + def num_equations(self) -> int: + return 1 + + def flux(self, u): + """f(u) = c * u""" + return self.c * u + + def max_wave_speed(self, u): + """最大波速 = |c|""" + return abs(self.c) + + def wave_speed(self): + return self.c + + + def exact_solution(self, x, t, initial_condition_func): + """ + 解析解: u(x, t) = u0(x - c * t) + 支持周期边界 + """ + if len(x) == 0: + return np.zeros((1, 0)) + + L = x[-1] - x[0] # 假设均匀周期网格 + x_shifted = (x - self.c * t + L) % L + u0 = initial_condition_func(x_shifted) # (1, ncells) + return u0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/flux/__init__.py b/example/1d-linear-convection/weno3/python-v1/02c/src/flux/__init__.py new file mode 100644 index 000000000..432a36cb0 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/flux/__init__.py @@ -0,0 +1,7 @@ +# flux/__init__.py +from .base import InviscidFluxCalculator +from .factory import FluxCalculatorFactory + +# 确保子模块被导入以触发注册 +from . import rusanov +from . import engquist_osher \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/flux/base.py b/example/1d-linear-convection/weno3/python-v1/02c/src/flux/base.py new file mode 100644 index 000000000..7a5a134fc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/flux/base.py @@ -0,0 +1,25 @@ +# flux/base.py +""" +抽象通量计算基类 +""" + +from abc import ABC, abstractmethod + +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/flux/engquist_osher.py b/example/1d-linear-convection/weno3/python-v1/02c/src/flux/engquist_osher.py new file mode 100644 index 000000000..eb7b8cdf7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/flux/engquist_osher.py @@ -0,0 +1,20 @@ +# flux/engquist_osher.py +""" +Engquist-Osher 通量计算器(线性对流专用) +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + eq = self.cfd.problem.equation + for i in range(self.mesh.nnodes): + c = eq.wave_speed() + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/flux/factory.py b/example/1d-linear-convection/weno3/python-v1/02c/src/flux/factory.py new file mode 100644 index 000000000..5ae77c3fa --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/flux/factory.py @@ -0,0 +1,25 @@ +# flux/factory.py +""" +通量计算器专用工厂(封装注册细节,提供清晰接口) +符合你希望“将创建逻辑封装在工厂中”的设计原则 +""" + +from core.registry import BaseFactory + +class FluxCalculatorFactory: + """通量计算器工厂:隐藏注册类别和组件名称等细节""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD 主对象,包含 config.flux_type 等配置 + + Returns: + 实现 InviscidFluxCalculator 接口的通量计算器实例 + """ + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/flux/rusanov.py b/example/1d-linear-convection/weno3/python-v1/02c/src/flux/rusanov.py new file mode 100644 index 000000000..baa7c62c5 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/flux/rusanov.py @@ -0,0 +1,25 @@ +# flux/rusanov.py +""" +Rusanov(Lax-Friedrichs)通量计算器 +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量,使用 Equation 解耦物理参数""" + + def compute(self, q_face_left, q_face_right, flux): + # 从 cfd 获取 equation(与 Julia 对齐) + eq = self.cfd.problem.equation + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = eq.wave_speed() + c_R = eq.wave_speed() + # 通过 equation 计算通量和波速 + F_L = eq.flux(u_L) + F_R = eq.flux(u_R) + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/initial_condition/__init__.py b/example/1d-linear-convection/weno3/python-v1/02c/src/initial_condition/__init__.py new file mode 100644 index 000000000..9eab71d5e --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/initial_condition/__init__.py @@ -0,0 +1,12 @@ +# src/initial_conditions/__init__.py +""" +初始条件模块 +提供各种初始条件实现和工厂创建接口 +""" + +from .base import InitialCondition +from .factory import InitialConditionFactory + +from .step import StepFunctionIC +from .sine import SineWaveIC +from .gaussian import GaussianPulseIC diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/initial_condition/base.py b/example/1d-linear-convection/weno3/python-v1/02c/src/initial_condition/base.py new file mode 100644 index 000000000..8a33a0d56 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/initial_condition/base.py @@ -0,0 +1,25 @@ +# src/initial_conditions/base.py +from abc import ABC, abstractmethod +import numpy as np + +class InitialCondition(ABC): + """初始条件抽象基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + """内部辅助方法:将值应用到物理区域""" + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/initial_condition/factory.py b/example/1d-linear-convection/weno3/python-v1/02c/src/initial_condition/factory.py new file mode 100644 index 000000000..2a7558b2c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/initial_condition/factory.py @@ -0,0 +1,24 @@ +# src/initial_conditions/factory.py +from core.registry import BaseFactory +from .base import InitialCondition + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + config: 配置对象,包含ic_type属性 + + Returns: + 初始条件实例 + """ + return BaseFactory.create_component('initial_condition', config.ic_type, config) + + @classmethod + def get_available_types(cls): + """获取所有可用的初始条件类型""" + return BaseFactory.get_available_components('initial_condition') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/initial_condition/gaussian.py b/example/1d-linear-convection/weno3/python-v1/02c/src/initial_condition/gaussian.py new file mode 100644 index 000000000..1dad463be --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/initial_condition/gaussian.py @@ -0,0 +1,16 @@ +# src/initial_conditions/gaussian.py +import numpy as np +from .base import InitialCondition +from core.registry import register_component + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/initial_condition/sine.py b/example/1d-linear-convection/weno3/python-v1/02c/src/initial_condition/sine.py new file mode 100644 index 000000000..c061fa5a9 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/initial_condition/sine.py @@ -0,0 +1,15 @@ +# src/initial_conditions/sine.py +import numpy as np +from .base import InitialCondition +from core.registry import register_component + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/initial_condition/step.py b/example/1d-linear-convection/weno3/python-v1/02c/src/initial_condition/step.py new file mode 100644 index 000000000..ff1120b2c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/initial_condition/step.py @@ -0,0 +1,17 @@ +# src/initial_conditions/step.py +import numpy as np +from .base import InitialCondition +from core.registry import register_component + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/mesh.py b/example/1d-linear-convection/weno3/python-v1/02c/src/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/plotter.py b/example/1d-linear-convection/weno3/python-v1/02c/src/plotter.py new file mode 100644 index 000000000..e1f99ae25 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/plotter.py @@ -0,0 +1,109 @@ +# plotter.py + +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/problem.py b/example/1d-linear-convection/weno3/python-v1/02c/src/problem.py new file mode 100644 index 000000000..53719f265 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/problem.py @@ -0,0 +1,38 @@ +# problem.py +""" +问题定义模块:每个 Problem 子类代表一个完整测试用例 +包含初始条件、解析解(可选)、物理方程等 +""" + +from abc import ABC, abstractmethod +from initial_condition import InitialConditionFactory + + +class Problem(ABC): + """ + 抽象问题基类 + 每个具体问题(如线性对流、Sod 激波管)应继承此类 + """ + def __init__(self, config): + self.config = config + + @abstractmethod + def initial_condition(self): + """ + 返回 InitialCondition 实例 + """ + pass + + def exact_solution(self, cfd): + """ + 可选:返回解析解(数值数组) + 若无解析解,可抛出 NotImplementedError 或返回 None + """ + x = cfd.domain.mesh.xcc + raise NotImplementedError( + f"Problem '{self.__class__.__name__}' does not provide an exact solution." + ) + + @property + def name(self): + return self.__class__.__name__ \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/problems/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/02c/src/problems/linear_advection.py new file mode 100644 index 000000000..b6d548f51 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/problems/linear_advection.py @@ -0,0 +1,39 @@ +# problems/linear_advection.py + +import numpy as np +from problem import Problem +from equations.linear_advection import LinearAdvectionEquation +from initial_condition import InitialConditionFactory + +class LinearAdvectionProblem(Problem): + """ + 线性对流问题:u_t + c u_x = 0 + 使用周期边界 + 任意初始条件 + """ + + def __init__(self, config): + super().__init__(config) + # 持有物理方程实例 + self.equation = LinearAdvectionEquation(wave_speed=config.wave_speed) + self._ic_cache = None # 缓存 IC 实例 + + def initial_condition(self): + """返回初始条件实例""" + if self._ic_cache is None: + self._ic_cache = InitialConditionFactory.create(self.config) + return self._ic_cache + + def exact_solution(self, cfd): + """ + 委托给 Equation 计算解析解 + """ + ic = self.initial_condition() + # 包装 evaluate_at 以返回 (1, ncells) + def vectorized_ic(x): + u0_scalar = ic.evaluate_at(x) # (ncells,) + return u0_scalar[np.newaxis, :] # (1, ncells) + + x = cfd.domain.mesh.xcc + t = cfd.config.final_time + u_exact = self.equation.exact_solution(x, t, vectorized_ic) + return u_exact[0] # 返回标量数组 (ncells,) 以兼容现有绘图逻辑 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python-v1/02c/src/reconstructor/__init__.py new file mode 100644 index 000000000..d705bb98a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/reconstructor/__init__.py @@ -0,0 +1,6 @@ +# reconstructor/__init__.py +from .base import Reconstructor +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from .weno5 import Weno5Reconstructor +from .factory import ReconstructorFactory diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/reconstructor/base.py b/example/1d-linear-convection/weno3/python-v1/02c/src/reconstructor/base.py new file mode 100644 index 000000000..de00327cc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def compute_face_values(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/reconstructor/eno.py b/example/1d-linear-convection/weno3/python-v1/02c/src/reconstructor/eno.py new file mode 100644 index 000000000..60699adf6 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/reconstructor/eno.py @@ -0,0 +1,96 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.initialize(self.config.spatial_order, self.domain.ntcells) + + def initialize(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def compute_face_values(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/reconstructor/factory.py b/example/1d-linear-convection/weno3/python-v1/02c/src/reconstructor/factory.py new file mode 100644 index 000000000..e287ab84b --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/reconstructor/factory.py @@ -0,0 +1,14 @@ +# reconstructor/factory.py +from core.registry import BaseFactory + +class ReconstructorFactory: + @staticmethod + def create(cfd): + config = cfd.config + scheme = config.recon_scheme.lower() + + if scheme.startswith("weno"): + if scheme == "weno": + order = getattr(config, 'spatial_order', None) + scheme = f"weno{order}" + return BaseFactory.create_component('reconstructor', scheme, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python-v1/02c/src/reconstructor/weno3.py new file mode 100644 index 000000000..f45eb35f2 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/reconstructor/weno3.py @@ -0,0 +1,62 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def __init__(self, cfd): + self.cfd = cfd + + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/reconstructor/weno5.py b/example/1d-linear-convection/weno3/python-v1/02c/src/reconstructor/weno5.py new file mode 100644 index 000000000..9d6652752 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/reconstructor/weno5.py @@ -0,0 +1,71 @@ +# reconstructor/weno5.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno5') +class Weno5Reconstructor(Reconstructor): + def __init__(self, cfd): + self.cfd = cfd + + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3, v4, v5 = u[i-2], u[i-1], u[i], u[i+1], u[i+2] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3, v4, v5) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3, v4, v5 = u[i-2], u[i-1], u[i], u[i+1], u[i+2] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3, v4, v5) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3, v4, v5): + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)**2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)**2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)**2 + (1.0/4.0)*(v2 - v4)**2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)**2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)**2 + d0 = 1.0/10.0 + d1 = 3.0/5.0 + d2 = 3.0/10.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha2 = d2 / (eps + beta2)**2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = 1.0/3.0*v1-7.0/6.0*v2+11.0/6.0*v3 # r=2 + q1 = -1.0/6.0*v2+5.0/6.0*v3+1.0/3.0*v4 # r=1 + q2 = 1.0/3.0*v3+5.0/6.0*v4-1.0/6.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3, v4, v5): + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)**2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)**2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)**2 + (1.0/4.0)*(v2 - v4)**2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)**2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)**2 + d0 = 3.0/10.0 + d1 = 3.0/5.0 + d2 = 1.0/10.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha2 = d2 / (eps + beta2)**2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = -1.0/6.0*v1+5.0/6.0*v2+1.0/3.0*v3 # r=2 + q1 = 1.0/3.0*v2+5.0/6.0*v3-1.0/6.0*v4 # r=1 + q2 = 11.0/6.0*v3-7.0/6.0*v4+1.0/3.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/residual.py b/example/1d-linear-convection/weno3/python-v1/02c/src/residual.py new file mode 100644 index 000000000..27d5a0c41 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/residual.py @@ -0,0 +1,41 @@ +# residual.py + +from flux.factory import FluxCalculatorFactory +from reconstructor import ReconstructorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + + #self.reconstructor = ReconstructorFactory.create(self.config, self.domain) + self.reconstructor = ReconstructorFactory.create(cfd) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._compute_face_values() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _compute_face_values(self): + """私有方法:界面值重建""" + self.reconstructor.compute_face_values(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/result.py b/example/1d-linear-convection/weno3/python-v1/02c/src/result.py new file mode 100644 index 000000000..5e572216d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/result.py @@ -0,0 +1,25 @@ +# result.py + +class ResultAssembler: + @staticmethod + def assemble(cfd: 'Cfd') -> dict: + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied].copy() + + # ✅ 从 problem 获取解析解(唯一正确路径) + try: + analytical = cfd.problem.exact_solution(cfd) + except NotImplementedError: + analytical = None # 或 np.full_like(u_numerical, np.nan) + + return { + "x": cfd.domain.mesh.xcc, + "numerical": u_numerical, + "analytical": analytical, + "config": { + "scheme": cfd.config.recon_scheme, + "order": cfd.config.spatial_order, + "rk_order": cfd.config.rk_order, + "final_time": cfd.config.final_time, + "problem": cfd.problem.name, + } + } \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/solution.py b/example/1d-linear-convection/weno3/python-v1/02c/src/solution.py new file mode 100644 index 000000000..90666c34a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/solution.py @@ -0,0 +1,32 @@ +# solution.py +import numpy as np + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/solver.py b/example/1d-linear-convection/weno3/python-v1/02c/src/solver.py new file mode 100644 index 000000000..66b1ad403 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/solver.py @@ -0,0 +1,67 @@ +# solver.py + +from mesh import Mesh +from domain import Domain +from solution import Solution +from config import CfdConfig +from result import ResultAssembler +from problem import Problem +from initial_condition import InitialConditionFactory +from boundary.factory import BoundaryConditionFactory +from residual import ResidualCalculator +from time_integration import TimeIntegrator,TimeIntegratorFactory + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, problem: Problem, mesh): + self.problem = problem + self.config = problem.config + self.domain = Domain(problem.config, mesh) + self.solution = Solution(problem.config, self.domain) + + self.initial_condition = InitialConditionFactory.create(self.config) + self.boundary_condition = BoundaryConditionFactory.create(self) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + + # ==================== 公共接口(供 TimeIntegrator 调用) ==================== + def compute_residual(self): + """计算物理残差(封装重建→通量→散度)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件""" + self.boundary_condition.apply(self.solution.u) + + # ==================== 初始化 ==================== + def initialize(self): + """初始化全场:先 IC,再 BC,最后同步 old field""" + self.initial_condition.apply(self.solution) + # 应用边界条件到初始场 + self.apply_boundary() + # 同步 old field + self.solution.update_old_field() + + + def run(self): + self.initialize() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + self.integrator.step(dt) + t += dt + config.dt = dt_old + + self.result = ResultAssembler.assemble(self) + return self.result["numerical"] + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/time_integration/__init__.py b/example/1d-linear-convection/weno3/python-v1/02c/src/time_integration/__init__.py new file mode 100644 index 000000000..6342de3bb --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/time_integration/__init__.py @@ -0,0 +1,8 @@ +# time_integration/__init__.py + +# 导出统一接口 +from .factory import TimeIntegratorFactory +from .base import TimeIntegrator + +# 触发子模块注册(关键!) +from . import rk1, rk2, rk3 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/time_integration/base.py b/example/1d-linear-convection/weno3/python-v1/02c/src/time_integration/base.py new file mode 100644 index 000000000..0cdf29a48 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/time_integration/base.py @@ -0,0 +1,27 @@ +# time_integration/base.py + +from abc import ABC, abstractmethod + +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + + @abstractmethod + def step(self, dt): + pass + + def compute_residual(self): + """计算残差(委托给 Cfd)""" + self.cfd.compute_residual() + + def apply_boundary(self): + """应用边界条件(委托给 Cfd)""" + self.cfd.apply_boundary() + + def map_idx(self, i): + """物理网格索引 → 残差数组索引""" + return i - self.domain.ist \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/time_integration/factory.py b/example/1d-linear-convection/weno3/python-v1/02c/src/time_integration/factory.py new file mode 100644 index 000000000..94662db22 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/time_integration/factory.py @@ -0,0 +1,10 @@ +# time_integration/factory.py + +from core.registry import BaseFactory + +class TimeIntegratorFactory: + @staticmethod + def create(cfd) -> 'TimeIntegrator': + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/time_integration/rk1.py b/example/1d-linear-convection/weno3/python-v1/02c/src/time_integration/rk1.py new file mode 100644 index 000000000..b4c8a0211 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/time_integration/rk1.py @@ -0,0 +1,15 @@ +# time_integration/rk1.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/time_integration/rk2.py b/example/1d-linear-convection/weno3/python-v1/02c/src/time_integration/rk2.py new file mode 100644 index 000000000..6d2be3049 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/time_integration/rk2.py @@ -0,0 +1,29 @@ +# time_integration/rk2.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + 0.5 * self.solution.un[i] + + 0.5 * self.solution.u[i] + + 0.5 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02c/src/time_integration/rk3.py b/example/1d-linear-convection/weno3/python-v1/02c/src/time_integration/rk3.py new file mode 100644 index 000000000..c70791e13 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02c/src/time_integration/rk3.py @@ -0,0 +1,43 @@ +# time_integration/rk3.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # Stage 1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # Stage 2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = ( + 0.75 * self.solution.un[i] + + 0.25 * self.solution.u[i] + + 0.25 * dt * self.solution.res[j] + ) + self.solution.u[:] = u2 + self.apply_boundary() + + # Stage 3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + c1 * self.solution.un[i] + + c2 * self.solution.u[i] + + c3 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/example/run_eno_weno.py b/example/1d-linear-convection/weno3/python-v1/02d/example/run_eno_weno.py new file mode 100644 index 000000000..c4063cb75 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/example/run_eno_weno.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +# example/run_eno_weno.py + +import sys +import os +src_path = os.path.join(os.path.dirname(__file__), '..', 'src') +if src_path not in sys.path: + sys.path.insert(0, src_path) + +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +from problems.linear_advection import LinearAdvectionProblem + + +def performEnoWenoAnalysisBAK(): + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO3 求解 + print("Running ENO3 solver...") + config_eno3 = CfdConfig() + config_eno3.with_reconstruction("eno", 3) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno3 = LinearAdvectionProblem(config_eno3) + cfd_eno3 = Cfd(problem_eno3, mesh) # ← 注入 problem + cfd_eno3.run() + + # 3. 配置并运行 WENO3 求解 + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno3 = LinearAdvectionProblem(config_weno3) + cfd_weno3 = Cfd(problem_weno3, mesh) # ← 注入 problem + cfd_weno3.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" + ) + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO5 求解 + print("Running ENO5 solver...") + config_eno5 = CfdConfig() + config_eno5.with_reconstruction("eno", 5) + config_eno5.dt = 0.0025 + config_eno5.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno5 = LinearAdvectionProblem(config_eno5) + cfd_eno5 = Cfd(problem_eno5, mesh) # ← 注入 problem + cfd_eno5.run() + + # 3. 配置并运行 WENO5求解 + print("Running WENO5 solver...") + config_weno5 = CfdConfig() + config_weno5.with_reconstruction("weno", 5) + config_weno5.dt = 0.0025 + config_weno5.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno5 = LinearAdvectionProblem(config_weno5) + cfd_weno5 = Cfd(problem_weno5, mesh) # ← 注入 problem + cfd_weno5.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno5.result, + weno_result=cfd_weno5.result, + save_path="eno_weno_comparison.png" + ) + + +if __name__ == "__main__": + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/boundary/__init__.py b/example/1d-linear-convection/weno3/python-v1/02d/src/boundary/__init__.py new file mode 100644 index 000000000..20120e8a5 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/boundary/__init__.py @@ -0,0 +1,21 @@ +# src/boundary/__init__.py +""" +边界条件模块 +提供各种边界条件实现和工厂创建接口 +""" + +from .base import BoundaryCondition +from .factory import BoundaryConditionFactory + +# 导入具体类以触发注册 +from .periodic import PeriodicBoundary +from .dirichlet import DirichletBoundary +from .neumann import NeumannBoundary + +__all__ = [ + 'BoundaryCondition', + 'BoundaryConditionFactory', + 'PeriodicBoundary', + 'DirichletBoundary', + 'NeumannBoundary', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/boundary/base.py b/example/1d-linear-convection/weno3/python-v1/02d/src/boundary/base.py new file mode 100644 index 000000000..cddf649dd --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/boundary/base.py @@ -0,0 +1,18 @@ +# src/boundary/base.py +from abc import ABC, abstractmethod + +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/boundary/dirichlet.py b/example/1d-linear-convection/weno3/python-v1/02d/src/boundary/dirichlet.py new file mode 100644 index 000000000..1eff36994 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/boundary/dirichlet.py @@ -0,0 +1,27 @@ +# src/boundary/dirichlet.py +from .base import BoundaryCondition +from core.registry import register_component + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/boundary/factory.py b/example/1d-linear-convection/weno3/python-v1/02d/src/boundary/factory.py new file mode 100644 index 000000000..4bd7ae55a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/boundary/factory.py @@ -0,0 +1,24 @@ +# src/boundary/factory.py +from core.registry import BaseFactory + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) + + @staticmethod + def get_available_types(): + """获取所有可用的边界条件类型""" + return BaseFactory.get_available_components('boundary') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/boundary/neumann.py b/example/1d-linear-convection/weno3/python-v1/02d/src/boundary/neumann.py new file mode 100644 index 000000000..6eb72f644 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/boundary/neumann.py @@ -0,0 +1,23 @@ +# src/boundary/neumann.py +from .base import BoundaryCondition +from core.registry import register_component + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/boundary/periodic.py b/example/1d-linear-convection/weno3/python-v1/02d/src/boundary/periodic.py new file mode 100644 index 000000000..bd601d95d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/boundary/periodic.py @@ -0,0 +1,19 @@ +# src/boundary/periodic.py +from .base import BoundaryCondition +from core.registry import register_component + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/config.py b/example/1d-linear-convection/weno3/python-v1/02d/src/config.py new file mode 100644 index 000000000..6432c80e9 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/config.py @@ -0,0 +1,45 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + #self.ic_type = "sin" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # "rusanov", "engquist-osher" + #self.flux_type = "engquist-osher" # "rusanov", "engquist-osher" + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + print(f"scheme={scheme}") + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/core/registry.py b/example/1d-linear-convection/weno3/python-v1/02d/src/core/registry.py new file mode 100644 index 000000000..4a52afc26 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/core/registry.py @@ -0,0 +1,83 @@ +# core/registry.py +""" +统一注册系统 + 通用工厂 +- ComponentRegistry: 组件注册表 +- register_component: 装饰器 +- BaseFactory: 通用工厂类 +""" + +from typing import Dict, Type, Any + + +# ==================== 1. 注册表核心 ==================== +class ComponentRegistry: + """组件注册表""" + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True + + @classmethod + def set_verbose(cls, verbose: bool): + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + #print(f"ComponentRegistry register cls={cls}") + #print(f"ComponentRegistry register name={name}") + #print(f"ComponentRegistry register component_class={component_class}") + if category not in cls._registries: + cls._registries[category] = {} + if name in cls._registries[category]: + if cls._registries[category][name] != component_class and cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + if category not in cls._registries: + raise ValueError(f"❌ 未知类别: {category} (可用: {list(cls._registries.keys())})") + if name not in cls._registries[category]: + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {list(cls._registries[category].keys())})") + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) for cat, comps in cls._registries.items()} + + +# ==================== 2. 装饰器 ==================== +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + #print(f"register_component decorator name={name}") + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + +# ==================== 3. 通用工厂 ==================== +class BaseFactory: + """通用工厂基类""" + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + name_lower = name.lower() + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + available = ComponentRegistry.list_all().get(category, []) + if available: + error_msg = f"不支持的 {category} 类型 '{name}'。可用类型:{available}" + else: + error_msg = f"不支持的 {category} 类型 '{name}'(无已注册组件)" + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/domain.py b/example/1d-linear-convection/weno3/python-v1/02d/src/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/equations/base.py b/example/1d-linear-convection/weno3/python-v1/02d/src/equations/base.py new file mode 100644 index 000000000..92e54f126 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/equations/base.py @@ -0,0 +1,56 @@ +# equations/base.py + +from abc import ABC, abstractmethod +import numpy as np + +class Equation(ABC): + """ + 控制方程抽象基类 + 所有物理方程(线性对流、Euler、MHD)必须继承此类 + """ + + @property + @abstractmethod + def num_equations(self) -> int: + """返回方程组变量数(标量=1,Euler=3,MHD=8)""" + pass + + @abstractmethod + def flux(self, u): + """ + 计算通量函数 f(u) + :param u: 状态向量 (num_equations,) + :return: 通量向量 (num_equations,) + """ + pass + + @abstractmethod + def max_wave_speed(self, u): + """ + 计算最大波速(用于 CFL 条件) + :param u: 状态向量 + :return: 标量波速 + """ + pass + + @abstractmethod + def wave_speed(self): + """ + 计算波速(用于 CFL 条件) + :param u: 状态向量 + :return: 标量波速 + """ + pass + + def exact_solution(self, x, t, initial_condition_func): + """ + 可选:提供解析解 + 子类可重写;若无解析解,调用方应捕获 NotImplementedError + :param x: 空间坐标数组 (ncells,) + :param t: 时间 + :param initial_condition_func: 初值函数 u0(x) -> (num_equations, ncells) + :return: 解 u(x,t) -> (num_equations, ncells) + """ + raise NotImplementedError( + f"Equation '{self.__class__.__name__}' does not provide an exact solution." + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/equations/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/02d/src/equations/linear_advection.py new file mode 100644 index 000000000..26aa026be --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/equations/linear_advection.py @@ -0,0 +1,42 @@ +# equations/linear_advection.py + +import numpy as np +from .base import Equation + +class LinearAdvectionEquation(Equation): + """ + 线性对流方程: u_t + c * u_x = 0 + 支持标量(num_equations=1) + """ + + def __init__(self, wave_speed: float): + self.c = wave_speed + + @property + def num_equations(self) -> int: + return 1 + + def flux(self, u): + """f(u) = c * u""" + return self.c * u + + def max_wave_speed(self, u): + """最大波速 = |c|""" + return abs(self.c) + + def wave_speed(self): + return self.c + + + def exact_solution(self, x, t, initial_condition_func): + """ + 解析解: u(x, t) = u0(x - c * t) + 支持周期边界 + """ + if len(x) == 0: + return np.zeros((1, 0)) + + L = x[-1] - x[0] # 假设均匀周期网格 + x_shifted = (x - self.c * t + L) % L + u0 = initial_condition_func(x_shifted) # (1, ncells) + return u0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/initial_condition/__init__.py b/example/1d-linear-convection/weno3/python-v1/02d/src/initial_condition/__init__.py new file mode 100644 index 000000000..9eab71d5e --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/initial_condition/__init__.py @@ -0,0 +1,12 @@ +# src/initial_conditions/__init__.py +""" +初始条件模块 +提供各种初始条件实现和工厂创建接口 +""" + +from .base import InitialCondition +from .factory import InitialConditionFactory + +from .step import StepFunctionIC +from .sine import SineWaveIC +from .gaussian import GaussianPulseIC diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/initial_condition/base.py b/example/1d-linear-convection/weno3/python-v1/02d/src/initial_condition/base.py new file mode 100644 index 000000000..8a33a0d56 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/initial_condition/base.py @@ -0,0 +1,25 @@ +# src/initial_conditions/base.py +from abc import ABC, abstractmethod +import numpy as np + +class InitialCondition(ABC): + """初始条件抽象基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + """内部辅助方法:将值应用到物理区域""" + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/initial_condition/factory.py b/example/1d-linear-convection/weno3/python-v1/02d/src/initial_condition/factory.py new file mode 100644 index 000000000..2a7558b2c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/initial_condition/factory.py @@ -0,0 +1,24 @@ +# src/initial_conditions/factory.py +from core.registry import BaseFactory +from .base import InitialCondition + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + config: 配置对象,包含ic_type属性 + + Returns: + 初始条件实例 + """ + return BaseFactory.create_component('initial_condition', config.ic_type, config) + + @classmethod + def get_available_types(cls): + """获取所有可用的初始条件类型""" + return BaseFactory.get_available_components('initial_condition') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/initial_condition/gaussian.py b/example/1d-linear-convection/weno3/python-v1/02d/src/initial_condition/gaussian.py new file mode 100644 index 000000000..1dad463be --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/initial_condition/gaussian.py @@ -0,0 +1,16 @@ +# src/initial_conditions/gaussian.py +import numpy as np +from .base import InitialCondition +from core.registry import register_component + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/initial_condition/sine.py b/example/1d-linear-convection/weno3/python-v1/02d/src/initial_condition/sine.py new file mode 100644 index 000000000..c061fa5a9 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/initial_condition/sine.py @@ -0,0 +1,15 @@ +# src/initial_conditions/sine.py +import numpy as np +from .base import InitialCondition +from core.registry import register_component + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/initial_condition/step.py b/example/1d-linear-convection/weno3/python-v1/02d/src/initial_condition/step.py new file mode 100644 index 000000000..ff1120b2c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/initial_condition/step.py @@ -0,0 +1,17 @@ +# src/initial_conditions/step.py +import numpy as np +from .base import InitialCondition +from core.registry import register_component + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/mesh.py b/example/1d-linear-convection/weno3/python-v1/02d/src/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/__init__.py b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/__init__.py new file mode 100644 index 000000000..4cfdcc73b --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/__init__.py @@ -0,0 +1,27 @@ +# src/numerics/__init__.py +""" +数值方法模块 +包含重构器、通量计算、时间积分等数值方法 +""" + +from .residual import ResidualCalculator +from .reconstructor import Reconstructor, ReconstructorFactory +from .flux import InviscidFluxCalculator, FluxCalculatorFactory +from .time_integration import TimeIntegrator, TimeIntegratorFactory + +__all__ = [ + # 残差计算 + 'ResidualCalculator', + + # 重构器 + 'Reconstructor', + 'ReconstructorFactory', + + # 通量计算 + 'InviscidFluxCalculator', + 'FluxCalculatorFactory', + + # 时间积分 + 'TimeIntegrator', + 'TimeIntegratorFactory', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/flux/__init__.py b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/flux/__init__.py new file mode 100644 index 000000000..c39d62538 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/flux/__init__.py @@ -0,0 +1,19 @@ +# src/numerics/flux/__init__.py +""" +通量计算模块 +提供Rusanov、Engquist-Osher等通量计算方法 +""" + +from .base import InviscidFluxCalculator +from .factory import FluxCalculatorFactory + +# 导入具体实现以触发注册 +from .rusanov import RusanovFluxCalculator +from .engquist_osher import EngquistOsherFluxCalculator + +__all__ = [ + 'InviscidFluxCalculator', + 'FluxCalculatorFactory', + 'RusanovFluxCalculator', + 'EngquistOsherFluxCalculator', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/flux/base.py b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/flux/base.py new file mode 100644 index 000000000..7a5a134fc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/flux/base.py @@ -0,0 +1,25 @@ +# flux/base.py +""" +抽象通量计算基类 +""" + +from abc import ABC, abstractmethod + +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/flux/engquist_osher.py b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/flux/engquist_osher.py new file mode 100644 index 000000000..eb7b8cdf7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/flux/engquist_osher.py @@ -0,0 +1,20 @@ +# flux/engquist_osher.py +""" +Engquist-Osher 通量计算器(线性对流专用) +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + eq = self.cfd.problem.equation + for i in range(self.mesh.nnodes): + c = eq.wave_speed() + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/flux/factory.py b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/flux/factory.py new file mode 100644 index 000000000..c35f6e96f --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/flux/factory.py @@ -0,0 +1,26 @@ +# src/numerics/flux/factory.py +from core.registry import BaseFactory + +class FluxCalculatorFactory: + """通量计算器工厂:隐藏注册类别和组件名称等细节""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD 主对象,包含 config.flux_type 等配置 + + Returns: + 实现 InviscidFluxCalculator 接口的通量计算器实例 + """ + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) + + @staticmethod + def get_available_types(): + """获取所有可用的重构器类型""" + from core.registry import BaseFactory + return BaseFactory.get_available_components('flux') + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/flux/rusanov.py b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/flux/rusanov.py new file mode 100644 index 000000000..baa7c62c5 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/flux/rusanov.py @@ -0,0 +1,25 @@ +# flux/rusanov.py +""" +Rusanov(Lax-Friedrichs)通量计算器 +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量,使用 Equation 解耦物理参数""" + + def compute(self, q_face_left, q_face_right, flux): + # 从 cfd 获取 equation(与 Julia 对齐) + eq = self.cfd.problem.equation + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = eq.wave_speed() + c_R = eq.wave_speed() + # 通过 equation 计算通量和波速 + F_L = eq.flux(u_L) + F_R = eq.flux(u_R) + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/reconstructor/__init__.py new file mode 100644 index 000000000..6b0a3d9bb --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/reconstructor/__init__.py @@ -0,0 +1,21 @@ +# src/numerics/reconstructor/__init__.py +""" +数值重构模块 +提供ENO、WENO等界面值重构方法 +""" + +from .base import Reconstructor +from .factory import ReconstructorFactory + +# 导入具体实现以触发注册 +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from .weno5 import Weno5Reconstructor + +__all__ = [ + 'Reconstructor', + 'ReconstructorFactory', + 'EnoReconstructor', + 'Weno3Reconstructor', + 'Weno5Reconstructor', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/reconstructor/base.py b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/reconstructor/base.py new file mode 100644 index 000000000..de00327cc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def compute_face_values(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/reconstructor/eno.py b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/reconstructor/eno.py new file mode 100644 index 000000000..60699adf6 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/reconstructor/eno.py @@ -0,0 +1,96 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.initialize(self.config.spatial_order, self.domain.ntcells) + + def initialize(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def compute_face_values(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/reconstructor/factory.py b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/reconstructor/factory.py new file mode 100644 index 000000000..1f297eaaa --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/reconstructor/factory.py @@ -0,0 +1,20 @@ +# src/numerics/reconstructor/factory.py +from core.registry import BaseFactory + +class ReconstructorFactory: + @staticmethod + def create(cfd): + config = cfd.config + scheme = config.recon_scheme.lower() + + if scheme.startswith("weno"): + if scheme == "weno": + order = getattr(config, 'spatial_order', None) + scheme = f"weno{order}" + return BaseFactory.create_component('reconstructor', scheme, cfd) + + @staticmethod + def get_available_types(): + """获取所有可用的重构器类型""" + from core.registry import BaseFactory + return BaseFactory.get_available_components('reconstructor') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/reconstructor/weno3.py new file mode 100644 index 000000000..f45eb35f2 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/reconstructor/weno3.py @@ -0,0 +1,62 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def __init__(self, cfd): + self.cfd = cfd + + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/reconstructor/weno5.py b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/reconstructor/weno5.py new file mode 100644 index 000000000..9d6652752 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/reconstructor/weno5.py @@ -0,0 +1,71 @@ +# reconstructor/weno5.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno5') +class Weno5Reconstructor(Reconstructor): + def __init__(self, cfd): + self.cfd = cfd + + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3, v4, v5 = u[i-2], u[i-1], u[i], u[i+1], u[i+2] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3, v4, v5) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3, v4, v5 = u[i-2], u[i-1], u[i], u[i+1], u[i+2] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3, v4, v5) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3, v4, v5): + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)**2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)**2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)**2 + (1.0/4.0)*(v2 - v4)**2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)**2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)**2 + d0 = 1.0/10.0 + d1 = 3.0/5.0 + d2 = 3.0/10.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha2 = d2 / (eps + beta2)**2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = 1.0/3.0*v1-7.0/6.0*v2+11.0/6.0*v3 # r=2 + q1 = -1.0/6.0*v2+5.0/6.0*v3+1.0/3.0*v4 # r=1 + q2 = 1.0/3.0*v3+5.0/6.0*v4-1.0/6.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3, v4, v5): + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)**2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)**2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)**2 + (1.0/4.0)*(v2 - v4)**2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)**2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)**2 + d0 = 3.0/10.0 + d1 = 3.0/5.0 + d2 = 1.0/10.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha2 = d2 / (eps + beta2)**2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = -1.0/6.0*v1+5.0/6.0*v2+1.0/3.0*v3 # r=2 + q1 = 1.0/3.0*v2+5.0/6.0*v3-1.0/6.0*v4 # r=1 + q2 = 11.0/6.0*v3-7.0/6.0*v4+1.0/3.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/residual.py b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/residual.py new file mode 100644 index 000000000..2f96f05bb --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/residual.py @@ -0,0 +1,39 @@ +# src/numerics/residual.py +from numerics.flux.factory import FluxCalculatorFactory +from numerics.reconstructor.factory import ReconstructorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + + self.reconstructor = ReconstructorFactory.create(cfd) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._compute_face_values() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _compute_face_values(self): + """私有方法:界面值重建""" + self.reconstructor.compute_face_values(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/time_integration/__init__.py b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/time_integration/__init__.py new file mode 100644 index 000000000..9c127c775 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/time_integration/__init__.py @@ -0,0 +1,21 @@ +# src/numerics/time_integration/__init__.py +""" +时间积分模块 +提供显式Runge-Kutta方法 +""" + +from .base import TimeIntegrator +from .factory import TimeIntegratorFactory + +# 导入具体实现以触发注册 +from .rk1 import RK1Integrator +from .rk2 import RK2Integrator +from .rk3 import RK3Integrator + +__all__ = [ + 'TimeIntegrator', + 'TimeIntegratorFactory', + 'RK1Integrator', + 'RK2Integrator', + 'RK3Integrator', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/time_integration/base.py b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/time_integration/base.py new file mode 100644 index 000000000..0cdf29a48 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/time_integration/base.py @@ -0,0 +1,27 @@ +# time_integration/base.py + +from abc import ABC, abstractmethod + +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + + @abstractmethod + def step(self, dt): + pass + + def compute_residual(self): + """计算残差(委托给 Cfd)""" + self.cfd.compute_residual() + + def apply_boundary(self): + """应用边界条件(委托给 Cfd)""" + self.cfd.apply_boundary() + + def map_idx(self, i): + """物理网格索引 → 残差数组索引""" + return i - self.domain.ist \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/time_integration/factory.py b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/time_integration/factory.py new file mode 100644 index 000000000..5e6f9779e --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/time_integration/factory.py @@ -0,0 +1,15 @@ +# src/numerics/time_integration/factory.py +from core.registry import BaseFactory + +class TimeIntegratorFactory: + @staticmethod + def create(cfd) -> 'TimeIntegrator': + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) + + @staticmethod + def get_available_types(): + """获取所有可用的重构器类型""" + from core.registry import BaseFactory + return BaseFactory.get_available_components('integrator') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/time_integration/rk1.py b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/time_integration/rk1.py new file mode 100644 index 000000000..b4c8a0211 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/time_integration/rk1.py @@ -0,0 +1,15 @@ +# time_integration/rk1.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/time_integration/rk2.py b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/time_integration/rk2.py new file mode 100644 index 000000000..6d2be3049 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/time_integration/rk2.py @@ -0,0 +1,29 @@ +# time_integration/rk2.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + 0.5 * self.solution.un[i] + + 0.5 * self.solution.u[i] + + 0.5 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/time_integration/rk3.py b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/time_integration/rk3.py new file mode 100644 index 000000000..c70791e13 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/numerics/time_integration/rk3.py @@ -0,0 +1,43 @@ +# time_integration/rk3.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # Stage 1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # Stage 2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = ( + 0.75 * self.solution.un[i] + + 0.25 * self.solution.u[i] + + 0.25 * dt * self.solution.res[j] + ) + self.solution.u[:] = u2 + self.apply_boundary() + + # Stage 3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + c1 * self.solution.un[i] + + c2 * self.solution.u[i] + + c3 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/plotter.py b/example/1d-linear-convection/weno3/python-v1/02d/src/plotter.py new file mode 100644 index 000000000..e1f99ae25 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/plotter.py @@ -0,0 +1,109 @@ +# plotter.py + +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/problem.py b/example/1d-linear-convection/weno3/python-v1/02d/src/problem.py new file mode 100644 index 000000000..53719f265 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/problem.py @@ -0,0 +1,38 @@ +# problem.py +""" +问题定义模块:每个 Problem 子类代表一个完整测试用例 +包含初始条件、解析解(可选)、物理方程等 +""" + +from abc import ABC, abstractmethod +from initial_condition import InitialConditionFactory + + +class Problem(ABC): + """ + 抽象问题基类 + 每个具体问题(如线性对流、Sod 激波管)应继承此类 + """ + def __init__(self, config): + self.config = config + + @abstractmethod + def initial_condition(self): + """ + 返回 InitialCondition 实例 + """ + pass + + def exact_solution(self, cfd): + """ + 可选:返回解析解(数值数组) + 若无解析解,可抛出 NotImplementedError 或返回 None + """ + x = cfd.domain.mesh.xcc + raise NotImplementedError( + f"Problem '{self.__class__.__name__}' does not provide an exact solution." + ) + + @property + def name(self): + return self.__class__.__name__ \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/problems/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/02d/src/problems/linear_advection.py new file mode 100644 index 000000000..b6d548f51 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/problems/linear_advection.py @@ -0,0 +1,39 @@ +# problems/linear_advection.py + +import numpy as np +from problem import Problem +from equations.linear_advection import LinearAdvectionEquation +from initial_condition import InitialConditionFactory + +class LinearAdvectionProblem(Problem): + """ + 线性对流问题:u_t + c u_x = 0 + 使用周期边界 + 任意初始条件 + """ + + def __init__(self, config): + super().__init__(config) + # 持有物理方程实例 + self.equation = LinearAdvectionEquation(wave_speed=config.wave_speed) + self._ic_cache = None # 缓存 IC 实例 + + def initial_condition(self): + """返回初始条件实例""" + if self._ic_cache is None: + self._ic_cache = InitialConditionFactory.create(self.config) + return self._ic_cache + + def exact_solution(self, cfd): + """ + 委托给 Equation 计算解析解 + """ + ic = self.initial_condition() + # 包装 evaluate_at 以返回 (1, ncells) + def vectorized_ic(x): + u0_scalar = ic.evaluate_at(x) # (ncells,) + return u0_scalar[np.newaxis, :] # (1, ncells) + + x = cfd.domain.mesh.xcc + t = cfd.config.final_time + u_exact = self.equation.exact_solution(x, t, vectorized_ic) + return u_exact[0] # 返回标量数组 (ncells,) 以兼容现有绘图逻辑 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/result.py b/example/1d-linear-convection/weno3/python-v1/02d/src/result.py new file mode 100644 index 000000000..5e572216d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/result.py @@ -0,0 +1,25 @@ +# result.py + +class ResultAssembler: + @staticmethod + def assemble(cfd: 'Cfd') -> dict: + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied].copy() + + # ✅ 从 problem 获取解析解(唯一正确路径) + try: + analytical = cfd.problem.exact_solution(cfd) + except NotImplementedError: + analytical = None # 或 np.full_like(u_numerical, np.nan) + + return { + "x": cfd.domain.mesh.xcc, + "numerical": u_numerical, + "analytical": analytical, + "config": { + "scheme": cfd.config.recon_scheme, + "order": cfd.config.spatial_order, + "rk_order": cfd.config.rk_order, + "final_time": cfd.config.final_time, + "problem": cfd.problem.name, + } + } \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/solution.py b/example/1d-linear-convection/weno3/python-v1/02d/src/solution.py new file mode 100644 index 000000000..90666c34a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/solution.py @@ -0,0 +1,32 @@ +# solution.py +import numpy as np + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02d/src/solver.py b/example/1d-linear-convection/weno3/python-v1/02d/src/solver.py new file mode 100644 index 000000000..33e3fb75d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02d/src/solver.py @@ -0,0 +1,68 @@ +# solver.py + +from mesh import Mesh +from domain import Domain +from solution import Solution +from config import CfdConfig +from result import ResultAssembler +from problem import Problem +from initial_condition import InitialConditionFactory +from boundary.factory import BoundaryConditionFactory + +from numerics.time_integration.factory import TimeIntegratorFactory +from numerics.residual import ResidualCalculator + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, problem: Problem, mesh): + self.problem = problem + self.config = problem.config + self.domain = Domain(problem.config, mesh) + self.solution = Solution(problem.config, self.domain) + + self.initial_condition = InitialConditionFactory.create(self.config) + self.boundary_condition = BoundaryConditionFactory.create(self) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + + # ==================== 公共接口(供 TimeIntegrator 调用) ==================== + def compute_residual(self): + """计算物理残差(封装重建→通量→散度)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件""" + self.boundary_condition.apply(self.solution.u) + + # ==================== 初始化 ==================== + def initialize(self): + """初始化全场:先 IC,再 BC,最后同步 old field""" + self.initial_condition.apply(self.solution) + # 应用边界条件到初始场 + self.apply_boundary() + # 同步 old field + self.solution.update_old_field() + + + def run(self): + self.initialize() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + self.integrator.step(dt) + t += dt + config.dt = dt_old + + self.result = ResultAssembler.assemble(self) + return self.result["numerical"] + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/example/run_eno_weno.py b/example/1d-linear-convection/weno3/python-v1/02e/example/run_eno_weno.py new file mode 100644 index 000000000..599d76522 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/example/run_eno_weno.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +# example/run_eno_weno.py + +import sys +import os +print(f"当前工作目录: {os.getcwd()}") +print(f"Python路径:") +for path in sys.path: + print(f" {path}") + +src_path = os.path.join(os.path.dirname(__file__), '..', 'src') +print(f"\n尝试添加src路径: {src_path}") +print(f"src路径是否存在: {os.path.exists(src_path)}") +print(f"physics目录是否存在: {os.path.exists(os.path.join(src_path, 'physics'))}") +print(f"initial_conditions目录是否存在: {os.path.exists(os.path.join(src_path, 'physics', 'initial_conditions'))}") + +if src_path not in sys.path: + sys.path.insert(0, src_path) + +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter +from physics.problems.linear_advection import LinearAdvectionProblem + + +def performEnoWenoAnalysisBAK(): + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO3 求解 + print("Running ENO3 solver...") + config_eno3 = CfdConfig() + config_eno3.with_reconstruction("eno", 3) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno3 = LinearAdvectionProblem(config_eno3) + cfd_eno3 = Cfd(problem_eno3, mesh) # ← 注入 problem + cfd_eno3.run() + + # 3. 配置并运行 WENO3 求解 + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno3 = LinearAdvectionProblem(config_weno3) + cfd_weno3 = Cfd(problem_weno3, mesh) # ← 注入 problem + cfd_weno3.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" + ) + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO5 求解 + print("Running ENO5 solver...") + config_eno5 = CfdConfig() + config_eno5.with_reconstruction("eno", 5) + config_eno5.dt = 0.0025 + config_eno5.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno5 = LinearAdvectionProblem(config_eno5) + cfd_eno5 = Cfd(problem_eno5, mesh) # ← 注入 problem + cfd_eno5.run() + + # 3. 配置并运行 WENO5求解 + print("Running WENO5 solver...") + config_weno5 = CfdConfig() + config_weno5.with_reconstruction("weno", 5) + config_weno5.dt = 0.0025 + config_weno5.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno5 = LinearAdvectionProblem(config_weno5) + cfd_weno5 = Cfd(problem_weno5, mesh) # ← 注入 problem + cfd_weno5.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno5.result, + weno_result=cfd_weno5.result, + save_path="eno_weno_comparison.png" + ) + + +if __name__ == "__main__": + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/boundary/__init__.py b/example/1d-linear-convection/weno3/python-v1/02e/src/boundary/__init__.py new file mode 100644 index 000000000..20120e8a5 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/boundary/__init__.py @@ -0,0 +1,21 @@ +# src/boundary/__init__.py +""" +边界条件模块 +提供各种边界条件实现和工厂创建接口 +""" + +from .base import BoundaryCondition +from .factory import BoundaryConditionFactory + +# 导入具体类以触发注册 +from .periodic import PeriodicBoundary +from .dirichlet import DirichletBoundary +from .neumann import NeumannBoundary + +__all__ = [ + 'BoundaryCondition', + 'BoundaryConditionFactory', + 'PeriodicBoundary', + 'DirichletBoundary', + 'NeumannBoundary', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/boundary/base.py b/example/1d-linear-convection/weno3/python-v1/02e/src/boundary/base.py new file mode 100644 index 000000000..cddf649dd --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/boundary/base.py @@ -0,0 +1,18 @@ +# src/boundary/base.py +from abc import ABC, abstractmethod + +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/boundary/dirichlet.py b/example/1d-linear-convection/weno3/python-v1/02e/src/boundary/dirichlet.py new file mode 100644 index 000000000..1eff36994 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/boundary/dirichlet.py @@ -0,0 +1,27 @@ +# src/boundary/dirichlet.py +from .base import BoundaryCondition +from core.registry import register_component + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/boundary/factory.py b/example/1d-linear-convection/weno3/python-v1/02e/src/boundary/factory.py new file mode 100644 index 000000000..4bd7ae55a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/boundary/factory.py @@ -0,0 +1,24 @@ +# src/boundary/factory.py +from core.registry import BaseFactory + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) + + @staticmethod + def get_available_types(): + """获取所有可用的边界条件类型""" + return BaseFactory.get_available_components('boundary') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/boundary/neumann.py b/example/1d-linear-convection/weno3/python-v1/02e/src/boundary/neumann.py new file mode 100644 index 000000000..6eb72f644 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/boundary/neumann.py @@ -0,0 +1,23 @@ +# src/boundary/neumann.py +from .base import BoundaryCondition +from core.registry import register_component + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/boundary/periodic.py b/example/1d-linear-convection/weno3/python-v1/02e/src/boundary/periodic.py new file mode 100644 index 000000000..bd601d95d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/boundary/periodic.py @@ -0,0 +1,19 @@ +# src/boundary/periodic.py +from .base import BoundaryCondition +from core.registry import register_component + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/config.py b/example/1d-linear-convection/weno3/python-v1/02e/src/config.py new file mode 100644 index 000000000..6432c80e9 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/config.py @@ -0,0 +1,45 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + #self.ic_type = "sin" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # "rusanov", "engquist-osher" + #self.flux_type = "engquist-osher" # "rusanov", "engquist-osher" + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + print(f"scheme={scheme}") + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/core/registry.py b/example/1d-linear-convection/weno3/python-v1/02e/src/core/registry.py new file mode 100644 index 000000000..4a52afc26 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/core/registry.py @@ -0,0 +1,83 @@ +# core/registry.py +""" +统一注册系统 + 通用工厂 +- ComponentRegistry: 组件注册表 +- register_component: 装饰器 +- BaseFactory: 通用工厂类 +""" + +from typing import Dict, Type, Any + + +# ==================== 1. 注册表核心 ==================== +class ComponentRegistry: + """组件注册表""" + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True + + @classmethod + def set_verbose(cls, verbose: bool): + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + #print(f"ComponentRegistry register cls={cls}") + #print(f"ComponentRegistry register name={name}") + #print(f"ComponentRegistry register component_class={component_class}") + if category not in cls._registries: + cls._registries[category] = {} + if name in cls._registries[category]: + if cls._registries[category][name] != component_class and cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + if category not in cls._registries: + raise ValueError(f"❌ 未知类别: {category} (可用: {list(cls._registries.keys())})") + if name not in cls._registries[category]: + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {list(cls._registries[category].keys())})") + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) for cat, comps in cls._registries.items()} + + +# ==================== 2. 装饰器 ==================== +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + #print(f"register_component decorator name={name}") + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + +# ==================== 3. 通用工厂 ==================== +class BaseFactory: + """通用工厂基类""" + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + name_lower = name.lower() + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + available = ComponentRegistry.list_all().get(category, []) + if available: + error_msg = f"不支持的 {category} 类型 '{name}'。可用类型:{available}" + else: + error_msg = f"不支持的 {category} 类型 '{name}'(无已注册组件)" + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/domain.py b/example/1d-linear-convection/weno3/python-v1/02e/src/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/mesh.py b/example/1d-linear-convection/weno3/python-v1/02e/src/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/__init__.py b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/__init__.py new file mode 100644 index 000000000..4cfdcc73b --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/__init__.py @@ -0,0 +1,27 @@ +# src/numerics/__init__.py +""" +数值方法模块 +包含重构器、通量计算、时间积分等数值方法 +""" + +from .residual import ResidualCalculator +from .reconstructor import Reconstructor, ReconstructorFactory +from .flux import InviscidFluxCalculator, FluxCalculatorFactory +from .time_integration import TimeIntegrator, TimeIntegratorFactory + +__all__ = [ + # 残差计算 + 'ResidualCalculator', + + # 重构器 + 'Reconstructor', + 'ReconstructorFactory', + + # 通量计算 + 'InviscidFluxCalculator', + 'FluxCalculatorFactory', + + # 时间积分 + 'TimeIntegrator', + 'TimeIntegratorFactory', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/flux/__init__.py b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/flux/__init__.py new file mode 100644 index 000000000..c39d62538 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/flux/__init__.py @@ -0,0 +1,19 @@ +# src/numerics/flux/__init__.py +""" +通量计算模块 +提供Rusanov、Engquist-Osher等通量计算方法 +""" + +from .base import InviscidFluxCalculator +from .factory import FluxCalculatorFactory + +# 导入具体实现以触发注册 +from .rusanov import RusanovFluxCalculator +from .engquist_osher import EngquistOsherFluxCalculator + +__all__ = [ + 'InviscidFluxCalculator', + 'FluxCalculatorFactory', + 'RusanovFluxCalculator', + 'EngquistOsherFluxCalculator', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/flux/base.py b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/flux/base.py new file mode 100644 index 000000000..7a5a134fc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/flux/base.py @@ -0,0 +1,25 @@ +# flux/base.py +""" +抽象通量计算基类 +""" + +from abc import ABC, abstractmethod + +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/flux/engquist_osher.py b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/flux/engquist_osher.py new file mode 100644 index 000000000..16dbce821 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/flux/engquist_osher.py @@ -0,0 +1,20 @@ +# flux/engquist_osher.py +""" +Engquist-Osher 通量计算器(线性对流专用) +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + eq = self.cfd.problem.physical_system().equation + for i in range(self.mesh.nnodes): + c = eq.wave_speed() + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/flux/factory.py b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/flux/factory.py new file mode 100644 index 000000000..c35f6e96f --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/flux/factory.py @@ -0,0 +1,26 @@ +# src/numerics/flux/factory.py +from core.registry import BaseFactory + +class FluxCalculatorFactory: + """通量计算器工厂:隐藏注册类别和组件名称等细节""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD 主对象,包含 config.flux_type 等配置 + + Returns: + 实现 InviscidFluxCalculator 接口的通量计算器实例 + """ + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) + + @staticmethod + def get_available_types(): + """获取所有可用的重构器类型""" + from core.registry import BaseFactory + return BaseFactory.get_available_components('flux') + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/flux/rusanov.py b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/flux/rusanov.py new file mode 100644 index 000000000..56daa6995 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/flux/rusanov.py @@ -0,0 +1,25 @@ +# flux/rusanov.py +""" +Rusanov(Lax-Friedrichs)通量计算器 +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量,使用 Equation 解耦物理参数""" + + def compute(self, q_face_left, q_face_right, flux): + # 从 cfd 获取 equation(与 Julia 对齐) + eq = self.cfd.problem.physical_system().equation + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = eq.wave_speed() + c_R = eq.wave_speed() + # 通过 equation 计算通量和波速 + F_L = eq.flux(u_L) + F_R = eq.flux(u_R) + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/reconstructor/__init__.py new file mode 100644 index 000000000..6b0a3d9bb --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/reconstructor/__init__.py @@ -0,0 +1,21 @@ +# src/numerics/reconstructor/__init__.py +""" +数值重构模块 +提供ENO、WENO等界面值重构方法 +""" + +from .base import Reconstructor +from .factory import ReconstructorFactory + +# 导入具体实现以触发注册 +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from .weno5 import Weno5Reconstructor + +__all__ = [ + 'Reconstructor', + 'ReconstructorFactory', + 'EnoReconstructor', + 'Weno3Reconstructor', + 'Weno5Reconstructor', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/reconstructor/base.py b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/reconstructor/base.py new file mode 100644 index 000000000..de00327cc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def compute_face_values(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/reconstructor/eno.py b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/reconstructor/eno.py new file mode 100644 index 000000000..60699adf6 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/reconstructor/eno.py @@ -0,0 +1,96 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.initialize(self.config.spatial_order, self.domain.ntcells) + + def initialize(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def compute_face_values(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/reconstructor/factory.py b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/reconstructor/factory.py new file mode 100644 index 000000000..1f297eaaa --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/reconstructor/factory.py @@ -0,0 +1,20 @@ +# src/numerics/reconstructor/factory.py +from core.registry import BaseFactory + +class ReconstructorFactory: + @staticmethod + def create(cfd): + config = cfd.config + scheme = config.recon_scheme.lower() + + if scheme.startswith("weno"): + if scheme == "weno": + order = getattr(config, 'spatial_order', None) + scheme = f"weno{order}" + return BaseFactory.create_component('reconstructor', scheme, cfd) + + @staticmethod + def get_available_types(): + """获取所有可用的重构器类型""" + from core.registry import BaseFactory + return BaseFactory.get_available_components('reconstructor') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/reconstructor/weno3.py new file mode 100644 index 000000000..f45eb35f2 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/reconstructor/weno3.py @@ -0,0 +1,62 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def __init__(self, cfd): + self.cfd = cfd + + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/reconstructor/weno5.py b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/reconstructor/weno5.py new file mode 100644 index 000000000..9d6652752 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/reconstructor/weno5.py @@ -0,0 +1,71 @@ +# reconstructor/weno5.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno5') +class Weno5Reconstructor(Reconstructor): + def __init__(self, cfd): + self.cfd = cfd + + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3, v4, v5 = u[i-2], u[i-1], u[i], u[i+1], u[i+2] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3, v4, v5) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3, v4, v5 = u[i-2], u[i-1], u[i], u[i+1], u[i+2] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3, v4, v5) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3, v4, v5): + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)**2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)**2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)**2 + (1.0/4.0)*(v2 - v4)**2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)**2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)**2 + d0 = 1.0/10.0 + d1 = 3.0/5.0 + d2 = 3.0/10.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha2 = d2 / (eps + beta2)**2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = 1.0/3.0*v1-7.0/6.0*v2+11.0/6.0*v3 # r=2 + q1 = -1.0/6.0*v2+5.0/6.0*v3+1.0/3.0*v4 # r=1 + q2 = 1.0/3.0*v3+5.0/6.0*v4-1.0/6.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3, v4, v5): + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)**2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)**2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)**2 + (1.0/4.0)*(v2 - v4)**2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)**2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)**2 + d0 = 3.0/10.0 + d1 = 3.0/5.0 + d2 = 1.0/10.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha2 = d2 / (eps + beta2)**2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = -1.0/6.0*v1+5.0/6.0*v2+1.0/3.0*v3 # r=2 + q1 = 1.0/3.0*v2+5.0/6.0*v3-1.0/6.0*v4 # r=1 + q2 = 11.0/6.0*v3-7.0/6.0*v4+1.0/3.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/residual.py b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/residual.py new file mode 100644 index 000000000..2f96f05bb --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/residual.py @@ -0,0 +1,39 @@ +# src/numerics/residual.py +from numerics.flux.factory import FluxCalculatorFactory +from numerics.reconstructor.factory import ReconstructorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + + self.reconstructor = ReconstructorFactory.create(cfd) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._compute_face_values() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _compute_face_values(self): + """私有方法:界面值重建""" + self.reconstructor.compute_face_values(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/time_integration/__init__.py b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/time_integration/__init__.py new file mode 100644 index 000000000..9c127c775 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/time_integration/__init__.py @@ -0,0 +1,21 @@ +# src/numerics/time_integration/__init__.py +""" +时间积分模块 +提供显式Runge-Kutta方法 +""" + +from .base import TimeIntegrator +from .factory import TimeIntegratorFactory + +# 导入具体实现以触发注册 +from .rk1 import RK1Integrator +from .rk2 import RK2Integrator +from .rk3 import RK3Integrator + +__all__ = [ + 'TimeIntegrator', + 'TimeIntegratorFactory', + 'RK1Integrator', + 'RK2Integrator', + 'RK3Integrator', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/time_integration/base.py b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/time_integration/base.py new file mode 100644 index 000000000..0cdf29a48 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/time_integration/base.py @@ -0,0 +1,27 @@ +# time_integration/base.py + +from abc import ABC, abstractmethod + +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + + @abstractmethod + def step(self, dt): + pass + + def compute_residual(self): + """计算残差(委托给 Cfd)""" + self.cfd.compute_residual() + + def apply_boundary(self): + """应用边界条件(委托给 Cfd)""" + self.cfd.apply_boundary() + + def map_idx(self, i): + """物理网格索引 → 残差数组索引""" + return i - self.domain.ist \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/time_integration/factory.py b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/time_integration/factory.py new file mode 100644 index 000000000..5e6f9779e --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/time_integration/factory.py @@ -0,0 +1,15 @@ +# src/numerics/time_integration/factory.py +from core.registry import BaseFactory + +class TimeIntegratorFactory: + @staticmethod + def create(cfd) -> 'TimeIntegrator': + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) + + @staticmethod + def get_available_types(): + """获取所有可用的重构器类型""" + from core.registry import BaseFactory + return BaseFactory.get_available_components('integrator') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/time_integration/rk1.py b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/time_integration/rk1.py new file mode 100644 index 000000000..b4c8a0211 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/time_integration/rk1.py @@ -0,0 +1,15 @@ +# time_integration/rk1.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/time_integration/rk2.py b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/time_integration/rk2.py new file mode 100644 index 000000000..6d2be3049 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/time_integration/rk2.py @@ -0,0 +1,29 @@ +# time_integration/rk2.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + 0.5 * self.solution.un[i] + + 0.5 * self.solution.u[i] + + 0.5 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/time_integration/rk3.py b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/time_integration/rk3.py new file mode 100644 index 000000000..c70791e13 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/numerics/time_integration/rk3.py @@ -0,0 +1,43 @@ +# time_integration/rk3.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # Stage 1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # Stage 2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = ( + 0.75 * self.solution.un[i] + + 0.25 * self.solution.u[i] + + 0.25 * dt * self.solution.res[j] + ) + self.solution.u[:] = u2 + self.apply_boundary() + + # Stage 3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + c1 * self.solution.un[i] + + c2 * self.solution.u[i] + + c3 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/physics/__init__.py b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/physics/equations/__init__.py b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/equations/__init__.py new file mode 100644 index 000000000..14b821172 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/equations/__init__.py @@ -0,0 +1,15 @@ +# src/physics/equations/__init__.py +""" +物理方程模块 +提供各种控制方程的实现 +""" + +from .base import Equation +from .linear_advection import LinearAdvectionEquation +from .euler import EulerEquation + +__all__ = [ + 'Equation', + 'LinearAdvectionEquation', + 'EulerEquation', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/physics/equations/base.py b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/equations/base.py new file mode 100644 index 000000000..05833e74c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/equations/base.py @@ -0,0 +1,60 @@ +# src/physics/equations/base.py +""" +物理方程抽象基类 +所有控制方程必须继承此类 +""" + +from abc import ABC, abstractmethod +import numpy as np + +class Equation(ABC): + """ + 控制方程抽象基类 + 所有物理方程(线性对流、Euler、MHD)必须继承此类 + """ + + @property + @abstractmethod + def num_equations(self) -> int: + """返回方程组变量数(标量=1,Euler=3,MHD=8)""" + pass + + @abstractmethod + def flux(self, u): + """ + 计算通量函数 f(u) + :param u: 状态向量 (num_equations,) + :return: 通量向量 (num_equations,) + """ + pass + + @abstractmethod + def max_wave_speed(self, u): + """ + 计算最大波速(用于 CFL 条件) + :param u: 状态向量 + :return: 标量波速 + """ + pass + + @abstractmethod + def wave_speed(self): + """ + 计算波速(用于 CFL 条件) + :param u: 状态向量 + :return: 标量波速 + """ + pass + + def exact_solution(self, x, t, initial_condition_func): + """ + 可选:提供解析解 + 子类可重写;若无解析解,调用方应捕获 NotImplementedError + :param x: 空间坐标数组 (ncells,) + :param t: 时间 + :param initial_condition_func: 初值函数 u0(x) -> (num_equations, ncells) + :return: 解 u(x,t) -> (num_equations, ncells) + """ + raise NotImplementedError( + f"Equation '{self.__class__.__name__}' does not provide an exact solution." + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/physics/equations/euler.py b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/equations/euler.py new file mode 100644 index 000000000..0b69c2d72 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/equations/euler.py @@ -0,0 +1,23 @@ +# src/physics/equations/euler.py +""" +欧拉方程(占位文件,未来实现) +""" +from .base import Equation + +class EulerEquation(Equation): + """欧拉方程:质量、动量、能量守恒""" + def __init__(self, gamma=1.4): + self.gamma = gamma + + @property + def num_equations(self) -> int: + return 3 + + def flux(self, u): + raise NotImplementedError("欧拉方程待实现") + + def max_wave_speed(self, u): + raise NotImplementedError("欧拉方程待实现") + + def wave_speed(self): + raise NotImplementedError("欧拉方程待实现") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/physics/equations/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/equations/linear_advection.py new file mode 100644 index 000000000..949c4a0ad --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/equations/linear_advection.py @@ -0,0 +1,42 @@ +# src/physics/equations/linear_advection.py +""" +线性对流方程: u_t + c * u_x = 0 +支持标量(num_equations=1) +""" + +import numpy as np +from .base import Equation +from core.registry import register_component + +@register_component('equation', 'linear_advection') +class LinearAdvectionEquation(Equation): + def __init__(self, wave_speed: float): + self.c = wave_speed + + @property + def num_equations(self) -> int: + return 1 + + def flux(self, u): + """f(u) = c * u""" + return self.c * u + + def max_wave_speed(self, u): + """最大波速 = |c|""" + return abs(self.c) + + def wave_speed(self): + return self.c + + def exact_solution(self, x, t, initial_condition_func): + """ + 解析解: u(x, t) = u0(x - c * t) + 支持周期边界 + """ + if len(x) == 0: + return np.zeros((1, 0)) + + L = x[-1] - x[0] # 假设均匀周期网格 + x_shifted = (x - self.c * t + L) % L + u0 = initial_condition_func(x_shifted) # (1, ncells) + return u0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/physics/initial_conditions/__init__.py b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/initial_conditions/__init__.py new file mode 100644 index 000000000..94b0ad802 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/initial_conditions/__init__.py @@ -0,0 +1,16 @@ +# src/physics/initial_conditions/__init__.py +from .base import InitialCondition +from .factory import InitialConditionFactory + +# 导入具体类以触发注册 +from .step import StepFunctionIC +from .sine import SineWaveIC +from .gaussian import GaussianPulseIC + +__all__ = [ + 'InitialCondition', + 'InitialConditionFactory', + 'StepFunctionIC', + 'SineWaveIC', + 'GaussianPulseIC', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/physics/initial_conditions/base.py b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/initial_conditions/base.py new file mode 100644 index 000000000..8a33a0d56 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/initial_conditions/base.py @@ -0,0 +1,25 @@ +# src/initial_conditions/base.py +from abc import ABC, abstractmethod +import numpy as np + +class InitialCondition(ABC): + """初始条件抽象基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + """内部辅助方法:将值应用到物理区域""" + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/physics/initial_conditions/factory.py b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/initial_conditions/factory.py new file mode 100644 index 000000000..2a7558b2c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/initial_conditions/factory.py @@ -0,0 +1,24 @@ +# src/initial_conditions/factory.py +from core.registry import BaseFactory +from .base import InitialCondition + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + config: 配置对象,包含ic_type属性 + + Returns: + 初始条件实例 + """ + return BaseFactory.create_component('initial_condition', config.ic_type, config) + + @classmethod + def get_available_types(cls): + """获取所有可用的初始条件类型""" + return BaseFactory.get_available_components('initial_condition') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/physics/initial_conditions/gaussian.py b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/initial_conditions/gaussian.py new file mode 100644 index 000000000..1dad463be --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/initial_conditions/gaussian.py @@ -0,0 +1,16 @@ +# src/initial_conditions/gaussian.py +import numpy as np +from .base import InitialCondition +from core.registry import register_component + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/physics/initial_conditions/sine.py b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/initial_conditions/sine.py new file mode 100644 index 000000000..c061fa5a9 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/initial_conditions/sine.py @@ -0,0 +1,15 @@ +# src/initial_conditions/sine.py +import numpy as np +from .base import InitialCondition +from core.registry import register_component + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/physics/initial_conditions/step.py b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/initial_conditions/step.py new file mode 100644 index 000000000..ff1120b2c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/initial_conditions/step.py @@ -0,0 +1,17 @@ +# src/initial_conditions/step.py +import numpy as np +from .base import InitialCondition +from core.registry import register_component + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/physics/problems/__init__.py b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/problems/__init__.py new file mode 100644 index 000000000..e9d329058 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/problems/__init__.py @@ -0,0 +1,14 @@ +# src/physics/problems/__init__.py +""" +问题定义模块 +""" + +from .base import Problem +from .factory import ProblemFactory +from .linear_advection import LinearAdvectionProblem + +__all__ = [ + 'Problem', + 'ProblemFactory', + 'LinearAdvectionProblem', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/physics/problems/base.py b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/problems/base.py new file mode 100644 index 000000000..ddd8ddc4a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/problems/base.py @@ -0,0 +1,42 @@ +# src/physics/problems/base.py +""" +问题定义抽象基类 +每个具体问题(如线性对流、Sod 激波管)应继承此类 +""" + +from abc import ABC, abstractmethod + +class Problem(ABC): + """ + 抽象问题基类 + """ + def __init__(self, config): + self.config = config + self._initial_condition = None + self._physical_system = None + + @abstractmethod + def initial_condition(self): + """返回 InitialCondition 实例""" + pass + + @abstractmethod + def physical_system(self): + """返回 PhysicalSystem 实例""" + pass + + def exact_solution(self, cfd): + """ + 计算解析解(委托给物理系统) + """ + x = cfd.domain.mesh.xcc + t = cfd.config.final_time + ic = self.initial_condition() + system = self.physical_system() + + u_exact = system.exact_solution(x, t, ic) + return u_exact[0] # 返回标量数组 (ncells,) 以兼容现有绘图逻辑 + + @property + def name(self): + return self.__class__.__name__ \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/physics/problems/factory.py b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/problems/factory.py new file mode 100644 index 000000000..b3e3563fa --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/problems/factory.py @@ -0,0 +1,28 @@ +# src/physics/problems/factory.py +""" +问题工厂 +""" + +from core.registry import BaseFactory + +class ProblemFactory: + """问题工厂""" + + @staticmethod + def create(problem_type: str, config): + """ + 创建问题实例 + + Args: + problem_type: 问题类型,如 'linear_advection' + config: 配置对象 + + Returns: + 问题实例 + """ + return BaseFactory.create_component('problem', problem_type, config) + + @staticmethod + def get_available_types(): + """获取所有可用的问题类型""" + return BaseFactory.get_available_components('problem') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/physics/problems/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/problems/linear_advection.py new file mode 100644 index 000000000..f19ea53a0 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/problems/linear_advection.py @@ -0,0 +1,36 @@ +# src/physics/problems/linear_advection.py +""" +线性对流问题 +""" + +from .base import Problem +from physics.equations.linear_advection import LinearAdvectionEquation +from physics.systems.linear_advection_system import LinearAdvectionSystem +from physics.initial_conditions.factory import InitialConditionFactory +from core.registry import register_component + +@register_component('problem', 'linear_advection') +class LinearAdvectionProblem(Problem): + """ + 线性对流问题:u_t + c u_x = 0 + 使用周期边界 + 任意初始条件 + """ + + def __init__(self, config): + super().__init__(config) + + def initial_condition(self): + """返回初始条件实例""" + if self._initial_condition is None: + from physics.initial_conditions.factory import InitialConditionFactory + self._initial_condition = InitialConditionFactory.create(self.config) + return self._initial_condition + + def physical_system(self): + """返回物理系统实例""" + if self._physical_system is None: + # 创建方程 + equation = LinearAdvectionEquation(wave_speed=self.config.wave_speed) + # 创建系统 + self._physical_system = LinearAdvectionSystem(equation) + return self._physical_system \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/physics/systems/__init__.py b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/systems/__init__.py new file mode 100644 index 000000000..bd22409cc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/systems/__init__.py @@ -0,0 +1,15 @@ +# src/physics/systems/__init__.py +""" +物理系统模块 +组合方程和解析解逻辑 +""" + +from .base import PhysicalSystem +from .factory import PhysicalSystemFactory +from .linear_advection_system import LinearAdvectionSystem + +__all__ = [ + 'PhysicalSystem', + 'PhysicalSystemFactory', + 'LinearAdvectionSystem', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/physics/systems/base.py b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/systems/base.py new file mode 100644 index 000000000..957212d03 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/systems/base.py @@ -0,0 +1,33 @@ +# src/physics/systems/base.py +""" +物理系统基类 +将方程与解析解等逻辑组合 +""" + +from abc import ABC, abstractmethod + +class PhysicalSystem(ABC): + """物理系统抽象基类""" + + def __init__(self, equation): + self.equation = equation + + @abstractmethod + def exact_solution(self, x, t, initial_condition): + """ + 计算给定初始条件的解析解 + :param x: 空间坐标数组 + :param t: 时间 + :param initial_condition: 初始条件对象 + :return: 解析解数组 + """ + pass + + @property + def wave_speed(self): + """获取波速(委托给方程)""" + return self.equation.wave_speed() + + def flux(self, u): + """计算通量(委托给方程)""" + return self.equation.flux(u) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/physics/systems/factory.py b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/systems/factory.py new file mode 100644 index 000000000..6fcc754e9 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/systems/factory.py @@ -0,0 +1,28 @@ +# src/physics/systems/factory.py +""" +物理系统工厂 +""" + +from core.registry import BaseFactory + +class PhysicalSystemFactory: + """物理系统工厂""" + + @staticmethod + def create(system_type: str, **kwargs): + """ + 创建物理系统实例 + + Args: + system_type: 系统类型,如 'linear_advection' + **kwargs: 传递给方程的参数 + + Returns: + 物理系统实例 + """ + return BaseFactory.create_component('system', system_type, **kwargs) + + @staticmethod + def get_available_types(): + """获取所有可用的系统类型""" + return BaseFactory.get_available_components('system') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/physics/systems/linear_advection_system.py b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/systems/linear_advection_system.py new file mode 100644 index 000000000..0f3a49018 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/physics/systems/linear_advection_system.py @@ -0,0 +1,36 @@ +# src/physics/systems/linear_advection_system.py +""" +线性对流物理系统 +""" + +from .base import PhysicalSystem +from core.registry import register_component +import numpy as np + +@register_component('system', 'linear_advection') +class LinearAdvectionSystem(PhysicalSystem): + """线性对流系统""" + + def exact_solution(self, x, t, initial_condition): + """ + 计算线性对流的解析解 + :param x: 空间坐标数组 + :param t: 时间 + :param initial_condition: 初始条件对象 + :return: 解析解数组 + """ + if len(x) == 0: + return np.zeros((1, 0)) + + L = x[-1] - x[0] # 假设均匀周期网格 + c = self.equation.wave_speed() + x_shifted = (x - c * t + L) % L + + # 调用初始条件的 evaluate_at 方法 + u0 = initial_condition.evaluate_at(x_shifted) + + # 确保返回形状正确 + if u0.ndim == 1: + u0 = u0[np.newaxis, :] # (1, ncells) + + return u0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/plotter.py b/example/1d-linear-convection/weno3/python-v1/02e/src/plotter.py new file mode 100644 index 000000000..e1f99ae25 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/plotter.py @@ -0,0 +1,109 @@ +# plotter.py + +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/problem.py b/example/1d-linear-convection/weno3/python-v1/02e/src/problem.py new file mode 100644 index 000000000..53719f265 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/problem.py @@ -0,0 +1,38 @@ +# problem.py +""" +问题定义模块:每个 Problem 子类代表一个完整测试用例 +包含初始条件、解析解(可选)、物理方程等 +""" + +from abc import ABC, abstractmethod +from initial_condition import InitialConditionFactory + + +class Problem(ABC): + """ + 抽象问题基类 + 每个具体问题(如线性对流、Sod 激波管)应继承此类 + """ + def __init__(self, config): + self.config = config + + @abstractmethod + def initial_condition(self): + """ + 返回 InitialCondition 实例 + """ + pass + + def exact_solution(self, cfd): + """ + 可选:返回解析解(数值数组) + 若无解析解,可抛出 NotImplementedError 或返回 None + """ + x = cfd.domain.mesh.xcc + raise NotImplementedError( + f"Problem '{self.__class__.__name__}' does not provide an exact solution." + ) + + @property + def name(self): + return self.__class__.__name__ \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/result.py b/example/1d-linear-convection/weno3/python-v1/02e/src/result.py new file mode 100644 index 000000000..5e572216d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/result.py @@ -0,0 +1,25 @@ +# result.py + +class ResultAssembler: + @staticmethod + def assemble(cfd: 'Cfd') -> dict: + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied].copy() + + # ✅ 从 problem 获取解析解(唯一正确路径) + try: + analytical = cfd.problem.exact_solution(cfd) + except NotImplementedError: + analytical = None # 或 np.full_like(u_numerical, np.nan) + + return { + "x": cfd.domain.mesh.xcc, + "numerical": u_numerical, + "analytical": analytical, + "config": { + "scheme": cfd.config.recon_scheme, + "order": cfd.config.spatial_order, + "rk_order": cfd.config.rk_order, + "final_time": cfd.config.final_time, + "problem": cfd.problem.name, + } + } \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/solution.py b/example/1d-linear-convection/weno3/python-v1/02e/src/solution.py new file mode 100644 index 000000000..90666c34a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/solution.py @@ -0,0 +1,32 @@ +# solution.py +import numpy as np + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02e/src/solver.py b/example/1d-linear-convection/weno3/python-v1/02e/src/solver.py new file mode 100644 index 000000000..fe2222041 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02e/src/solver.py @@ -0,0 +1,68 @@ +# solver.py + +from mesh import Mesh +from domain import Domain +from solution import Solution +from config import CfdConfig +from result import ResultAssembler +from physics.problems.base import Problem +from physics.initial_conditions import InitialConditionFactory +from boundary.factory import BoundaryConditionFactory + +from numerics.time_integration.factory import TimeIntegratorFactory +from numerics.residual import ResidualCalculator + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, problem: Problem, mesh): + self.problem = problem + self.config = problem.config + self.domain = Domain(problem.config, mesh) + self.solution = Solution(problem.config, self.domain) + + self.initial_condition = InitialConditionFactory.create(self.config) + self.boundary_condition = BoundaryConditionFactory.create(self) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + + # ==================== 公共接口(供 TimeIntegrator 调用) ==================== + def compute_residual(self): + """计算物理残差(封装重建→通量→散度)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件""" + self.boundary_condition.apply(self.solution.u) + + # ==================== 初始化 ==================== + def initialize(self): + """初始化全场:先 IC,再 BC,最后同步 old field""" + self.initial_condition.apply(self.solution) + # 应用边界条件到初始场 + self.apply_boundary() + # 同步 old field + self.solution.update_old_field() + + + def run(self): + self.initialize() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + self.integrator.step(dt) + t += dt + config.dt = dt_old + + self.result = ResultAssembler.assemble(self) + return self.result["numerical"] + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/example/run_eno_weno.py b/example/1d-linear-convection/weno3/python-v1/02f/example/run_eno_weno.py new file mode 100644 index 000000000..8f8bb8de4 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/example/run_eno_weno.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +# example/run_eno_weno.py + +import sys +import os +print(f"当前工作目录: {os.getcwd()}") +print(f"Python路径:") +for path in sys.path: + print(f" {path}") + +src_path = os.path.join(os.path.dirname(__file__), '..', 'src') +print(f"\n尝试添加src路径: {src_path}") +print(f"src路径是否存在: {os.path.exists(src_path)}") +print(f"physics目录是否存在: {os.path.exists(os.path.join(src_path, 'physics'))}") +print(f"initial_conditions目录是否存在: {os.path.exists(os.path.join(src_path, 'physics', 'initial_conditions'))}") + +if src_path not in sys.path: + sys.path.insert(0, src_path) + +from solver import Cfd +from config import CfdConfig +from infrastructure.mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter +from physics.problems.linear_advection import LinearAdvectionProblem + + +def performEnoWenoAnalysisBAK(): + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO3 求解 + print("Running ENO3 solver...") + config_eno3 = CfdConfig() + config_eno3.with_reconstruction("eno", 3) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno3 = LinearAdvectionProblem(config_eno3) + cfd_eno3 = Cfd(problem_eno3, mesh) # ← 注入 problem + cfd_eno3.run() + + # 3. 配置并运行 WENO3 求解 + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno3 = LinearAdvectionProblem(config_weno3) + cfd_weno3 = Cfd(problem_weno3, mesh) # ← 注入 problem + cfd_weno3.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" + ) + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO5 求解 + print("Running ENO5 solver...") + config_eno5 = CfdConfig() + config_eno5.with_reconstruction("eno", 5) + config_eno5.dt = 0.0025 + config_eno5.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno5 = LinearAdvectionProblem(config_eno5) + cfd_eno5 = Cfd(problem_eno5, mesh) # ← 注入 problem + cfd_eno5.run() + + # 3. 配置并运行 WENO5求解 + print("Running WENO5 solver...") + config_weno5 = CfdConfig() + config_weno5.with_reconstruction("weno", 5) + config_weno5.dt = 0.0025 + config_weno5.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno5 = LinearAdvectionProblem(config_weno5) + cfd_weno5 = Cfd(problem_weno5, mesh) # ← 注入 problem + cfd_weno5.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno5.result, + weno_result=cfd_weno5.result, + save_path="eno_weno_comparison.png" + ) + + +if __name__ == "__main__": + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/config.py b/example/1d-linear-convection/weno3/python-v1/02f/src/config.py new file mode 100644 index 000000000..6432c80e9 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/config.py @@ -0,0 +1,45 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + #self.ic_type = "sin" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # "rusanov", "engquist-osher" + #self.flux_type = "engquist-osher" # "rusanov", "engquist-osher" + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + print(f"scheme={scheme}") + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/core/registry.py b/example/1d-linear-convection/weno3/python-v1/02f/src/core/registry.py new file mode 100644 index 000000000..4a52afc26 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/core/registry.py @@ -0,0 +1,83 @@ +# core/registry.py +""" +统一注册系统 + 通用工厂 +- ComponentRegistry: 组件注册表 +- register_component: 装饰器 +- BaseFactory: 通用工厂类 +""" + +from typing import Dict, Type, Any + + +# ==================== 1. 注册表核心 ==================== +class ComponentRegistry: + """组件注册表""" + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True + + @classmethod + def set_verbose(cls, verbose: bool): + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + #print(f"ComponentRegistry register cls={cls}") + #print(f"ComponentRegistry register name={name}") + #print(f"ComponentRegistry register component_class={component_class}") + if category not in cls._registries: + cls._registries[category] = {} + if name in cls._registries[category]: + if cls._registries[category][name] != component_class and cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + if category not in cls._registries: + raise ValueError(f"❌ 未知类别: {category} (可用: {list(cls._registries.keys())})") + if name not in cls._registries[category]: + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {list(cls._registries[category].keys())})") + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) for cat, comps in cls._registries.items()} + + +# ==================== 2. 装饰器 ==================== +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + #print(f"register_component decorator name={name}") + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + +# ==================== 3. 通用工厂 ==================== +class BaseFactory: + """通用工厂基类""" + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + name_lower = name.lower() + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + available = ComponentRegistry.list_all().get(category, []) + if available: + error_msg = f"不支持的 {category} 类型 '{name}'。可用类型:{available}" + else: + error_msg = f"不支持的 {category} 类型 '{name}'(无已注册组件)" + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/__init__.py b/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/__init__.py new file mode 100644 index 000000000..e476afcb7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/__init__.py @@ -0,0 +1,18 @@ +# src/infrastructure/__init__.py +""" +基础设施模块 +包含网格、域、解、边界条件等基础组件 +""" + +from .mesh import Mesh +from .domain import Domain +from .solution import Solution +from .boundary import BoundaryCondition, BoundaryConditionFactory + +__all__ = [ + 'Mesh', + 'Domain', + 'Solution', + 'BoundaryCondition', + 'BoundaryConditionFactory', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/boundary/__init__.py b/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/boundary/__init__.py new file mode 100644 index 000000000..20120e8a5 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/boundary/__init__.py @@ -0,0 +1,21 @@ +# src/boundary/__init__.py +""" +边界条件模块 +提供各种边界条件实现和工厂创建接口 +""" + +from .base import BoundaryCondition +from .factory import BoundaryConditionFactory + +# 导入具体类以触发注册 +from .periodic import PeriodicBoundary +from .dirichlet import DirichletBoundary +from .neumann import NeumannBoundary + +__all__ = [ + 'BoundaryCondition', + 'BoundaryConditionFactory', + 'PeriodicBoundary', + 'DirichletBoundary', + 'NeumannBoundary', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/boundary/base.py b/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/boundary/base.py new file mode 100644 index 000000000..cddf649dd --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/boundary/base.py @@ -0,0 +1,18 @@ +# src/boundary/base.py +from abc import ABC, abstractmethod + +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/boundary/dirichlet.py b/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/boundary/dirichlet.py new file mode 100644 index 000000000..1eff36994 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/boundary/dirichlet.py @@ -0,0 +1,27 @@ +# src/boundary/dirichlet.py +from .base import BoundaryCondition +from core.registry import register_component + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/boundary/factory.py b/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/boundary/factory.py new file mode 100644 index 000000000..4bd7ae55a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/boundary/factory.py @@ -0,0 +1,24 @@ +# src/boundary/factory.py +from core.registry import BaseFactory + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) + + @staticmethod + def get_available_types(): + """获取所有可用的边界条件类型""" + return BaseFactory.get_available_components('boundary') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/boundary/neumann.py b/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/boundary/neumann.py new file mode 100644 index 000000000..6eb72f644 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/boundary/neumann.py @@ -0,0 +1,23 @@ +# src/boundary/neumann.py +from .base import BoundaryCondition +from core.registry import register_component + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/boundary/periodic.py b/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/boundary/periodic.py new file mode 100644 index 000000000..bd601d95d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/boundary/periodic.py @@ -0,0 +1,19 @@ +# src/boundary/periodic.py +from .base import BoundaryCondition +from core.registry import register_component + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/domain.py b/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/domain.py new file mode 100644 index 000000000..05402f4c6 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/domain.py @@ -0,0 +1,56 @@ +# src/infrastructure/domain.py +from .mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/mesh.py b/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/solution.py b/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/solution.py new file mode 100644 index 000000000..c3a00dd54 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/infrastructure/solution.py @@ -0,0 +1,32 @@ +# src/infrastructure/solution.py +import numpy as np + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/__init__.py b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/__init__.py new file mode 100644 index 000000000..4cfdcc73b --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/__init__.py @@ -0,0 +1,27 @@ +# src/numerics/__init__.py +""" +数值方法模块 +包含重构器、通量计算、时间积分等数值方法 +""" + +from .residual import ResidualCalculator +from .reconstructor import Reconstructor, ReconstructorFactory +from .flux import InviscidFluxCalculator, FluxCalculatorFactory +from .time_integration import TimeIntegrator, TimeIntegratorFactory + +__all__ = [ + # 残差计算 + 'ResidualCalculator', + + # 重构器 + 'Reconstructor', + 'ReconstructorFactory', + + # 通量计算 + 'InviscidFluxCalculator', + 'FluxCalculatorFactory', + + # 时间积分 + 'TimeIntegrator', + 'TimeIntegratorFactory', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/flux/__init__.py b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/flux/__init__.py new file mode 100644 index 000000000..c39d62538 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/flux/__init__.py @@ -0,0 +1,19 @@ +# src/numerics/flux/__init__.py +""" +通量计算模块 +提供Rusanov、Engquist-Osher等通量计算方法 +""" + +from .base import InviscidFluxCalculator +from .factory import FluxCalculatorFactory + +# 导入具体实现以触发注册 +from .rusanov import RusanovFluxCalculator +from .engquist_osher import EngquistOsherFluxCalculator + +__all__ = [ + 'InviscidFluxCalculator', + 'FluxCalculatorFactory', + 'RusanovFluxCalculator', + 'EngquistOsherFluxCalculator', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/flux/base.py b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/flux/base.py new file mode 100644 index 000000000..7a5a134fc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/flux/base.py @@ -0,0 +1,25 @@ +# flux/base.py +""" +抽象通量计算基类 +""" + +from abc import ABC, abstractmethod + +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/flux/engquist_osher.py b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/flux/engquist_osher.py new file mode 100644 index 000000000..16dbce821 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/flux/engquist_osher.py @@ -0,0 +1,20 @@ +# flux/engquist_osher.py +""" +Engquist-Osher 通量计算器(线性对流专用) +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + eq = self.cfd.problem.physical_system().equation + for i in range(self.mesh.nnodes): + c = eq.wave_speed() + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/flux/factory.py b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/flux/factory.py new file mode 100644 index 000000000..c35f6e96f --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/flux/factory.py @@ -0,0 +1,26 @@ +# src/numerics/flux/factory.py +from core.registry import BaseFactory + +class FluxCalculatorFactory: + """通量计算器工厂:隐藏注册类别和组件名称等细节""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD 主对象,包含 config.flux_type 等配置 + + Returns: + 实现 InviscidFluxCalculator 接口的通量计算器实例 + """ + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) + + @staticmethod + def get_available_types(): + """获取所有可用的重构器类型""" + from core.registry import BaseFactory + return BaseFactory.get_available_components('flux') + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/flux/rusanov.py b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/flux/rusanov.py new file mode 100644 index 000000000..56daa6995 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/flux/rusanov.py @@ -0,0 +1,25 @@ +# flux/rusanov.py +""" +Rusanov(Lax-Friedrichs)通量计算器 +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量,使用 Equation 解耦物理参数""" + + def compute(self, q_face_left, q_face_right, flux): + # 从 cfd 获取 equation(与 Julia 对齐) + eq = self.cfd.problem.physical_system().equation + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = eq.wave_speed() + c_R = eq.wave_speed() + # 通过 equation 计算通量和波速 + F_L = eq.flux(u_L) + F_R = eq.flux(u_R) + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/reconstructor/__init__.py new file mode 100644 index 000000000..6b0a3d9bb --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/reconstructor/__init__.py @@ -0,0 +1,21 @@ +# src/numerics/reconstructor/__init__.py +""" +数值重构模块 +提供ENO、WENO等界面值重构方法 +""" + +from .base import Reconstructor +from .factory import ReconstructorFactory + +# 导入具体实现以触发注册 +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from .weno5 import Weno5Reconstructor + +__all__ = [ + 'Reconstructor', + 'ReconstructorFactory', + 'EnoReconstructor', + 'Weno3Reconstructor', + 'Weno5Reconstructor', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/reconstructor/base.py b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/reconstructor/base.py new file mode 100644 index 000000000..de00327cc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def compute_face_values(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/reconstructor/eno.py b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/reconstructor/eno.py new file mode 100644 index 000000000..60699adf6 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/reconstructor/eno.py @@ -0,0 +1,96 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.initialize(self.config.spatial_order, self.domain.ntcells) + + def initialize(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def compute_face_values(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/reconstructor/factory.py b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/reconstructor/factory.py new file mode 100644 index 000000000..1f297eaaa --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/reconstructor/factory.py @@ -0,0 +1,20 @@ +# src/numerics/reconstructor/factory.py +from core.registry import BaseFactory + +class ReconstructorFactory: + @staticmethod + def create(cfd): + config = cfd.config + scheme = config.recon_scheme.lower() + + if scheme.startswith("weno"): + if scheme == "weno": + order = getattr(config, 'spatial_order', None) + scheme = f"weno{order}" + return BaseFactory.create_component('reconstructor', scheme, cfd) + + @staticmethod + def get_available_types(): + """获取所有可用的重构器类型""" + from core.registry import BaseFactory + return BaseFactory.get_available_components('reconstructor') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/reconstructor/weno3.py new file mode 100644 index 000000000..f45eb35f2 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/reconstructor/weno3.py @@ -0,0 +1,62 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def __init__(self, cfd): + self.cfd = cfd + + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/reconstructor/weno5.py b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/reconstructor/weno5.py new file mode 100644 index 000000000..9d6652752 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/reconstructor/weno5.py @@ -0,0 +1,71 @@ +# reconstructor/weno5.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno5') +class Weno5Reconstructor(Reconstructor): + def __init__(self, cfd): + self.cfd = cfd + + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3, v4, v5 = u[i-2], u[i-1], u[i], u[i+1], u[i+2] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3, v4, v5) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3, v4, v5 = u[i-2], u[i-1], u[i], u[i+1], u[i+2] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3, v4, v5) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3, v4, v5): + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)**2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)**2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)**2 + (1.0/4.0)*(v2 - v4)**2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)**2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)**2 + d0 = 1.0/10.0 + d1 = 3.0/5.0 + d2 = 3.0/10.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha2 = d2 / (eps + beta2)**2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = 1.0/3.0*v1-7.0/6.0*v2+11.0/6.0*v3 # r=2 + q1 = -1.0/6.0*v2+5.0/6.0*v3+1.0/3.0*v4 # r=1 + q2 = 1.0/3.0*v3+5.0/6.0*v4-1.0/6.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3, v4, v5): + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)**2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)**2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)**2 + (1.0/4.0)*(v2 - v4)**2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)**2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)**2 + d0 = 3.0/10.0 + d1 = 3.0/5.0 + d2 = 1.0/10.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha2 = d2 / (eps + beta2)**2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = -1.0/6.0*v1+5.0/6.0*v2+1.0/3.0*v3 # r=2 + q1 = 1.0/3.0*v2+5.0/6.0*v3-1.0/6.0*v4 # r=1 + q2 = 11.0/6.0*v3-7.0/6.0*v4+1.0/3.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/residual.py b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/residual.py new file mode 100644 index 000000000..2f96f05bb --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/residual.py @@ -0,0 +1,39 @@ +# src/numerics/residual.py +from numerics.flux.factory import FluxCalculatorFactory +from numerics.reconstructor.factory import ReconstructorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + + self.reconstructor = ReconstructorFactory.create(cfd) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._compute_face_values() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _compute_face_values(self): + """私有方法:界面值重建""" + self.reconstructor.compute_face_values(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/time_integration/__init__.py b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/time_integration/__init__.py new file mode 100644 index 000000000..9c127c775 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/time_integration/__init__.py @@ -0,0 +1,21 @@ +# src/numerics/time_integration/__init__.py +""" +时间积分模块 +提供显式Runge-Kutta方法 +""" + +from .base import TimeIntegrator +from .factory import TimeIntegratorFactory + +# 导入具体实现以触发注册 +from .rk1 import RK1Integrator +from .rk2 import RK2Integrator +from .rk3 import RK3Integrator + +__all__ = [ + 'TimeIntegrator', + 'TimeIntegratorFactory', + 'RK1Integrator', + 'RK2Integrator', + 'RK3Integrator', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/time_integration/base.py b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/time_integration/base.py new file mode 100644 index 000000000..0cdf29a48 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/time_integration/base.py @@ -0,0 +1,27 @@ +# time_integration/base.py + +from abc import ABC, abstractmethod + +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + + @abstractmethod + def step(self, dt): + pass + + def compute_residual(self): + """计算残差(委托给 Cfd)""" + self.cfd.compute_residual() + + def apply_boundary(self): + """应用边界条件(委托给 Cfd)""" + self.cfd.apply_boundary() + + def map_idx(self, i): + """物理网格索引 → 残差数组索引""" + return i - self.domain.ist \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/time_integration/factory.py b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/time_integration/factory.py new file mode 100644 index 000000000..5e6f9779e --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/time_integration/factory.py @@ -0,0 +1,15 @@ +# src/numerics/time_integration/factory.py +from core.registry import BaseFactory + +class TimeIntegratorFactory: + @staticmethod + def create(cfd) -> 'TimeIntegrator': + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) + + @staticmethod + def get_available_types(): + """获取所有可用的重构器类型""" + from core.registry import BaseFactory + return BaseFactory.get_available_components('integrator') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/time_integration/rk1.py b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/time_integration/rk1.py new file mode 100644 index 000000000..b4c8a0211 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/time_integration/rk1.py @@ -0,0 +1,15 @@ +# time_integration/rk1.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/time_integration/rk2.py b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/time_integration/rk2.py new file mode 100644 index 000000000..6d2be3049 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/time_integration/rk2.py @@ -0,0 +1,29 @@ +# time_integration/rk2.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + 0.5 * self.solution.un[i] + + 0.5 * self.solution.u[i] + + 0.5 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/time_integration/rk3.py b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/time_integration/rk3.py new file mode 100644 index 000000000..c70791e13 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/numerics/time_integration/rk3.py @@ -0,0 +1,43 @@ +# time_integration/rk3.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # Stage 1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # Stage 2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = ( + 0.75 * self.solution.un[i] + + 0.25 * self.solution.u[i] + + 0.25 * dt * self.solution.res[j] + ) + self.solution.u[:] = u2 + self.apply_boundary() + + # Stage 3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + c1 * self.solution.un[i] + + c2 * self.solution.u[i] + + c3 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/physics/__init__.py b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/physics/equations/__init__.py b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/equations/__init__.py new file mode 100644 index 000000000..14b821172 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/equations/__init__.py @@ -0,0 +1,15 @@ +# src/physics/equations/__init__.py +""" +物理方程模块 +提供各种控制方程的实现 +""" + +from .base import Equation +from .linear_advection import LinearAdvectionEquation +from .euler import EulerEquation + +__all__ = [ + 'Equation', + 'LinearAdvectionEquation', + 'EulerEquation', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/physics/equations/base.py b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/equations/base.py new file mode 100644 index 000000000..05833e74c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/equations/base.py @@ -0,0 +1,60 @@ +# src/physics/equations/base.py +""" +物理方程抽象基类 +所有控制方程必须继承此类 +""" + +from abc import ABC, abstractmethod +import numpy as np + +class Equation(ABC): + """ + 控制方程抽象基类 + 所有物理方程(线性对流、Euler、MHD)必须继承此类 + """ + + @property + @abstractmethod + def num_equations(self) -> int: + """返回方程组变量数(标量=1,Euler=3,MHD=8)""" + pass + + @abstractmethod + def flux(self, u): + """ + 计算通量函数 f(u) + :param u: 状态向量 (num_equations,) + :return: 通量向量 (num_equations,) + """ + pass + + @abstractmethod + def max_wave_speed(self, u): + """ + 计算最大波速(用于 CFL 条件) + :param u: 状态向量 + :return: 标量波速 + """ + pass + + @abstractmethod + def wave_speed(self): + """ + 计算波速(用于 CFL 条件) + :param u: 状态向量 + :return: 标量波速 + """ + pass + + def exact_solution(self, x, t, initial_condition_func): + """ + 可选:提供解析解 + 子类可重写;若无解析解,调用方应捕获 NotImplementedError + :param x: 空间坐标数组 (ncells,) + :param t: 时间 + :param initial_condition_func: 初值函数 u0(x) -> (num_equations, ncells) + :return: 解 u(x,t) -> (num_equations, ncells) + """ + raise NotImplementedError( + f"Equation '{self.__class__.__name__}' does not provide an exact solution." + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/physics/equations/euler.py b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/equations/euler.py new file mode 100644 index 000000000..0b69c2d72 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/equations/euler.py @@ -0,0 +1,23 @@ +# src/physics/equations/euler.py +""" +欧拉方程(占位文件,未来实现) +""" +from .base import Equation + +class EulerEquation(Equation): + """欧拉方程:质量、动量、能量守恒""" + def __init__(self, gamma=1.4): + self.gamma = gamma + + @property + def num_equations(self) -> int: + return 3 + + def flux(self, u): + raise NotImplementedError("欧拉方程待实现") + + def max_wave_speed(self, u): + raise NotImplementedError("欧拉方程待实现") + + def wave_speed(self): + raise NotImplementedError("欧拉方程待实现") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/physics/equations/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/equations/linear_advection.py new file mode 100644 index 000000000..949c4a0ad --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/equations/linear_advection.py @@ -0,0 +1,42 @@ +# src/physics/equations/linear_advection.py +""" +线性对流方程: u_t + c * u_x = 0 +支持标量(num_equations=1) +""" + +import numpy as np +from .base import Equation +from core.registry import register_component + +@register_component('equation', 'linear_advection') +class LinearAdvectionEquation(Equation): + def __init__(self, wave_speed: float): + self.c = wave_speed + + @property + def num_equations(self) -> int: + return 1 + + def flux(self, u): + """f(u) = c * u""" + return self.c * u + + def max_wave_speed(self, u): + """最大波速 = |c|""" + return abs(self.c) + + def wave_speed(self): + return self.c + + def exact_solution(self, x, t, initial_condition_func): + """ + 解析解: u(x, t) = u0(x - c * t) + 支持周期边界 + """ + if len(x) == 0: + return np.zeros((1, 0)) + + L = x[-1] - x[0] # 假设均匀周期网格 + x_shifted = (x - self.c * t + L) % L + u0 = initial_condition_func(x_shifted) # (1, ncells) + return u0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/physics/initial_conditions/__init__.py b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/initial_conditions/__init__.py new file mode 100644 index 000000000..94b0ad802 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/initial_conditions/__init__.py @@ -0,0 +1,16 @@ +# src/physics/initial_conditions/__init__.py +from .base import InitialCondition +from .factory import InitialConditionFactory + +# 导入具体类以触发注册 +from .step import StepFunctionIC +from .sine import SineWaveIC +from .gaussian import GaussianPulseIC + +__all__ = [ + 'InitialCondition', + 'InitialConditionFactory', + 'StepFunctionIC', + 'SineWaveIC', + 'GaussianPulseIC', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/physics/initial_conditions/base.py b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/initial_conditions/base.py new file mode 100644 index 000000000..8a33a0d56 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/initial_conditions/base.py @@ -0,0 +1,25 @@ +# src/initial_conditions/base.py +from abc import ABC, abstractmethod +import numpy as np + +class InitialCondition(ABC): + """初始条件抽象基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + """内部辅助方法:将值应用到物理区域""" + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/physics/initial_conditions/factory.py b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/initial_conditions/factory.py new file mode 100644 index 000000000..2a7558b2c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/initial_conditions/factory.py @@ -0,0 +1,24 @@ +# src/initial_conditions/factory.py +from core.registry import BaseFactory +from .base import InitialCondition + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + config: 配置对象,包含ic_type属性 + + Returns: + 初始条件实例 + """ + return BaseFactory.create_component('initial_condition', config.ic_type, config) + + @classmethod + def get_available_types(cls): + """获取所有可用的初始条件类型""" + return BaseFactory.get_available_components('initial_condition') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/physics/initial_conditions/gaussian.py b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/initial_conditions/gaussian.py new file mode 100644 index 000000000..1dad463be --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/initial_conditions/gaussian.py @@ -0,0 +1,16 @@ +# src/initial_conditions/gaussian.py +import numpy as np +from .base import InitialCondition +from core.registry import register_component + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/physics/initial_conditions/sine.py b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/initial_conditions/sine.py new file mode 100644 index 000000000..c061fa5a9 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/initial_conditions/sine.py @@ -0,0 +1,15 @@ +# src/initial_conditions/sine.py +import numpy as np +from .base import InitialCondition +from core.registry import register_component + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/physics/initial_conditions/step.py b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/initial_conditions/step.py new file mode 100644 index 000000000..ff1120b2c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/initial_conditions/step.py @@ -0,0 +1,17 @@ +# src/initial_conditions/step.py +import numpy as np +from .base import InitialCondition +from core.registry import register_component + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/physics/problems/__init__.py b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/problems/__init__.py new file mode 100644 index 000000000..e9d329058 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/problems/__init__.py @@ -0,0 +1,14 @@ +# src/physics/problems/__init__.py +""" +问题定义模块 +""" + +from .base import Problem +from .factory import ProblemFactory +from .linear_advection import LinearAdvectionProblem + +__all__ = [ + 'Problem', + 'ProblemFactory', + 'LinearAdvectionProblem', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/physics/problems/base.py b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/problems/base.py new file mode 100644 index 000000000..ddd8ddc4a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/problems/base.py @@ -0,0 +1,42 @@ +# src/physics/problems/base.py +""" +问题定义抽象基类 +每个具体问题(如线性对流、Sod 激波管)应继承此类 +""" + +from abc import ABC, abstractmethod + +class Problem(ABC): + """ + 抽象问题基类 + """ + def __init__(self, config): + self.config = config + self._initial_condition = None + self._physical_system = None + + @abstractmethod + def initial_condition(self): + """返回 InitialCondition 实例""" + pass + + @abstractmethod + def physical_system(self): + """返回 PhysicalSystem 实例""" + pass + + def exact_solution(self, cfd): + """ + 计算解析解(委托给物理系统) + """ + x = cfd.domain.mesh.xcc + t = cfd.config.final_time + ic = self.initial_condition() + system = self.physical_system() + + u_exact = system.exact_solution(x, t, ic) + return u_exact[0] # 返回标量数组 (ncells,) 以兼容现有绘图逻辑 + + @property + def name(self): + return self.__class__.__name__ \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/physics/problems/factory.py b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/problems/factory.py new file mode 100644 index 000000000..b3e3563fa --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/problems/factory.py @@ -0,0 +1,28 @@ +# src/physics/problems/factory.py +""" +问题工厂 +""" + +from core.registry import BaseFactory + +class ProblemFactory: + """问题工厂""" + + @staticmethod + def create(problem_type: str, config): + """ + 创建问题实例 + + Args: + problem_type: 问题类型,如 'linear_advection' + config: 配置对象 + + Returns: + 问题实例 + """ + return BaseFactory.create_component('problem', problem_type, config) + + @staticmethod + def get_available_types(): + """获取所有可用的问题类型""" + return BaseFactory.get_available_components('problem') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/physics/problems/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/problems/linear_advection.py new file mode 100644 index 000000000..f19ea53a0 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/problems/linear_advection.py @@ -0,0 +1,36 @@ +# src/physics/problems/linear_advection.py +""" +线性对流问题 +""" + +from .base import Problem +from physics.equations.linear_advection import LinearAdvectionEquation +from physics.systems.linear_advection_system import LinearAdvectionSystem +from physics.initial_conditions.factory import InitialConditionFactory +from core.registry import register_component + +@register_component('problem', 'linear_advection') +class LinearAdvectionProblem(Problem): + """ + 线性对流问题:u_t + c u_x = 0 + 使用周期边界 + 任意初始条件 + """ + + def __init__(self, config): + super().__init__(config) + + def initial_condition(self): + """返回初始条件实例""" + if self._initial_condition is None: + from physics.initial_conditions.factory import InitialConditionFactory + self._initial_condition = InitialConditionFactory.create(self.config) + return self._initial_condition + + def physical_system(self): + """返回物理系统实例""" + if self._physical_system is None: + # 创建方程 + equation = LinearAdvectionEquation(wave_speed=self.config.wave_speed) + # 创建系统 + self._physical_system = LinearAdvectionSystem(equation) + return self._physical_system \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/physics/systems/__init__.py b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/systems/__init__.py new file mode 100644 index 000000000..bd22409cc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/systems/__init__.py @@ -0,0 +1,15 @@ +# src/physics/systems/__init__.py +""" +物理系统模块 +组合方程和解析解逻辑 +""" + +from .base import PhysicalSystem +from .factory import PhysicalSystemFactory +from .linear_advection_system import LinearAdvectionSystem + +__all__ = [ + 'PhysicalSystem', + 'PhysicalSystemFactory', + 'LinearAdvectionSystem', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/physics/systems/base.py b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/systems/base.py new file mode 100644 index 000000000..957212d03 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/systems/base.py @@ -0,0 +1,33 @@ +# src/physics/systems/base.py +""" +物理系统基类 +将方程与解析解等逻辑组合 +""" + +from abc import ABC, abstractmethod + +class PhysicalSystem(ABC): + """物理系统抽象基类""" + + def __init__(self, equation): + self.equation = equation + + @abstractmethod + def exact_solution(self, x, t, initial_condition): + """ + 计算给定初始条件的解析解 + :param x: 空间坐标数组 + :param t: 时间 + :param initial_condition: 初始条件对象 + :return: 解析解数组 + """ + pass + + @property + def wave_speed(self): + """获取波速(委托给方程)""" + return self.equation.wave_speed() + + def flux(self, u): + """计算通量(委托给方程)""" + return self.equation.flux(u) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/physics/systems/factory.py b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/systems/factory.py new file mode 100644 index 000000000..6fcc754e9 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/systems/factory.py @@ -0,0 +1,28 @@ +# src/physics/systems/factory.py +""" +物理系统工厂 +""" + +from core.registry import BaseFactory + +class PhysicalSystemFactory: + """物理系统工厂""" + + @staticmethod + def create(system_type: str, **kwargs): + """ + 创建物理系统实例 + + Args: + system_type: 系统类型,如 'linear_advection' + **kwargs: 传递给方程的参数 + + Returns: + 物理系统实例 + """ + return BaseFactory.create_component('system', system_type, **kwargs) + + @staticmethod + def get_available_types(): + """获取所有可用的系统类型""" + return BaseFactory.get_available_components('system') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/physics/systems/linear_advection_system.py b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/systems/linear_advection_system.py new file mode 100644 index 000000000..0f3a49018 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/physics/systems/linear_advection_system.py @@ -0,0 +1,36 @@ +# src/physics/systems/linear_advection_system.py +""" +线性对流物理系统 +""" + +from .base import PhysicalSystem +from core.registry import register_component +import numpy as np + +@register_component('system', 'linear_advection') +class LinearAdvectionSystem(PhysicalSystem): + """线性对流系统""" + + def exact_solution(self, x, t, initial_condition): + """ + 计算线性对流的解析解 + :param x: 空间坐标数组 + :param t: 时间 + :param initial_condition: 初始条件对象 + :return: 解析解数组 + """ + if len(x) == 0: + return np.zeros((1, 0)) + + L = x[-1] - x[0] # 假设均匀周期网格 + c = self.equation.wave_speed() + x_shifted = (x - c * t + L) % L + + # 调用初始条件的 evaluate_at 方法 + u0 = initial_condition.evaluate_at(x_shifted) + + # 确保返回形状正确 + if u0.ndim == 1: + u0 = u0[np.newaxis, :] # (1, ncells) + + return u0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/plotter.py b/example/1d-linear-convection/weno3/python-v1/02f/src/plotter.py new file mode 100644 index 000000000..e1f99ae25 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/plotter.py @@ -0,0 +1,109 @@ +# plotter.py + +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/result.py b/example/1d-linear-convection/weno3/python-v1/02f/src/result.py new file mode 100644 index 000000000..5e572216d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/result.py @@ -0,0 +1,25 @@ +# result.py + +class ResultAssembler: + @staticmethod + def assemble(cfd: 'Cfd') -> dict: + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied].copy() + + # ✅ 从 problem 获取解析解(唯一正确路径) + try: + analytical = cfd.problem.exact_solution(cfd) + except NotImplementedError: + analytical = None # 或 np.full_like(u_numerical, np.nan) + + return { + "x": cfd.domain.mesh.xcc, + "numerical": u_numerical, + "analytical": analytical, + "config": { + "scheme": cfd.config.recon_scheme, + "order": cfd.config.spatial_order, + "rk_order": cfd.config.rk_order, + "final_time": cfd.config.final_time, + "problem": cfd.problem.name, + } + } \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02f/src/solver.py b/example/1d-linear-convection/weno3/python-v1/02f/src/solver.py new file mode 100644 index 000000000..7bbd43495 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02f/src/solver.py @@ -0,0 +1,74 @@ +# solver.py + +#from mesh import Mesh +#from domain import Domain +#from solution import Solution +from config import CfdConfig +from result import ResultAssembler +from physics.problems.base import Problem +from physics.initial_conditions import InitialConditionFactory +#from boundary.factory import BoundaryConditionFactory + +from infrastructure.boundary.factory import BoundaryConditionFactory +from infrastructure.mesh import Mesh +from infrastructure.domain import Domain +from infrastructure.solution import Solution + + +from numerics.time_integration.factory import TimeIntegratorFactory +from numerics.residual import ResidualCalculator + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, problem: Problem, mesh): + self.problem = problem + self.config = problem.config + self.domain = Domain(problem.config, mesh) + self.solution = Solution(problem.config, self.domain) + + self.initial_condition = InitialConditionFactory.create(self.config) + self.boundary_condition = BoundaryConditionFactory.create(self) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + + # ==================== 公共接口(供 TimeIntegrator 调用) ==================== + def compute_residual(self): + """计算物理残差(封装重建→通量→散度)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件""" + self.boundary_condition.apply(self.solution.u) + + # ==================== 初始化 ==================== + def initialize(self): + """初始化全场:先 IC,再 BC,最后同步 old field""" + self.initial_condition.apply(self.solution) + # 应用边界条件到初始场 + self.apply_boundary() + # 同步 old field + self.solution.update_old_field() + + + def run(self): + self.initialize() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + self.integrator.step(dt) + t += dt + config.dt = dt_old + + self.result = ResultAssembler.assemble(self) + return self.result["numerical"] + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/example/run_eno_weno.py b/example/1d-linear-convection/weno3/python-v1/02g/example/run_eno_weno.py new file mode 100644 index 000000000..d210c6f41 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/example/run_eno_weno.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +# example/run_eno_weno.py + +import sys +import os + +src_path = os.path.join(os.path.dirname(__file__), '..', 'src') +if src_path not in sys.path: + sys.path.insert(0, src_path) + +from core.solver import Cfd +from infrastructure.config import CfdConfig +from infrastructure.mesh import Mesh +from visualization.plotter import plot_eno_weno_comparison, CFDPlotter +from physics.problems.linear_advection import LinearAdvectionProblem + +def performEnoWenoAnalysisBAK(): + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO3 求解 + print("Running ENO3 solver...") + config_eno3 = CfdConfig() + config_eno3.with_reconstruction("eno", 3) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno3 = LinearAdvectionProblem(config_eno3) + cfd_eno3 = Cfd(problem_eno3, mesh) # ← 注入 problem + cfd_eno3.run() + + # 3. 配置并运行 WENO3 求解 + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno3 = LinearAdvectionProblem(config_weno3) + cfd_weno3 = Cfd(problem_weno3, mesh) # ← 注入 problem + cfd_weno3.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" + ) + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO5 求解 + print("Running ENO5 solver...") + config_eno5 = CfdConfig() + config_eno5.with_reconstruction("eno", 5) + config_eno5.dt = 0.0025 + config_eno5.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno5 = LinearAdvectionProblem(config_eno5) + cfd_eno5 = Cfd(problem_eno5, mesh) # ← 注入 problem + cfd_eno5.run() + + # 3. 配置并运行 WENO5求解 + print("Running WENO5 solver...") + config_weno5 = CfdConfig() + config_weno5.with_reconstruction("weno", 5) + config_weno5.dt = 0.0025 + config_weno5.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno5 = LinearAdvectionProblem(config_weno5) + cfd_weno5 = Cfd(problem_weno5, mesh) # ← 注入 problem + cfd_weno5.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno5.result, + weno_result=cfd_weno5.result, + save_path="eno_weno_comparison.png" + ) + + +if __name__ == "__main__": + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/core/registry.py b/example/1d-linear-convection/weno3/python-v1/02g/src/core/registry.py new file mode 100644 index 000000000..dad44ec74 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/core/registry.py @@ -0,0 +1,83 @@ +# src/core/registry.py +""" +统一注册系统 + 通用工厂 +- ComponentRegistry: 组件注册表 +- register_component: 装饰器 +- BaseFactory: 通用工厂类 +""" + +from typing import Dict, Type, Any + + +# ==================== 1. 注册表核心 ==================== +class ComponentRegistry: + """组件注册表""" + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True + + @classmethod + def set_verbose(cls, verbose: bool): + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + #print(f"ComponentRegistry register cls={cls}") + #print(f"ComponentRegistry register name={name}") + #print(f"ComponentRegistry register component_class={component_class}") + if category not in cls._registries: + cls._registries[category] = {} + if name in cls._registries[category]: + if cls._registries[category][name] != component_class and cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + if category not in cls._registries: + raise ValueError(f"❌ 未知类别: {category} (可用: {list(cls._registries.keys())})") + if name not in cls._registries[category]: + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {list(cls._registries[category].keys())})") + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) for cat, comps in cls._registries.items()} + + +# ==================== 2. 装饰器 ==================== +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + #print(f"register_component decorator name={name}") + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + +# ==================== 3. 通用工厂 ==================== +class BaseFactory: + """通用工厂基类""" + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + name_lower = name.lower() + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + available = ComponentRegistry.list_all().get(category, []) + if available: + error_msg = f"不支持的 {category} 类型 '{name}'。可用类型:{available}" + else: + error_msg = f"不支持的 {category} 类型 '{name}'(无已注册组件)" + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/core/solver.py b/example/1d-linear-convection/weno3/python-v1/02g/src/core/solver.py new file mode 100644 index 000000000..d41de3eef --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/core/solver.py @@ -0,0 +1,67 @@ +# src/core/solver.py + +from infrastructure.config import CfdConfig +from infrastructure.result import ResultAssembler +from infrastructure.boundary.factory import BoundaryConditionFactory +from infrastructure.mesh import Mesh +from infrastructure.domain import Domain +from infrastructure.solution import Solution +from physics.problems.base import Problem +from physics.initial_conditions import InitialConditionFactory +from numerics.time_integration.factory import TimeIntegratorFactory +from numerics.residual import ResidualCalculator + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, problem: Problem, mesh): + self.problem = problem + self.config = problem.config + self.domain = Domain(problem.config, mesh) + self.solution = Solution(problem.config, self.domain) + + self.initial_condition = InitialConditionFactory.create(self.config) + self.boundary_condition = BoundaryConditionFactory.create(self) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + + # ==================== 公共接口(供 TimeIntegrator 调用) ==================== + def compute_residual(self): + """计算物理残差(封装重建→通量→散度)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件""" + self.boundary_condition.apply(self.solution.u) + + # ==================== 初始化 ==================== + def initialize(self): + """初始化全场:先 IC,再 BC,最后同步 old field""" + self.initial_condition.apply(self.solution) + # 应用边界条件到初始场 + self.apply_boundary() + # 同步 old field + self.solution.update_old_field() + + + def run(self): + self.initialize() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + self.integrator.step(dt) + t += dt + config.dt = dt_old + + self.result = ResultAssembler.assemble(self) + return self.result["numerical"] + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/__init__.py b/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/__init__.py new file mode 100644 index 000000000..e476afcb7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/__init__.py @@ -0,0 +1,18 @@ +# src/infrastructure/__init__.py +""" +基础设施模块 +包含网格、域、解、边界条件等基础组件 +""" + +from .mesh import Mesh +from .domain import Domain +from .solution import Solution +from .boundary import BoundaryCondition, BoundaryConditionFactory + +__all__ = [ + 'Mesh', + 'Domain', + 'Solution', + 'BoundaryCondition', + 'BoundaryConditionFactory', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/boundary/__init__.py b/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/boundary/__init__.py new file mode 100644 index 000000000..20120e8a5 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/boundary/__init__.py @@ -0,0 +1,21 @@ +# src/boundary/__init__.py +""" +边界条件模块 +提供各种边界条件实现和工厂创建接口 +""" + +from .base import BoundaryCondition +from .factory import BoundaryConditionFactory + +# 导入具体类以触发注册 +from .periodic import PeriodicBoundary +from .dirichlet import DirichletBoundary +from .neumann import NeumannBoundary + +__all__ = [ + 'BoundaryCondition', + 'BoundaryConditionFactory', + 'PeriodicBoundary', + 'DirichletBoundary', + 'NeumannBoundary', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/boundary/base.py b/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/boundary/base.py new file mode 100644 index 000000000..cddf649dd --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/boundary/base.py @@ -0,0 +1,18 @@ +# src/boundary/base.py +from abc import ABC, abstractmethod + +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/boundary/dirichlet.py b/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/boundary/dirichlet.py new file mode 100644 index 000000000..1eff36994 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/boundary/dirichlet.py @@ -0,0 +1,27 @@ +# src/boundary/dirichlet.py +from .base import BoundaryCondition +from core.registry import register_component + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/boundary/factory.py b/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/boundary/factory.py new file mode 100644 index 000000000..4bd7ae55a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/boundary/factory.py @@ -0,0 +1,24 @@ +# src/boundary/factory.py +from core.registry import BaseFactory + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) + + @staticmethod + def get_available_types(): + """获取所有可用的边界条件类型""" + return BaseFactory.get_available_components('boundary') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/boundary/neumann.py b/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/boundary/neumann.py new file mode 100644 index 000000000..6eb72f644 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/boundary/neumann.py @@ -0,0 +1,23 @@ +# src/boundary/neumann.py +from .base import BoundaryCondition +from core.registry import register_component + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/boundary/periodic.py b/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/boundary/periodic.py new file mode 100644 index 000000000..bd601d95d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/boundary/periodic.py @@ -0,0 +1,19 @@ +# src/boundary/periodic.py +from .base import BoundaryCondition +from core.registry import register_component + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/config.py b/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/config.py new file mode 100644 index 000000000..58a952ebe --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/config.py @@ -0,0 +1,45 @@ +# src/infrastructure/config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + #self.ic_type = "sin" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # "rusanov", "engquist-osher" + #self.flux_type = "engquist-osher" # "rusanov", "engquist-osher" + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + print(f"scheme={scheme}") + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/domain.py b/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/domain.py new file mode 100644 index 000000000..05402f4c6 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/domain.py @@ -0,0 +1,56 @@ +# src/infrastructure/domain.py +from .mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/mesh.py b/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/result.py b/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/result.py new file mode 100644 index 000000000..6d70839bc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/result.py @@ -0,0 +1,25 @@ +# src/infrastructure/result.py + +class ResultAssembler: + @staticmethod + def assemble(cfd: 'Cfd') -> dict: + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied].copy() + + # ✅ 从 problem 获取解析解(唯一正确路径) + try: + analytical = cfd.problem.exact_solution(cfd) + except NotImplementedError: + analytical = None # 或 np.full_like(u_numerical, np.nan) + + return { + "x": cfd.domain.mesh.xcc, + "numerical": u_numerical, + "analytical": analytical, + "config": { + "scheme": cfd.config.recon_scheme, + "order": cfd.config.spatial_order, + "rk_order": cfd.config.rk_order, + "final_time": cfd.config.final_time, + "problem": cfd.problem.name, + } + } \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/solution.py b/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/solution.py new file mode 100644 index 000000000..c3a00dd54 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/infrastructure/solution.py @@ -0,0 +1,32 @@ +# src/infrastructure/solution.py +import numpy as np + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/__init__.py b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/__init__.py new file mode 100644 index 000000000..4cfdcc73b --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/__init__.py @@ -0,0 +1,27 @@ +# src/numerics/__init__.py +""" +数值方法模块 +包含重构器、通量计算、时间积分等数值方法 +""" + +from .residual import ResidualCalculator +from .reconstructor import Reconstructor, ReconstructorFactory +from .flux import InviscidFluxCalculator, FluxCalculatorFactory +from .time_integration import TimeIntegrator, TimeIntegratorFactory + +__all__ = [ + # 残差计算 + 'ResidualCalculator', + + # 重构器 + 'Reconstructor', + 'ReconstructorFactory', + + # 通量计算 + 'InviscidFluxCalculator', + 'FluxCalculatorFactory', + + # 时间积分 + 'TimeIntegrator', + 'TimeIntegratorFactory', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/flux/__init__.py b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/flux/__init__.py new file mode 100644 index 000000000..c39d62538 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/flux/__init__.py @@ -0,0 +1,19 @@ +# src/numerics/flux/__init__.py +""" +通量计算模块 +提供Rusanov、Engquist-Osher等通量计算方法 +""" + +from .base import InviscidFluxCalculator +from .factory import FluxCalculatorFactory + +# 导入具体实现以触发注册 +from .rusanov import RusanovFluxCalculator +from .engquist_osher import EngquistOsherFluxCalculator + +__all__ = [ + 'InviscidFluxCalculator', + 'FluxCalculatorFactory', + 'RusanovFluxCalculator', + 'EngquistOsherFluxCalculator', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/flux/base.py b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/flux/base.py new file mode 100644 index 000000000..7a5a134fc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/flux/base.py @@ -0,0 +1,25 @@ +# flux/base.py +""" +抽象通量计算基类 +""" + +from abc import ABC, abstractmethod + +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/flux/engquist_osher.py b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/flux/engquist_osher.py new file mode 100644 index 000000000..16dbce821 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/flux/engquist_osher.py @@ -0,0 +1,20 @@ +# flux/engquist_osher.py +""" +Engquist-Osher 通量计算器(线性对流专用) +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + eq = self.cfd.problem.physical_system().equation + for i in range(self.mesh.nnodes): + c = eq.wave_speed() + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/flux/factory.py b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/flux/factory.py new file mode 100644 index 000000000..c35f6e96f --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/flux/factory.py @@ -0,0 +1,26 @@ +# src/numerics/flux/factory.py +from core.registry import BaseFactory + +class FluxCalculatorFactory: + """通量计算器工厂:隐藏注册类别和组件名称等细节""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD 主对象,包含 config.flux_type 等配置 + + Returns: + 实现 InviscidFluxCalculator 接口的通量计算器实例 + """ + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) + + @staticmethod + def get_available_types(): + """获取所有可用的重构器类型""" + from core.registry import BaseFactory + return BaseFactory.get_available_components('flux') + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/flux/rusanov.py b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/flux/rusanov.py new file mode 100644 index 000000000..56daa6995 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/flux/rusanov.py @@ -0,0 +1,25 @@ +# flux/rusanov.py +""" +Rusanov(Lax-Friedrichs)通量计算器 +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量,使用 Equation 解耦物理参数""" + + def compute(self, q_face_left, q_face_right, flux): + # 从 cfd 获取 equation(与 Julia 对齐) + eq = self.cfd.problem.physical_system().equation + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = eq.wave_speed() + c_R = eq.wave_speed() + # 通过 equation 计算通量和波速 + F_L = eq.flux(u_L) + F_R = eq.flux(u_R) + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/reconstructor/__init__.py new file mode 100644 index 000000000..6b0a3d9bb --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/reconstructor/__init__.py @@ -0,0 +1,21 @@ +# src/numerics/reconstructor/__init__.py +""" +数值重构模块 +提供ENO、WENO等界面值重构方法 +""" + +from .base import Reconstructor +from .factory import ReconstructorFactory + +# 导入具体实现以触发注册 +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from .weno5 import Weno5Reconstructor + +__all__ = [ + 'Reconstructor', + 'ReconstructorFactory', + 'EnoReconstructor', + 'Weno3Reconstructor', + 'Weno5Reconstructor', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/reconstructor/base.py b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/reconstructor/base.py new file mode 100644 index 000000000..de00327cc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def compute_face_values(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/reconstructor/eno.py b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/reconstructor/eno.py new file mode 100644 index 000000000..60699adf6 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/reconstructor/eno.py @@ -0,0 +1,96 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.initialize(self.config.spatial_order, self.domain.ntcells) + + def initialize(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def compute_face_values(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/reconstructor/factory.py b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/reconstructor/factory.py new file mode 100644 index 000000000..1f297eaaa --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/reconstructor/factory.py @@ -0,0 +1,20 @@ +# src/numerics/reconstructor/factory.py +from core.registry import BaseFactory + +class ReconstructorFactory: + @staticmethod + def create(cfd): + config = cfd.config + scheme = config.recon_scheme.lower() + + if scheme.startswith("weno"): + if scheme == "weno": + order = getattr(config, 'spatial_order', None) + scheme = f"weno{order}" + return BaseFactory.create_component('reconstructor', scheme, cfd) + + @staticmethod + def get_available_types(): + """获取所有可用的重构器类型""" + from core.registry import BaseFactory + return BaseFactory.get_available_components('reconstructor') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/reconstructor/weno3.py new file mode 100644 index 000000000..f45eb35f2 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/reconstructor/weno3.py @@ -0,0 +1,62 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def __init__(self, cfd): + self.cfd = cfd + + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/reconstructor/weno5.py b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/reconstructor/weno5.py new file mode 100644 index 000000000..9d6652752 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/reconstructor/weno5.py @@ -0,0 +1,71 @@ +# reconstructor/weno5.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno5') +class Weno5Reconstructor(Reconstructor): + def __init__(self, cfd): + self.cfd = cfd + + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3, v4, v5 = u[i-2], u[i-1], u[i], u[i+1], u[i+2] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3, v4, v5) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3, v4, v5 = u[i-2], u[i-1], u[i], u[i+1], u[i+2] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3, v4, v5) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3, v4, v5): + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)**2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)**2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)**2 + (1.0/4.0)*(v2 - v4)**2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)**2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)**2 + d0 = 1.0/10.0 + d1 = 3.0/5.0 + d2 = 3.0/10.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha2 = d2 / (eps + beta2)**2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = 1.0/3.0*v1-7.0/6.0*v2+11.0/6.0*v3 # r=2 + q1 = -1.0/6.0*v2+5.0/6.0*v3+1.0/3.0*v4 # r=1 + q2 = 1.0/3.0*v3+5.0/6.0*v4-1.0/6.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3, v4, v5): + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)**2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)**2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)**2 + (1.0/4.0)*(v2 - v4)**2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)**2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)**2 + d0 = 3.0/10.0 + d1 = 3.0/5.0 + d2 = 1.0/10.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha2 = d2 / (eps + beta2)**2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = -1.0/6.0*v1+5.0/6.0*v2+1.0/3.0*v3 # r=2 + q1 = 1.0/3.0*v2+5.0/6.0*v3-1.0/6.0*v4 # r=1 + q2 = 11.0/6.0*v3-7.0/6.0*v4+1.0/3.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/residual.py b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/residual.py new file mode 100644 index 000000000..2f96f05bb --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/residual.py @@ -0,0 +1,39 @@ +# src/numerics/residual.py +from numerics.flux.factory import FluxCalculatorFactory +from numerics.reconstructor.factory import ReconstructorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + + self.reconstructor = ReconstructorFactory.create(cfd) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._compute_face_values() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _compute_face_values(self): + """私有方法:界面值重建""" + self.reconstructor.compute_face_values(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/time_integration/__init__.py b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/time_integration/__init__.py new file mode 100644 index 000000000..9c127c775 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/time_integration/__init__.py @@ -0,0 +1,21 @@ +# src/numerics/time_integration/__init__.py +""" +时间积分模块 +提供显式Runge-Kutta方法 +""" + +from .base import TimeIntegrator +from .factory import TimeIntegratorFactory + +# 导入具体实现以触发注册 +from .rk1 import RK1Integrator +from .rk2 import RK2Integrator +from .rk3 import RK3Integrator + +__all__ = [ + 'TimeIntegrator', + 'TimeIntegratorFactory', + 'RK1Integrator', + 'RK2Integrator', + 'RK3Integrator', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/time_integration/base.py b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/time_integration/base.py new file mode 100644 index 000000000..0cdf29a48 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/time_integration/base.py @@ -0,0 +1,27 @@ +# time_integration/base.py + +from abc import ABC, abstractmethod + +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + + @abstractmethod + def step(self, dt): + pass + + def compute_residual(self): + """计算残差(委托给 Cfd)""" + self.cfd.compute_residual() + + def apply_boundary(self): + """应用边界条件(委托给 Cfd)""" + self.cfd.apply_boundary() + + def map_idx(self, i): + """物理网格索引 → 残差数组索引""" + return i - self.domain.ist \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/time_integration/factory.py b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/time_integration/factory.py new file mode 100644 index 000000000..5e6f9779e --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/time_integration/factory.py @@ -0,0 +1,15 @@ +# src/numerics/time_integration/factory.py +from core.registry import BaseFactory + +class TimeIntegratorFactory: + @staticmethod + def create(cfd) -> 'TimeIntegrator': + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) + + @staticmethod + def get_available_types(): + """获取所有可用的重构器类型""" + from core.registry import BaseFactory + return BaseFactory.get_available_components('integrator') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/time_integration/rk1.py b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/time_integration/rk1.py new file mode 100644 index 000000000..b4c8a0211 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/time_integration/rk1.py @@ -0,0 +1,15 @@ +# time_integration/rk1.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/time_integration/rk2.py b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/time_integration/rk2.py new file mode 100644 index 000000000..6d2be3049 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/time_integration/rk2.py @@ -0,0 +1,29 @@ +# time_integration/rk2.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + 0.5 * self.solution.un[i] + + 0.5 * self.solution.u[i] + + 0.5 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/time_integration/rk3.py b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/time_integration/rk3.py new file mode 100644 index 000000000..c70791e13 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/numerics/time_integration/rk3.py @@ -0,0 +1,43 @@ +# time_integration/rk3.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # Stage 1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # Stage 2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = ( + 0.75 * self.solution.un[i] + + 0.25 * self.solution.u[i] + + 0.25 * dt * self.solution.res[j] + ) + self.solution.u[:] = u2 + self.apply_boundary() + + # Stage 3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + c1 * self.solution.un[i] + + c2 * self.solution.u[i] + + c3 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/physics/__init__.py b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/physics/equations/__init__.py b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/equations/__init__.py new file mode 100644 index 000000000..14b821172 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/equations/__init__.py @@ -0,0 +1,15 @@ +# src/physics/equations/__init__.py +""" +物理方程模块 +提供各种控制方程的实现 +""" + +from .base import Equation +from .linear_advection import LinearAdvectionEquation +from .euler import EulerEquation + +__all__ = [ + 'Equation', + 'LinearAdvectionEquation', + 'EulerEquation', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/physics/equations/base.py b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/equations/base.py new file mode 100644 index 000000000..05833e74c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/equations/base.py @@ -0,0 +1,60 @@ +# src/physics/equations/base.py +""" +物理方程抽象基类 +所有控制方程必须继承此类 +""" + +from abc import ABC, abstractmethod +import numpy as np + +class Equation(ABC): + """ + 控制方程抽象基类 + 所有物理方程(线性对流、Euler、MHD)必须继承此类 + """ + + @property + @abstractmethod + def num_equations(self) -> int: + """返回方程组变量数(标量=1,Euler=3,MHD=8)""" + pass + + @abstractmethod + def flux(self, u): + """ + 计算通量函数 f(u) + :param u: 状态向量 (num_equations,) + :return: 通量向量 (num_equations,) + """ + pass + + @abstractmethod + def max_wave_speed(self, u): + """ + 计算最大波速(用于 CFL 条件) + :param u: 状态向量 + :return: 标量波速 + """ + pass + + @abstractmethod + def wave_speed(self): + """ + 计算波速(用于 CFL 条件) + :param u: 状态向量 + :return: 标量波速 + """ + pass + + def exact_solution(self, x, t, initial_condition_func): + """ + 可选:提供解析解 + 子类可重写;若无解析解,调用方应捕获 NotImplementedError + :param x: 空间坐标数组 (ncells,) + :param t: 时间 + :param initial_condition_func: 初值函数 u0(x) -> (num_equations, ncells) + :return: 解 u(x,t) -> (num_equations, ncells) + """ + raise NotImplementedError( + f"Equation '{self.__class__.__name__}' does not provide an exact solution." + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/physics/equations/euler.py b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/equations/euler.py new file mode 100644 index 000000000..0b69c2d72 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/equations/euler.py @@ -0,0 +1,23 @@ +# src/physics/equations/euler.py +""" +欧拉方程(占位文件,未来实现) +""" +from .base import Equation + +class EulerEquation(Equation): + """欧拉方程:质量、动量、能量守恒""" + def __init__(self, gamma=1.4): + self.gamma = gamma + + @property + def num_equations(self) -> int: + return 3 + + def flux(self, u): + raise NotImplementedError("欧拉方程待实现") + + def max_wave_speed(self, u): + raise NotImplementedError("欧拉方程待实现") + + def wave_speed(self): + raise NotImplementedError("欧拉方程待实现") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/physics/equations/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/equations/linear_advection.py new file mode 100644 index 000000000..949c4a0ad --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/equations/linear_advection.py @@ -0,0 +1,42 @@ +# src/physics/equations/linear_advection.py +""" +线性对流方程: u_t + c * u_x = 0 +支持标量(num_equations=1) +""" + +import numpy as np +from .base import Equation +from core.registry import register_component + +@register_component('equation', 'linear_advection') +class LinearAdvectionEquation(Equation): + def __init__(self, wave_speed: float): + self.c = wave_speed + + @property + def num_equations(self) -> int: + return 1 + + def flux(self, u): + """f(u) = c * u""" + return self.c * u + + def max_wave_speed(self, u): + """最大波速 = |c|""" + return abs(self.c) + + def wave_speed(self): + return self.c + + def exact_solution(self, x, t, initial_condition_func): + """ + 解析解: u(x, t) = u0(x - c * t) + 支持周期边界 + """ + if len(x) == 0: + return np.zeros((1, 0)) + + L = x[-1] - x[0] # 假设均匀周期网格 + x_shifted = (x - self.c * t + L) % L + u0 = initial_condition_func(x_shifted) # (1, ncells) + return u0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/physics/initial_conditions/__init__.py b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/initial_conditions/__init__.py new file mode 100644 index 000000000..94b0ad802 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/initial_conditions/__init__.py @@ -0,0 +1,16 @@ +# src/physics/initial_conditions/__init__.py +from .base import InitialCondition +from .factory import InitialConditionFactory + +# 导入具体类以触发注册 +from .step import StepFunctionIC +from .sine import SineWaveIC +from .gaussian import GaussianPulseIC + +__all__ = [ + 'InitialCondition', + 'InitialConditionFactory', + 'StepFunctionIC', + 'SineWaveIC', + 'GaussianPulseIC', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/physics/initial_conditions/base.py b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/initial_conditions/base.py new file mode 100644 index 000000000..8a33a0d56 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/initial_conditions/base.py @@ -0,0 +1,25 @@ +# src/initial_conditions/base.py +from abc import ABC, abstractmethod +import numpy as np + +class InitialCondition(ABC): + """初始条件抽象基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + """内部辅助方法:将值应用到物理区域""" + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/physics/initial_conditions/factory.py b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/initial_conditions/factory.py new file mode 100644 index 000000000..2a7558b2c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/initial_conditions/factory.py @@ -0,0 +1,24 @@ +# src/initial_conditions/factory.py +from core.registry import BaseFactory +from .base import InitialCondition + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + config: 配置对象,包含ic_type属性 + + Returns: + 初始条件实例 + """ + return BaseFactory.create_component('initial_condition', config.ic_type, config) + + @classmethod + def get_available_types(cls): + """获取所有可用的初始条件类型""" + return BaseFactory.get_available_components('initial_condition') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/physics/initial_conditions/gaussian.py b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/initial_conditions/gaussian.py new file mode 100644 index 000000000..1dad463be --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/initial_conditions/gaussian.py @@ -0,0 +1,16 @@ +# src/initial_conditions/gaussian.py +import numpy as np +from .base import InitialCondition +from core.registry import register_component + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/physics/initial_conditions/sine.py b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/initial_conditions/sine.py new file mode 100644 index 000000000..c061fa5a9 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/initial_conditions/sine.py @@ -0,0 +1,15 @@ +# src/initial_conditions/sine.py +import numpy as np +from .base import InitialCondition +from core.registry import register_component + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/physics/initial_conditions/step.py b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/initial_conditions/step.py new file mode 100644 index 000000000..ff1120b2c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/initial_conditions/step.py @@ -0,0 +1,17 @@ +# src/initial_conditions/step.py +import numpy as np +from .base import InitialCondition +from core.registry import register_component + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/physics/problems/__init__.py b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/problems/__init__.py new file mode 100644 index 000000000..e9d329058 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/problems/__init__.py @@ -0,0 +1,14 @@ +# src/physics/problems/__init__.py +""" +问题定义模块 +""" + +from .base import Problem +from .factory import ProblemFactory +from .linear_advection import LinearAdvectionProblem + +__all__ = [ + 'Problem', + 'ProblemFactory', + 'LinearAdvectionProblem', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/physics/problems/base.py b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/problems/base.py new file mode 100644 index 000000000..ddd8ddc4a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/problems/base.py @@ -0,0 +1,42 @@ +# src/physics/problems/base.py +""" +问题定义抽象基类 +每个具体问题(如线性对流、Sod 激波管)应继承此类 +""" + +from abc import ABC, abstractmethod + +class Problem(ABC): + """ + 抽象问题基类 + """ + def __init__(self, config): + self.config = config + self._initial_condition = None + self._physical_system = None + + @abstractmethod + def initial_condition(self): + """返回 InitialCondition 实例""" + pass + + @abstractmethod + def physical_system(self): + """返回 PhysicalSystem 实例""" + pass + + def exact_solution(self, cfd): + """ + 计算解析解(委托给物理系统) + """ + x = cfd.domain.mesh.xcc + t = cfd.config.final_time + ic = self.initial_condition() + system = self.physical_system() + + u_exact = system.exact_solution(x, t, ic) + return u_exact[0] # 返回标量数组 (ncells,) 以兼容现有绘图逻辑 + + @property + def name(self): + return self.__class__.__name__ \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/physics/problems/factory.py b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/problems/factory.py new file mode 100644 index 000000000..b3e3563fa --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/problems/factory.py @@ -0,0 +1,28 @@ +# src/physics/problems/factory.py +""" +问题工厂 +""" + +from core.registry import BaseFactory + +class ProblemFactory: + """问题工厂""" + + @staticmethod + def create(problem_type: str, config): + """ + 创建问题实例 + + Args: + problem_type: 问题类型,如 'linear_advection' + config: 配置对象 + + Returns: + 问题实例 + """ + return BaseFactory.create_component('problem', problem_type, config) + + @staticmethod + def get_available_types(): + """获取所有可用的问题类型""" + return BaseFactory.get_available_components('problem') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/physics/problems/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/problems/linear_advection.py new file mode 100644 index 000000000..f19ea53a0 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/problems/linear_advection.py @@ -0,0 +1,36 @@ +# src/physics/problems/linear_advection.py +""" +线性对流问题 +""" + +from .base import Problem +from physics.equations.linear_advection import LinearAdvectionEquation +from physics.systems.linear_advection_system import LinearAdvectionSystem +from physics.initial_conditions.factory import InitialConditionFactory +from core.registry import register_component + +@register_component('problem', 'linear_advection') +class LinearAdvectionProblem(Problem): + """ + 线性对流问题:u_t + c u_x = 0 + 使用周期边界 + 任意初始条件 + """ + + def __init__(self, config): + super().__init__(config) + + def initial_condition(self): + """返回初始条件实例""" + if self._initial_condition is None: + from physics.initial_conditions.factory import InitialConditionFactory + self._initial_condition = InitialConditionFactory.create(self.config) + return self._initial_condition + + def physical_system(self): + """返回物理系统实例""" + if self._physical_system is None: + # 创建方程 + equation = LinearAdvectionEquation(wave_speed=self.config.wave_speed) + # 创建系统 + self._physical_system = LinearAdvectionSystem(equation) + return self._physical_system \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/physics/systems/__init__.py b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/systems/__init__.py new file mode 100644 index 000000000..bd22409cc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/systems/__init__.py @@ -0,0 +1,15 @@ +# src/physics/systems/__init__.py +""" +物理系统模块 +组合方程和解析解逻辑 +""" + +from .base import PhysicalSystem +from .factory import PhysicalSystemFactory +from .linear_advection_system import LinearAdvectionSystem + +__all__ = [ + 'PhysicalSystem', + 'PhysicalSystemFactory', + 'LinearAdvectionSystem', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/physics/systems/base.py b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/systems/base.py new file mode 100644 index 000000000..957212d03 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/systems/base.py @@ -0,0 +1,33 @@ +# src/physics/systems/base.py +""" +物理系统基类 +将方程与解析解等逻辑组合 +""" + +from abc import ABC, abstractmethod + +class PhysicalSystem(ABC): + """物理系统抽象基类""" + + def __init__(self, equation): + self.equation = equation + + @abstractmethod + def exact_solution(self, x, t, initial_condition): + """ + 计算给定初始条件的解析解 + :param x: 空间坐标数组 + :param t: 时间 + :param initial_condition: 初始条件对象 + :return: 解析解数组 + """ + pass + + @property + def wave_speed(self): + """获取波速(委托给方程)""" + return self.equation.wave_speed() + + def flux(self, u): + """计算通量(委托给方程)""" + return self.equation.flux(u) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/physics/systems/factory.py b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/systems/factory.py new file mode 100644 index 000000000..6fcc754e9 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/systems/factory.py @@ -0,0 +1,28 @@ +# src/physics/systems/factory.py +""" +物理系统工厂 +""" + +from core.registry import BaseFactory + +class PhysicalSystemFactory: + """物理系统工厂""" + + @staticmethod + def create(system_type: str, **kwargs): + """ + 创建物理系统实例 + + Args: + system_type: 系统类型,如 'linear_advection' + **kwargs: 传递给方程的参数 + + Returns: + 物理系统实例 + """ + return BaseFactory.create_component('system', system_type, **kwargs) + + @staticmethod + def get_available_types(): + """获取所有可用的系统类型""" + return BaseFactory.get_available_components('system') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/physics/systems/linear_advection_system.py b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/systems/linear_advection_system.py new file mode 100644 index 000000000..0f3a49018 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/physics/systems/linear_advection_system.py @@ -0,0 +1,36 @@ +# src/physics/systems/linear_advection_system.py +""" +线性对流物理系统 +""" + +from .base import PhysicalSystem +from core.registry import register_component +import numpy as np + +@register_component('system', 'linear_advection') +class LinearAdvectionSystem(PhysicalSystem): + """线性对流系统""" + + def exact_solution(self, x, t, initial_condition): + """ + 计算线性对流的解析解 + :param x: 空间坐标数组 + :param t: 时间 + :param initial_condition: 初始条件对象 + :return: 解析解数组 + """ + if len(x) == 0: + return np.zeros((1, 0)) + + L = x[-1] - x[0] # 假设均匀周期网格 + c = self.equation.wave_speed() + x_shifted = (x - c * t + L) % L + + # 调用初始条件的 evaluate_at 方法 + u0 = initial_condition.evaluate_at(x_shifted) + + # 确保返回形状正确 + if u0.ndim == 1: + u0 = u0[np.newaxis, :] # (1, ncells) + + return u0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02g/src/visualization/plotter.py b/example/1d-linear-convection/weno3/python-v1/02g/src/visualization/plotter.py new file mode 100644 index 000000000..e1f99ae25 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02g/src/visualization/plotter.py @@ -0,0 +1,109 @@ +# plotter.py + +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/example/linear_advection/run_eno_weno.py b/example/1d-linear-convection/weno3/python-v1/02h/example/linear_advection/run_eno_weno.py new file mode 100644 index 000000000..b22822641 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/example/linear_advection/run_eno_weno.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +# example/run_eno_weno.py + +import sys +import os + +# 获取项目的绝对路径 +current_dir = os.path.dirname(os.path.abspath(__file__)) +project_root = os.path.abspath(os.path.join(current_dir, '..', '..')) + +print(f"current_dir={current_dir}") +print(f"project_root={project_root}") + +#src_path = os.path.join(os.path.dirname(__file__), '..', 'src') +# 新的路径(在 example/linear_advection/ 目录下): +src_path = os.path.join(os.path.dirname(__file__), '..', '..', 'src') +if src_path not in sys.path: + sys.path.insert(0, src_path) + + + +from core.solver import Cfd +from infrastructure.config import CfdConfig +from infrastructure.mesh import Mesh +from visualization.plotter import plot_eno_weno_comparison, CFDPlotter +from physics.problems.linear_advection import LinearAdvectionProblem + +def performEnoWenoAnalysisBAK(): + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO3 求解 + print("Running ENO3 solver...") + config_eno3 = CfdConfig() + config_eno3.with_reconstruction("eno", 3) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno3 = LinearAdvectionProblem(config_eno3) + cfd_eno3 = Cfd(problem_eno3, mesh) # ← 注入 problem + cfd_eno3.run() + + # 3. 配置并运行 WENO3 求解 + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno3 = LinearAdvectionProblem(config_weno3) + cfd_weno3 = Cfd(problem_weno3, mesh) # ← 注入 problem + cfd_weno3.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" + ) + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + mesh = Mesh() + + # 2. 配置并运行 ENO5 求解 + print("Running ENO5 solver...") + config_eno5 = CfdConfig() + config_eno5.with_reconstruction("eno", 5) + config_eno5.dt = 0.0025 + config_eno5.rk_order = 2 + + # ✅ 创建 Problem 实例(替代直接传 config) + problem_eno5 = LinearAdvectionProblem(config_eno5) + cfd_eno5 = Cfd(problem_eno5, mesh) # ← 注入 problem + cfd_eno5.run() + + # 3. 配置并运行 WENO5求解 + print("Running WENO5 solver...") + config_weno5 = CfdConfig() + config_weno5.with_reconstruction("weno", 5) + config_weno5.dt = 0.0025 + config_weno5.rk_order = 2 + + # ✅ 创建另一个 Problem 实例 + problem_weno5 = LinearAdvectionProblem(config_weno5) + cfd_weno5 = Cfd(problem_weno5, mesh) # ← 注入 problem + cfd_weno5.run() + + # 4. 绘制对比图(完全不变) + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno5.result, + weno_result=cfd_weno5.result, + save_path="eno_weno_comparison.png" + ) + + +if __name__ == "__main__": + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/core/registry.py b/example/1d-linear-convection/weno3/python-v1/02h/src/core/registry.py new file mode 100644 index 000000000..dad44ec74 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/core/registry.py @@ -0,0 +1,83 @@ +# src/core/registry.py +""" +统一注册系统 + 通用工厂 +- ComponentRegistry: 组件注册表 +- register_component: 装饰器 +- BaseFactory: 通用工厂类 +""" + +from typing import Dict, Type, Any + + +# ==================== 1. 注册表核心 ==================== +class ComponentRegistry: + """组件注册表""" + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True + + @classmethod + def set_verbose(cls, verbose: bool): + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + #print(f"ComponentRegistry register cls={cls}") + #print(f"ComponentRegistry register name={name}") + #print(f"ComponentRegistry register component_class={component_class}") + if category not in cls._registries: + cls._registries[category] = {} + if name in cls._registries[category]: + if cls._registries[category][name] != component_class and cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + if category not in cls._registries: + raise ValueError(f"❌ 未知类别: {category} (可用: {list(cls._registries.keys())})") + if name not in cls._registries[category]: + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {list(cls._registries[category].keys())})") + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) for cat, comps in cls._registries.items()} + + +# ==================== 2. 装饰器 ==================== +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + #print(f"register_component decorator name={name}") + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + +# ==================== 3. 通用工厂 ==================== +class BaseFactory: + """通用工厂基类""" + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + name_lower = name.lower() + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + available = ComponentRegistry.list_all().get(category, []) + if available: + error_msg = f"不支持的 {category} 类型 '{name}'。可用类型:{available}" + else: + error_msg = f"不支持的 {category} 类型 '{name}'(无已注册组件)" + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/core/solver.py b/example/1d-linear-convection/weno3/python-v1/02h/src/core/solver.py new file mode 100644 index 000000000..d41de3eef --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/core/solver.py @@ -0,0 +1,67 @@ +# src/core/solver.py + +from infrastructure.config import CfdConfig +from infrastructure.result import ResultAssembler +from infrastructure.boundary.factory import BoundaryConditionFactory +from infrastructure.mesh import Mesh +from infrastructure.domain import Domain +from infrastructure.solution import Solution +from physics.problems.base import Problem +from physics.initial_conditions import InitialConditionFactory +from numerics.time_integration.factory import TimeIntegratorFactory +from numerics.residual import ResidualCalculator + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, problem: Problem, mesh): + self.problem = problem + self.config = problem.config + self.domain = Domain(problem.config, mesh) + self.solution = Solution(problem.config, self.domain) + + self.initial_condition = InitialConditionFactory.create(self.config) + self.boundary_condition = BoundaryConditionFactory.create(self) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + + # ==================== 公共接口(供 TimeIntegrator 调用) ==================== + def compute_residual(self): + """计算物理残差(封装重建→通量→散度)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件""" + self.boundary_condition.apply(self.solution.u) + + # ==================== 初始化 ==================== + def initialize(self): + """初始化全场:先 IC,再 BC,最后同步 old field""" + self.initial_condition.apply(self.solution) + # 应用边界条件到初始场 + self.apply_boundary() + # 同步 old field + self.solution.update_old_field() + + + def run(self): + self.initialize() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + self.integrator.step(dt) + t += dt + config.dt = dt_old + + self.result = ResultAssembler.assemble(self) + return self.result["numerical"] + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/__init__.py b/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/__init__.py new file mode 100644 index 000000000..e476afcb7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/__init__.py @@ -0,0 +1,18 @@ +# src/infrastructure/__init__.py +""" +基础设施模块 +包含网格、域、解、边界条件等基础组件 +""" + +from .mesh import Mesh +from .domain import Domain +from .solution import Solution +from .boundary import BoundaryCondition, BoundaryConditionFactory + +__all__ = [ + 'Mesh', + 'Domain', + 'Solution', + 'BoundaryCondition', + 'BoundaryConditionFactory', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/boundary/__init__.py b/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/boundary/__init__.py new file mode 100644 index 000000000..20120e8a5 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/boundary/__init__.py @@ -0,0 +1,21 @@ +# src/boundary/__init__.py +""" +边界条件模块 +提供各种边界条件实现和工厂创建接口 +""" + +from .base import BoundaryCondition +from .factory import BoundaryConditionFactory + +# 导入具体类以触发注册 +from .periodic import PeriodicBoundary +from .dirichlet import DirichletBoundary +from .neumann import NeumannBoundary + +__all__ = [ + 'BoundaryCondition', + 'BoundaryConditionFactory', + 'PeriodicBoundary', + 'DirichletBoundary', + 'NeumannBoundary', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/boundary/base.py b/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/boundary/base.py new file mode 100644 index 000000000..cddf649dd --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/boundary/base.py @@ -0,0 +1,18 @@ +# src/boundary/base.py +from abc import ABC, abstractmethod + +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/boundary/dirichlet.py b/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/boundary/dirichlet.py new file mode 100644 index 000000000..1eff36994 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/boundary/dirichlet.py @@ -0,0 +1,27 @@ +# src/boundary/dirichlet.py +from .base import BoundaryCondition +from core.registry import register_component + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/boundary/factory.py b/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/boundary/factory.py new file mode 100644 index 000000000..4bd7ae55a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/boundary/factory.py @@ -0,0 +1,24 @@ +# src/boundary/factory.py +from core.registry import BaseFactory + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) + + @staticmethod + def get_available_types(): + """获取所有可用的边界条件类型""" + return BaseFactory.get_available_components('boundary') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/boundary/neumann.py b/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/boundary/neumann.py new file mode 100644 index 000000000..6eb72f644 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/boundary/neumann.py @@ -0,0 +1,23 @@ +# src/boundary/neumann.py +from .base import BoundaryCondition +from core.registry import register_component + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/boundary/periodic.py b/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/boundary/periodic.py new file mode 100644 index 000000000..bd601d95d --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/boundary/periodic.py @@ -0,0 +1,19 @@ +# src/boundary/periodic.py +from .base import BoundaryCondition +from core.registry import register_component + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/config.py b/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/config.py new file mode 100644 index 000000000..58a952ebe --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/config.py @@ -0,0 +1,45 @@ +# src/infrastructure/config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + #self.ic_type = "sin" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # "rusanov", "engquist-osher" + #self.flux_type = "engquist-osher" # "rusanov", "engquist-osher" + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + print(f"scheme={scheme}") + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/domain.py b/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/domain.py new file mode 100644 index 000000000..05402f4c6 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/domain.py @@ -0,0 +1,56 @@ +# src/infrastructure/domain.py +from .mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/mesh.py b/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/result.py b/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/result.py new file mode 100644 index 000000000..6d70839bc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/result.py @@ -0,0 +1,25 @@ +# src/infrastructure/result.py + +class ResultAssembler: + @staticmethod + def assemble(cfd: 'Cfd') -> dict: + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied].copy() + + # ✅ 从 problem 获取解析解(唯一正确路径) + try: + analytical = cfd.problem.exact_solution(cfd) + except NotImplementedError: + analytical = None # 或 np.full_like(u_numerical, np.nan) + + return { + "x": cfd.domain.mesh.xcc, + "numerical": u_numerical, + "analytical": analytical, + "config": { + "scheme": cfd.config.recon_scheme, + "order": cfd.config.spatial_order, + "rk_order": cfd.config.rk_order, + "final_time": cfd.config.final_time, + "problem": cfd.problem.name, + } + } \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/solution.py b/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/solution.py new file mode 100644 index 000000000..c3a00dd54 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/infrastructure/solution.py @@ -0,0 +1,32 @@ +# src/infrastructure/solution.py +import numpy as np + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/__init__.py b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/__init__.py new file mode 100644 index 000000000..4cfdcc73b --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/__init__.py @@ -0,0 +1,27 @@ +# src/numerics/__init__.py +""" +数值方法模块 +包含重构器、通量计算、时间积分等数值方法 +""" + +from .residual import ResidualCalculator +from .reconstructor import Reconstructor, ReconstructorFactory +from .flux import InviscidFluxCalculator, FluxCalculatorFactory +from .time_integration import TimeIntegrator, TimeIntegratorFactory + +__all__ = [ + # 残差计算 + 'ResidualCalculator', + + # 重构器 + 'Reconstructor', + 'ReconstructorFactory', + + # 通量计算 + 'InviscidFluxCalculator', + 'FluxCalculatorFactory', + + # 时间积分 + 'TimeIntegrator', + 'TimeIntegratorFactory', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/flux/__init__.py b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/flux/__init__.py new file mode 100644 index 000000000..c39d62538 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/flux/__init__.py @@ -0,0 +1,19 @@ +# src/numerics/flux/__init__.py +""" +通量计算模块 +提供Rusanov、Engquist-Osher等通量计算方法 +""" + +from .base import InviscidFluxCalculator +from .factory import FluxCalculatorFactory + +# 导入具体实现以触发注册 +from .rusanov import RusanovFluxCalculator +from .engquist_osher import EngquistOsherFluxCalculator + +__all__ = [ + 'InviscidFluxCalculator', + 'FluxCalculatorFactory', + 'RusanovFluxCalculator', + 'EngquistOsherFluxCalculator', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/flux/base.py b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/flux/base.py new file mode 100644 index 000000000..7a5a134fc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/flux/base.py @@ -0,0 +1,25 @@ +# flux/base.py +""" +抽象通量计算基类 +""" + +from abc import ABC, abstractmethod + +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/flux/engquist_osher.py b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/flux/engquist_osher.py new file mode 100644 index 000000000..16dbce821 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/flux/engquist_osher.py @@ -0,0 +1,20 @@ +# flux/engquist_osher.py +""" +Engquist-Osher 通量计算器(线性对流专用) +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + eq = self.cfd.problem.physical_system().equation + for i in range(self.mesh.nnodes): + c = eq.wave_speed() + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/flux/factory.py b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/flux/factory.py new file mode 100644 index 000000000..c35f6e96f --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/flux/factory.py @@ -0,0 +1,26 @@ +# src/numerics/flux/factory.py +from core.registry import BaseFactory + +class FluxCalculatorFactory: + """通量计算器工厂:隐藏注册类别和组件名称等细节""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD 主对象,包含 config.flux_type 等配置 + + Returns: + 实现 InviscidFluxCalculator 接口的通量计算器实例 + """ + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) + + @staticmethod + def get_available_types(): + """获取所有可用的重构器类型""" + from core.registry import BaseFactory + return BaseFactory.get_available_components('flux') + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/flux/rusanov.py b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/flux/rusanov.py new file mode 100644 index 000000000..56daa6995 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/flux/rusanov.py @@ -0,0 +1,25 @@ +# flux/rusanov.py +""" +Rusanov(Lax-Friedrichs)通量计算器 +""" + +from core.registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量,使用 Equation 解耦物理参数""" + + def compute(self, q_face_left, q_face_right, flux): + # 从 cfd 获取 equation(与 Julia 对齐) + eq = self.cfd.problem.physical_system().equation + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = eq.wave_speed() + c_R = eq.wave_speed() + # 通过 equation 计算通量和波速 + F_L = eq.flux(u_L) + F_R = eq.flux(u_R) + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/reconstructor/__init__.py new file mode 100644 index 000000000..6b0a3d9bb --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/reconstructor/__init__.py @@ -0,0 +1,21 @@ +# src/numerics/reconstructor/__init__.py +""" +数值重构模块 +提供ENO、WENO等界面值重构方法 +""" + +from .base import Reconstructor +from .factory import ReconstructorFactory + +# 导入具体实现以触发注册 +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from .weno5 import Weno5Reconstructor + +__all__ = [ + 'Reconstructor', + 'ReconstructorFactory', + 'EnoReconstructor', + 'Weno3Reconstructor', + 'Weno5Reconstructor', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/reconstructor/base.py b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/reconstructor/base.py new file mode 100644 index 000000000..de00327cc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def compute_face_values(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/reconstructor/eno.py b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/reconstructor/eno.py new file mode 100644 index 000000000..60699adf6 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/reconstructor/eno.py @@ -0,0 +1,96 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.initialize(self.config.spatial_order, self.domain.ntcells) + + def initialize(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def compute_face_values(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/reconstructor/factory.py b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/reconstructor/factory.py new file mode 100644 index 000000000..1f297eaaa --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/reconstructor/factory.py @@ -0,0 +1,20 @@ +# src/numerics/reconstructor/factory.py +from core.registry import BaseFactory + +class ReconstructorFactory: + @staticmethod + def create(cfd): + config = cfd.config + scheme = config.recon_scheme.lower() + + if scheme.startswith("weno"): + if scheme == "weno": + order = getattr(config, 'spatial_order', None) + scheme = f"weno{order}" + return BaseFactory.create_component('reconstructor', scheme, cfd) + + @staticmethod + def get_available_types(): + """获取所有可用的重构器类型""" + from core.registry import BaseFactory + return BaseFactory.get_available_components('reconstructor') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/reconstructor/weno3.py new file mode 100644 index 000000000..f45eb35f2 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/reconstructor/weno3.py @@ -0,0 +1,62 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def __init__(self, cfd): + self.cfd = cfd + + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/reconstructor/weno5.py b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/reconstructor/weno5.py new file mode 100644 index 000000000..9d6652752 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/reconstructor/weno5.py @@ -0,0 +1,71 @@ +# reconstructor/weno5.py +import numpy as np +from .base import Reconstructor +from core.registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno5') +class Weno5Reconstructor(Reconstructor): + def __init__(self, cfd): + self.cfd = cfd + + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3, v4, v5 = u[i-2], u[i-1], u[i], u[i+1], u[i+2] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3, v4, v5) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3, v4, v5 = u[i-2], u[i-1], u[i], u[i+1], u[i+2] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3, v4, v5) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3, v4, v5): + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)**2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)**2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)**2 + (1.0/4.0)*(v2 - v4)**2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)**2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)**2 + d0 = 1.0/10.0 + d1 = 3.0/5.0 + d2 = 3.0/10.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha2 = d2 / (eps + beta2)**2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = 1.0/3.0*v1-7.0/6.0*v2+11.0/6.0*v3 # r=2 + q1 = -1.0/6.0*v2+5.0/6.0*v3+1.0/3.0*v4 # r=1 + q2 = 1.0/3.0*v3+5.0/6.0*v4-1.0/6.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3, v4, v5): + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)**2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)**2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)**2 + (1.0/4.0)*(v2 - v4)**2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)**2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)**2 + d0 = 3.0/10.0 + d1 = 3.0/5.0 + d2 = 1.0/10.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha2 = d2 / (eps + beta2)**2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = -1.0/6.0*v1+5.0/6.0*v2+1.0/3.0*v3 # r=2 + q1 = 1.0/3.0*v2+5.0/6.0*v3-1.0/6.0*v4 # r=1 + q2 = 11.0/6.0*v3-7.0/6.0*v4+1.0/3.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/residual.py b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/residual.py new file mode 100644 index 000000000..2f96f05bb --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/residual.py @@ -0,0 +1,39 @@ +# src/numerics/residual.py +from numerics.flux.factory import FluxCalculatorFactory +from numerics.reconstructor.factory import ReconstructorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + + self.reconstructor = ReconstructorFactory.create(cfd) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._compute_face_values() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _compute_face_values(self): + """私有方法:界面值重建""" + self.reconstructor.compute_face_values(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/time_integration/__init__.py b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/time_integration/__init__.py new file mode 100644 index 000000000..9c127c775 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/time_integration/__init__.py @@ -0,0 +1,21 @@ +# src/numerics/time_integration/__init__.py +""" +时间积分模块 +提供显式Runge-Kutta方法 +""" + +from .base import TimeIntegrator +from .factory import TimeIntegratorFactory + +# 导入具体实现以触发注册 +from .rk1 import RK1Integrator +from .rk2 import RK2Integrator +from .rk3 import RK3Integrator + +__all__ = [ + 'TimeIntegrator', + 'TimeIntegratorFactory', + 'RK1Integrator', + 'RK2Integrator', + 'RK3Integrator', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/time_integration/base.py b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/time_integration/base.py new file mode 100644 index 000000000..0cdf29a48 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/time_integration/base.py @@ -0,0 +1,27 @@ +# time_integration/base.py + +from abc import ABC, abstractmethod + +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + + @abstractmethod + def step(self, dt): + pass + + def compute_residual(self): + """计算残差(委托给 Cfd)""" + self.cfd.compute_residual() + + def apply_boundary(self): + """应用边界条件(委托给 Cfd)""" + self.cfd.apply_boundary() + + def map_idx(self, i): + """物理网格索引 → 残差数组索引""" + return i - self.domain.ist \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/time_integration/factory.py b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/time_integration/factory.py new file mode 100644 index 000000000..5e6f9779e --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/time_integration/factory.py @@ -0,0 +1,15 @@ +# src/numerics/time_integration/factory.py +from core.registry import BaseFactory + +class TimeIntegratorFactory: + @staticmethod + def create(cfd) -> 'TimeIntegrator': + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) + + @staticmethod + def get_available_types(): + """获取所有可用的重构器类型""" + from core.registry import BaseFactory + return BaseFactory.get_available_components('integrator') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/time_integration/rk1.py b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/time_integration/rk1.py new file mode 100644 index 000000000..b4c8a0211 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/time_integration/rk1.py @@ -0,0 +1,15 @@ +# time_integration/rk1.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/time_integration/rk2.py b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/time_integration/rk2.py new file mode 100644 index 000000000..6d2be3049 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/time_integration/rk2.py @@ -0,0 +1,29 @@ +# time_integration/rk2.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + 0.5 * self.solution.un[i] + + 0.5 * self.solution.u[i] + + 0.5 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/time_integration/rk3.py b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/time_integration/rk3.py new file mode 100644 index 000000000..c70791e13 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/numerics/time_integration/rk3.py @@ -0,0 +1,43 @@ +# time_integration/rk3.py + +from .base import TimeIntegrator +from core.registry import register_component + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # Stage 1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # Stage 2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = ( + 0.75 * self.solution.un[i] + + 0.25 * self.solution.u[i] + + 0.25 * dt * self.solution.res[j] + ) + self.solution.u[:] = u2 + self.apply_boundary() + + # Stage 3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = ( + c1 * self.solution.un[i] + + c2 * self.solution.u[i] + + c3 * dt * self.solution.res[j] + ) + self.apply_boundary() + self.solution.update_old_field() \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/physics/__init__.py b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/physics/equations/__init__.py b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/equations/__init__.py new file mode 100644 index 000000000..14b821172 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/equations/__init__.py @@ -0,0 +1,15 @@ +# src/physics/equations/__init__.py +""" +物理方程模块 +提供各种控制方程的实现 +""" + +from .base import Equation +from .linear_advection import LinearAdvectionEquation +from .euler import EulerEquation + +__all__ = [ + 'Equation', + 'LinearAdvectionEquation', + 'EulerEquation', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/physics/equations/base.py b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/equations/base.py new file mode 100644 index 000000000..05833e74c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/equations/base.py @@ -0,0 +1,60 @@ +# src/physics/equations/base.py +""" +物理方程抽象基类 +所有控制方程必须继承此类 +""" + +from abc import ABC, abstractmethod +import numpy as np + +class Equation(ABC): + """ + 控制方程抽象基类 + 所有物理方程(线性对流、Euler、MHD)必须继承此类 + """ + + @property + @abstractmethod + def num_equations(self) -> int: + """返回方程组变量数(标量=1,Euler=3,MHD=8)""" + pass + + @abstractmethod + def flux(self, u): + """ + 计算通量函数 f(u) + :param u: 状态向量 (num_equations,) + :return: 通量向量 (num_equations,) + """ + pass + + @abstractmethod + def max_wave_speed(self, u): + """ + 计算最大波速(用于 CFL 条件) + :param u: 状态向量 + :return: 标量波速 + """ + pass + + @abstractmethod + def wave_speed(self): + """ + 计算波速(用于 CFL 条件) + :param u: 状态向量 + :return: 标量波速 + """ + pass + + def exact_solution(self, x, t, initial_condition_func): + """ + 可选:提供解析解 + 子类可重写;若无解析解,调用方应捕获 NotImplementedError + :param x: 空间坐标数组 (ncells,) + :param t: 时间 + :param initial_condition_func: 初值函数 u0(x) -> (num_equations, ncells) + :return: 解 u(x,t) -> (num_equations, ncells) + """ + raise NotImplementedError( + f"Equation '{self.__class__.__name__}' does not provide an exact solution." + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/physics/equations/euler.py b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/equations/euler.py new file mode 100644 index 000000000..0b69c2d72 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/equations/euler.py @@ -0,0 +1,23 @@ +# src/physics/equations/euler.py +""" +欧拉方程(占位文件,未来实现) +""" +from .base import Equation + +class EulerEquation(Equation): + """欧拉方程:质量、动量、能量守恒""" + def __init__(self, gamma=1.4): + self.gamma = gamma + + @property + def num_equations(self) -> int: + return 3 + + def flux(self, u): + raise NotImplementedError("欧拉方程待实现") + + def max_wave_speed(self, u): + raise NotImplementedError("欧拉方程待实现") + + def wave_speed(self): + raise NotImplementedError("欧拉方程待实现") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/physics/equations/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/equations/linear_advection.py new file mode 100644 index 000000000..949c4a0ad --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/equations/linear_advection.py @@ -0,0 +1,42 @@ +# src/physics/equations/linear_advection.py +""" +线性对流方程: u_t + c * u_x = 0 +支持标量(num_equations=1) +""" + +import numpy as np +from .base import Equation +from core.registry import register_component + +@register_component('equation', 'linear_advection') +class LinearAdvectionEquation(Equation): + def __init__(self, wave_speed: float): + self.c = wave_speed + + @property + def num_equations(self) -> int: + return 1 + + def flux(self, u): + """f(u) = c * u""" + return self.c * u + + def max_wave_speed(self, u): + """最大波速 = |c|""" + return abs(self.c) + + def wave_speed(self): + return self.c + + def exact_solution(self, x, t, initial_condition_func): + """ + 解析解: u(x, t) = u0(x - c * t) + 支持周期边界 + """ + if len(x) == 0: + return np.zeros((1, 0)) + + L = x[-1] - x[0] # 假设均匀周期网格 + x_shifted = (x - self.c * t + L) % L + u0 = initial_condition_func(x_shifted) # (1, ncells) + return u0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/physics/initial_conditions/__init__.py b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/initial_conditions/__init__.py new file mode 100644 index 000000000..94b0ad802 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/initial_conditions/__init__.py @@ -0,0 +1,16 @@ +# src/physics/initial_conditions/__init__.py +from .base import InitialCondition +from .factory import InitialConditionFactory + +# 导入具体类以触发注册 +from .step import StepFunctionIC +from .sine import SineWaveIC +from .gaussian import GaussianPulseIC + +__all__ = [ + 'InitialCondition', + 'InitialConditionFactory', + 'StepFunctionIC', + 'SineWaveIC', + 'GaussianPulseIC', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/physics/initial_conditions/base.py b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/initial_conditions/base.py new file mode 100644 index 000000000..8a33a0d56 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/initial_conditions/base.py @@ -0,0 +1,25 @@ +# src/initial_conditions/base.py +from abc import ABC, abstractmethod +import numpy as np + +class InitialCondition(ABC): + """初始条件抽象基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + """内部辅助方法:将值应用到物理区域""" + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/physics/initial_conditions/factory.py b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/initial_conditions/factory.py new file mode 100644 index 000000000..2a7558b2c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/initial_conditions/factory.py @@ -0,0 +1,24 @@ +# src/initial_conditions/factory.py +from core.registry import BaseFactory +from .base import InitialCondition + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + config: 配置对象,包含ic_type属性 + + Returns: + 初始条件实例 + """ + return BaseFactory.create_component('initial_condition', config.ic_type, config) + + @classmethod + def get_available_types(cls): + """获取所有可用的初始条件类型""" + return BaseFactory.get_available_components('initial_condition') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/physics/initial_conditions/gaussian.py b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/initial_conditions/gaussian.py new file mode 100644 index 000000000..1dad463be --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/initial_conditions/gaussian.py @@ -0,0 +1,16 @@ +# src/initial_conditions/gaussian.py +import numpy as np +from .base import InitialCondition +from core.registry import register_component + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/physics/initial_conditions/sine.py b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/initial_conditions/sine.py new file mode 100644 index 000000000..c061fa5a9 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/initial_conditions/sine.py @@ -0,0 +1,15 @@ +# src/initial_conditions/sine.py +import numpy as np +from .base import InitialCondition +from core.registry import register_component + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/physics/initial_conditions/step.py b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/initial_conditions/step.py new file mode 100644 index 000000000..ff1120b2c --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/initial_conditions/step.py @@ -0,0 +1,17 @@ +# src/initial_conditions/step.py +import numpy as np +from .base import InitialCondition +from core.registry import register_component + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/physics/problems/__init__.py b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/problems/__init__.py new file mode 100644 index 000000000..e9d329058 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/problems/__init__.py @@ -0,0 +1,14 @@ +# src/physics/problems/__init__.py +""" +问题定义模块 +""" + +from .base import Problem +from .factory import ProblemFactory +from .linear_advection import LinearAdvectionProblem + +__all__ = [ + 'Problem', + 'ProblemFactory', + 'LinearAdvectionProblem', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/physics/problems/base.py b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/problems/base.py new file mode 100644 index 000000000..ddd8ddc4a --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/problems/base.py @@ -0,0 +1,42 @@ +# src/physics/problems/base.py +""" +问题定义抽象基类 +每个具体问题(如线性对流、Sod 激波管)应继承此类 +""" + +from abc import ABC, abstractmethod + +class Problem(ABC): + """ + 抽象问题基类 + """ + def __init__(self, config): + self.config = config + self._initial_condition = None + self._physical_system = None + + @abstractmethod + def initial_condition(self): + """返回 InitialCondition 实例""" + pass + + @abstractmethod + def physical_system(self): + """返回 PhysicalSystem 实例""" + pass + + def exact_solution(self, cfd): + """ + 计算解析解(委托给物理系统) + """ + x = cfd.domain.mesh.xcc + t = cfd.config.final_time + ic = self.initial_condition() + system = self.physical_system() + + u_exact = system.exact_solution(x, t, ic) + return u_exact[0] # 返回标量数组 (ncells,) 以兼容现有绘图逻辑 + + @property + def name(self): + return self.__class__.__name__ \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/physics/problems/factory.py b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/problems/factory.py new file mode 100644 index 000000000..b3e3563fa --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/problems/factory.py @@ -0,0 +1,28 @@ +# src/physics/problems/factory.py +""" +问题工厂 +""" + +from core.registry import BaseFactory + +class ProblemFactory: + """问题工厂""" + + @staticmethod + def create(problem_type: str, config): + """ + 创建问题实例 + + Args: + problem_type: 问题类型,如 'linear_advection' + config: 配置对象 + + Returns: + 问题实例 + """ + return BaseFactory.create_component('problem', problem_type, config) + + @staticmethod + def get_available_types(): + """获取所有可用的问题类型""" + return BaseFactory.get_available_components('problem') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/physics/problems/linear_advection.py b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/problems/linear_advection.py new file mode 100644 index 000000000..f19ea53a0 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/problems/linear_advection.py @@ -0,0 +1,36 @@ +# src/physics/problems/linear_advection.py +""" +线性对流问题 +""" + +from .base import Problem +from physics.equations.linear_advection import LinearAdvectionEquation +from physics.systems.linear_advection_system import LinearAdvectionSystem +from physics.initial_conditions.factory import InitialConditionFactory +from core.registry import register_component + +@register_component('problem', 'linear_advection') +class LinearAdvectionProblem(Problem): + """ + 线性对流问题:u_t + c u_x = 0 + 使用周期边界 + 任意初始条件 + """ + + def __init__(self, config): + super().__init__(config) + + def initial_condition(self): + """返回初始条件实例""" + if self._initial_condition is None: + from physics.initial_conditions.factory import InitialConditionFactory + self._initial_condition = InitialConditionFactory.create(self.config) + return self._initial_condition + + def physical_system(self): + """返回物理系统实例""" + if self._physical_system is None: + # 创建方程 + equation = LinearAdvectionEquation(wave_speed=self.config.wave_speed) + # 创建系统 + self._physical_system = LinearAdvectionSystem(equation) + return self._physical_system \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/physics/systems/__init__.py b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/systems/__init__.py new file mode 100644 index 000000000..bd22409cc --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/systems/__init__.py @@ -0,0 +1,15 @@ +# src/physics/systems/__init__.py +""" +物理系统模块 +组合方程和解析解逻辑 +""" + +from .base import PhysicalSystem +from .factory import PhysicalSystemFactory +from .linear_advection_system import LinearAdvectionSystem + +__all__ = [ + 'PhysicalSystem', + 'PhysicalSystemFactory', + 'LinearAdvectionSystem', +] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/physics/systems/base.py b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/systems/base.py new file mode 100644 index 000000000..957212d03 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/systems/base.py @@ -0,0 +1,33 @@ +# src/physics/systems/base.py +""" +物理系统基类 +将方程与解析解等逻辑组合 +""" + +from abc import ABC, abstractmethod + +class PhysicalSystem(ABC): + """物理系统抽象基类""" + + def __init__(self, equation): + self.equation = equation + + @abstractmethod + def exact_solution(self, x, t, initial_condition): + """ + 计算给定初始条件的解析解 + :param x: 空间坐标数组 + :param t: 时间 + :param initial_condition: 初始条件对象 + :return: 解析解数组 + """ + pass + + @property + def wave_speed(self): + """获取波速(委托给方程)""" + return self.equation.wave_speed() + + def flux(self, u): + """计算通量(委托给方程)""" + return self.equation.flux(u) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/physics/systems/factory.py b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/systems/factory.py new file mode 100644 index 000000000..6fcc754e9 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/systems/factory.py @@ -0,0 +1,28 @@ +# src/physics/systems/factory.py +""" +物理系统工厂 +""" + +from core.registry import BaseFactory + +class PhysicalSystemFactory: + """物理系统工厂""" + + @staticmethod + def create(system_type: str, **kwargs): + """ + 创建物理系统实例 + + Args: + system_type: 系统类型,如 'linear_advection' + **kwargs: 传递给方程的参数 + + Returns: + 物理系统实例 + """ + return BaseFactory.create_component('system', system_type, **kwargs) + + @staticmethod + def get_available_types(): + """获取所有可用的系统类型""" + return BaseFactory.get_available_components('system') \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/physics/systems/linear_advection_system.py b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/systems/linear_advection_system.py new file mode 100644 index 000000000..0f3a49018 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/physics/systems/linear_advection_system.py @@ -0,0 +1,36 @@ +# src/physics/systems/linear_advection_system.py +""" +线性对流物理系统 +""" + +from .base import PhysicalSystem +from core.registry import register_component +import numpy as np + +@register_component('system', 'linear_advection') +class LinearAdvectionSystem(PhysicalSystem): + """线性对流系统""" + + def exact_solution(self, x, t, initial_condition): + """ + 计算线性对流的解析解 + :param x: 空间坐标数组 + :param t: 时间 + :param initial_condition: 初始条件对象 + :return: 解析解数组 + """ + if len(x) == 0: + return np.zeros((1, 0)) + + L = x[-1] - x[0] # 假设均匀周期网格 + c = self.equation.wave_speed() + x_shifted = (x - c * t + L) % L + + # 调用初始条件的 evaluate_at 方法 + u0 = initial_condition.evaluate_at(x_shifted) + + # 确保返回形状正确 + if u0.ndim == 1: + u0 = u0[np.newaxis, :] # (1, ncells) + + return u0 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python-v1/02h/src/visualization/plotter.py b/example/1d-linear-convection/weno3/python-v1/02h/src/visualization/plotter.py new file mode 100644 index 000000000..e1f99ae25 --- /dev/null +++ b/example/1d-linear-convection/weno3/python-v1/02h/src/visualization/plotter.py @@ -0,0 +1,109 @@ +# plotter.py + +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05/boundary.py b/example/1d-linear-convection/weno3/python/05/boundary.py new file mode 100644 index 000000000..2d8af5a2e --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05/boundary.py @@ -0,0 +1,83 @@ +from abc import ABC, abstractmethod + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(可无限扩展) ---------------------- +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界(进口)固定值(从配置读取) + left_value = self.config.get("left_boundary_value", 1.0) + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # 右边界(出口)固定值(从配置读取) + right_value = self.config.get("right_boundary_value", 2.0) + for ig in range(nghosts): + u[ied + ig] = right_value + +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + +# ---------------------- 边界条件工厂(动态创建实例) ---------------------- +class BoundaryConditionFactory: + """边界条件工厂:根据配置创建对应边界条件实例""" + @staticmethod + def create(cfd): + # 从配置读取边界类型(支持多边界组合,1D暂用单一类型) + bc_type = cfd.config.boundary_type.lower() + + if bc_type == "periodic": + return PeriodicBoundary(cfd) + elif bc_type == "dirichlet": + return DirichletBoundary(cfd) + elif bc_type == "neumann": + return NeumannBoundary(cfd) + else: + raise ValueError(f"不支持的边界类型:{bc_type}(可选:periodic/dirichlet/neumann)") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05/config.py b/example/1d-linear-convection/weno3/python/05/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05/domain.py b/example/1d-linear-convection/weno3/python/05/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05/eno_weno_comparison.png b/example/1d-linear-convection/weno3/python/05/eno_weno_comparison.png new file mode 100644 index 0000000000000000000000000000000000000000..8d7135ec5ad2cfcf1cd9fffa499596b9bbd8bb33 GIT binary patch literal 224091 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

QZ3;A{z^y1I%tmNc(c)bqip=UU8@!F-F(kyVSX zZb&pV&oItCTky94)7<^#a*u4IxZ>NF&gp`SnAe-ydqe??dKX+0&EOe@*QG!6;fG!p zt;Rm>ft1*XGiIwtCN7dz_=l+-Eq6dFlK^`H+D7Q7=)@I3%1G+U`q7-3hh@}V&FUI#CteS+dttD@&U0MvsfK3-1$zdEl|)2{;`oV~k|=|Ex;EEh za@;+WmTH4<;-)R7pMQeFs3Q)ls6XMSlw3E8TM2iv_WJpIy}#q@U9ojwO-CRT!Fn=Z=3__<*OwvD5j0!h{XsdKl2E%8#j9Fby+4hvDR9yyVph zaC?XYlX{Ix$vKpg+n&N(y{nV*-Q6Kko=Yj2HL_n5(p`Nz1p_|s$ka_;PV^-Q%D<^q8TuuWk2f zL9v6Ug6xn>#1+ySx$XvI*gLtxe`HX|!+j67f8|4)SMUvYM-B>6DAx{s{^qh=wHHQ4 zn(dph#=g6}HB*ypsrIF|$^$ctr80pJ?3n$9?#CFeF1vj+&!30NS0~-xxT{)xr&f%f zPP*q964{+68lR+&m~dzfdwZ@>6w=jduT-0k1w8QRXx`SuE93Dv$V;Lc{CWEr#6i?9xc>a#&c|yCMU%MlOyd_4alOr6dv5%dLYn`?RSc zxsdX6CZb$8{KnjPQAsbY4Ji%L=nR~5DJ9|Qo1qs=-VX0$-s6&xsD#Gj5P(`x5!i)` zs-NL9HMRxud2H25nisVwl%Y9A9GQBiIB z4yhAfcLfvT7Qz>1?KBhO>3$W;b5WOL+k$(BvAM}jc>po5t5!$4niA#1;>>JM z8a0CYY2vIyt7MJ73x(1#Oa1;(U*!B^=}k>I9o0En#huYV1rH<&<*l}PaFA~|bTirk zZ-WZCdtziM6g!2P=Rfz%i!(mgJ1IIF$TYbECZ5X|Uag>78(=A*9NzA7k(~55l^!`h zda-`Et8!WAc+6`eTxnB$|~Bx3CvI7uad0 zWLa~eZK}{^YlF7ylRtL;LivNSWHjyV=QG}a_aV;bZ;hA03OCiknlW$!fcPgwW^51> zw(HEHJmx#SQWmV2m7*3JmeomHaBBaq$q(Vx?|4uyDa_o5FBL<>N{dSz&%Fo@6d;pe z+(yGXnS_M%CvkC!=>g*cA4AAD+d#_}cXyjOQvU6_qAx93k&Os!yQkqOAv@%;)<~Rd z0aZACu-F{xn!!7iYj?lfw_FX!k7T(`C};K5KKUykD%DsF`b|yYBPiiy#c)&M{pRfS z_02*R4}S(rcK3&NmtN1dwpV<-Qxa%<8WH*!8#Ax0C~(en?M4o}O)ESBv8xPP|3L0} z_vgZ^?%R#``-ZEZHnIUdeSA^Ab!PfYl}D1w`x#yCozMIDO~v1FvaxxFrB5c5QeW-z z-$`NKV6#iKmCQqaw@as>2&}~ajMCMvBE+)e7q%GGWU`|_jLOKdXFQ>^%$fMBEn!Of zYtUGJe&)wbp)3x8nH+kLhW?$PA>~X-g=@psb}p^a6zpbigF`{PoVuH|{5CV3n7#15--@$wT4Je;!?_b z#Mv^Q5X!z4jHNWdtdgMA9dk+5GgAHuGi>7vJ4?H!t_hHhRK8r#b1zQ$k3D@jkz!hkWOGpQNb1 zLdzTl+wZGr0MC=I$=M0in8|8(IJU9{1+gvLcqb*m%-2%N3I3{2%vdPvj~}|)c_QFi z_ie^oQJxFah84T&xIm{i|S!Ot@_as0R{L6NP*RBPs5F=~2Zy=Ti2TgOsq6 z*sOzLxh`|5Z-e0(oveqiw$bQwBzxz=X&T}E`!L|Dk`s^7$RQLYY z!mt2bm0&0UWi5l(0Rv7Vb=rG|Hq5cYDA%XBG5i^?zyE$;fs)F~2fiHe^N!Nfc}L~g z67lWvb4kX&W3tJ)*QqkwE#%$SMP9?hthy!<{?pVVNX%E@=-_r4ESk7w^}|!XHzw8T z%T;(7hN=oQ#E2o&b4B4!O# zaDb$JSd{N{@z{VGm|2%PzV-WwHyg)YP@_w(rcmzArjo4(KeRG_14EWa3&%7|nGiur zPxLiRMVnh&$99=e2%y1aZmdOKj7!jV#0KSE82x%{Y&cYrJ$N~VA}k=1DWMfbYB<#; z`$qzqU;omsUh_I_BBogb3S`z5RwcBCPt4{~cAuP@a90Z(&JC8~&nkXWcx*YSNwe

(#qjbAF8)Evg750_f1ExC@M3-{&(R3?5GW)g&)nLINH;<=cWxFmydiFS{<<+kEWZ2N zcwss5MC&YzF2ZwEf8%LGykyidyS6D`R}nbO3vA~TILIUxmA>sTUwTJT>nL%|_}N1J z$Y~6YPHwIgZrFiP4E8?H%#}#@!bYFS+!z-Rg~Vc%?(BqpqBO4JN>KGu`>h9S8QE|D zS;n5VOtR*=K>vGzCN-461zmONQbfb$P%F9_5v1;BA<=_T6pDmu+)Csl(fN4mK#i-_ z%Y(WdK?e@$eb13SbJo|N96r*mMaaJig#;kM$rAFSETMIW; zFXGU;aHK?tQF`sTgKeINK5A9Dz!U@4CCu+DAfhs()tFvBIR9?S#s64(g(t$%imVyq zoYP@cafB}v&-J;=Kuu_G&M84+R>kQ% z1poB-P0JT<$K>GWjq}|Jh-p`Ai!Lguu3ilZ!3V0`#MkoNzvk&&AJ|{$ag3k2Z61JK zn`{yeVl(9d7aH$y4_8m6>bxHwfjhBbV-;~aPLUBhMVJ$9vhS(kP$JBneqGQXUbET8ktqowF7Y#tJutPs}jY@ zw?1e;CFR(Ew&0q;^gss|HFp%_j(QPx%^&xc{~ZRq8mBvoFT`+-D=} zmOKr&J__@M1Z68`or0TfbaWkQIqVM3)Gc;~=2L^8&6>gp#$2`SjGn!=e@JsRwdTmb zK1d|E)6=r^W_R^iW_6_3=NyovvmX0I zpN_-O9AeQ^GRrM5d4e{#wejVt*4%(P4b&4t*aXO#e~E919A%AszJW(76<0@n(nBHk zuwdqhdbkzPMzgVQ^=6WD!Pq|8C=}&J4%ZV(3WGT@`q^zgb!y4k0ozV|nItR3ZniM9 zLp*wM@saj$3gzr#gW>qw##JvR*=*Eo?d-J9EFoasADStdi(lIiW_X$zFabr0Y?;_oSf3SOob!!#J|A68ppX>_^xxLyFKWev4mN8tx0cbIZ`@&>X{;P6KskU z*|ZHfZ3gl+?yVg@BX#ZK(k6gpgA81?K>>Mm|KX``=i0F7LnzC_he z{0e;BcLK_H&*iCKz0=HR@X*81|JgDo=f=Zw^yeJ|-%|D)@4@@3?4F-XOYi!IDWwbX zu=}esjiXIoiHe7CFIm#r-yQ4Hy}h0y@PqgG>muMgz$aX-CPpI!MC#-CIxlg?nX6sp z2cK^HX6#dT9oL*`PW7vsjFeGU%*NFE6!g@x$;dzu=4>fFIoLFjOx8={>8XigMISD% zFhLo_zdoLA=-Mph4)VVjXd@7*512qKyrH5Oofe@fZ$dq1@mbJNawAX3g@%$pV;zvK zH8BgG*rpud+7Ou-HU=I{*ioUJruQ;^t!~_Xr!%$MIpFskU*Yw*K1yzD|hVCaE0(fHTAWsR})l;VUrlrK*J30T%ik9Mj*&GWc+O*fS4 zU}9^V=iUBUMXEzFNLn)V&jW`sN`h4`<6SPLU}%h5Zf1S_irn{bbyf!j%?SFmj(tBl{$9=jy;y4SX;~w;fLNM9}Lp`?VM{IJN%@sQs zIC`v<+#wK+9h6r|Id0TH?P1$D#RF#tO&E3iKLoB-kvhK3d3L|wtplV( z;_KhbqJA*0JKiuK=z!f$;pL(Kcw{#WK*!qvR__Pm$W!quE>t~qDU)})o-5|2w3Ilc zG(V4E4)5~U{_}D^LGN{`X8;9w4zyy=yr=|REv@UX4`DLm^C{2gG9cdWZg0&&MG zyRWmjV!|A++F|l54i)7BaLKZB1lIn_W-Zaeg)_5F>QLwSSyPh z33HW_8d+kke4c3q^m>qSFilR(jfdZhvr|E!WYMQDyH>2-!dTqXs)g5Cnl{P7g2`yCjOx(d#j z&bK9N9AkW1-v4-bh{FK=YKyUPb<%?|IKASzZ%>SMv9firzED(WTIlP^N=uB?y1P|Y zD1eQR7b(yc39X;XNo(6N>!g8!uj7H`o=dOKo!>`P<02Cgpp@K)pgseZl5nt~@fxX3 z>5h0CM$%*s>6 z%#Do8($mw?CI|JktB`IWy3LkC*>r_4?1{%AhoQ3#G}sDR@y>Ksw^yAH8{^9paL2A^ z9p}(QwNEM8K2y4tC^GMd<>`Rsz0PLqVv?VRe#TmqFy;CHM~+xxsNLVXjQ=oPEO+rX z7=yk$!hO!!Ue6vnjlx|`iRn%w-1R2&Q=cEfnf(3tq%F)p5VRF0zjlP$Kkk`F(>wRR z$9;6t+;U>-lHqMGdYrFI*Ud>dWjgp+liw;?p?Dv=!$W4Nj{-jpU*Z2*mzq^YX=Fhb zcpeIH-L4hmg;d+n&yReV%jRqRg}#4YjW&3)R%W6?8vTHD`tI)TE3Vz}s9IV%GTBRC zTQ?L_&O`g#R-|>tF^lX33$N}Wx$_o3xVvtQStv!O?X6P&nH%8eksW7uI*Xd28P~4L z)NZ^xo801mdVWoPS=qgy3pqg!VoF-4gXLsAQtQ|3_RofRZb}ovlam0(T zF{1hb%%QgFPKq`P{5Tx)s`G*TU*9Z3e+$nf`fB<&R+`V~B+2S+3?K^1-G4FQ1S(aW zq+wgbuSamzD!tra9)D+;eE(JRN4_L0K}@A;^JzKRo`PaeGM3)uQ{H+*#%a0#&X)lyN>`wD**X z+Kqk`6FL{1hrBgwnTfEXV||eKPd;}wGY}0 z`|if3jSiK`XJEw0IhgN5oalG|WfAdd0?Ej&H`A;isk*Cx0xsWQ$9(I>(PZbeJS8b* z(6>+#``a1|#H7hXh*6*6p6-;u`l5@nyEZ(0&j4X_II3NcWcO5@S?G3Z=5>rRQZ<&G zK9w0Vuuu3JxPZ(@nSz%L2vD#do`ZJFci5Wxnmnk<}xALXHOUA zi(6KlEip_00SDreXN`pUO&^B~3yCgr`-BmA&FJ7WIWDL|#&71K2gB>()=4+IAv4-ud1<@e-T)??u~p~R zD9K0&LwFM-NZIxB*GF1|TtOkrkP4kO3Yzic#CD^s-|B^~O?$1Wt3zw`ZBgd8Ma14d z8xmA*BBUcn5?(}ZeK&Yj$%NcmL9IJeb9=y-*ZR?v_Wq0RYL>bXJ5e59+HkkijbFmb z+|BbowWhF_+<2JW2P!1Ww;+I)!`O!j5tdx* z>A5`O%Okg*#{sOOoQ7=AMinHC&fmPFP{`R;NL1my&qtrajIA`xaV>Q1ss5c9+YpgT z>yiyP)*o=B-0g7N-+BqLhgV61vg1JfC+UGYWb^R=5qIuT5Ao1Xwb#73pSi3{tyfC< z)0_C^ZV<;J5Eu;`-d$OgPct#}b8#8QFs&p6U5xwDU~6GvbVzs9FgPH&$_WL#1nUbt z^4y>WoP9GgZtf=Psp7^0sd8|}(nXW3R&%oP^9&wM8*ObwAw~C$bsh3Cx0K$%Tkt;9 zwJa(}-X}jp)+wZGGJYgq7S09ZpJ3nM7KebHY%+Oz*dwC&h6oE*i&?&-mV+3ft#79Z z?2YmLZ(1r32aiCdkXZ>f0;RkNbW9;U6D~HKbp5f5&~jEXVNTsZ&m$pLRJY zcx5g5Qk!`V*ilnBAn=iLM<<>uKLpl!q`(SQ-PBa5BHfp@Uxj(nl5zUm6M*X3 zw#OHL#)>`&EAG&+#`EC8`6*_V%BRLagqf0f*#2;!{-sKB14mQjwp4y{6{GB)w_ewL z>K4LlhkNTotj)3+G+6v>(P$(Lky`Di>FhGCMy`CKFw9D=8yerQa%B}08j3gs#;vTq zz5UY3>nw|ccHIr0zdQT8f$%c1Jg$6>E#r4zHQy^_9j%%e8!-O-G*%%i5W5UQVmrQ+hd6G; zc5`I}7QAw*<^?v)NpWdKk+jjViEr0qJq&4ygv+;X;>EvPW7c#T9qgEPTPKSe!BoCJ z?VSkYWGAFSLOv_coEidfqCD^mGLI~$VjR*10p_Z=R5H)Nk_TNQf>+r6S&=S*KmF(a zj^0PqFYtcp4=-Dyy{5)fG4eH)YBkuO(R?>YRno>``71Z-A++SGalmgd_pI+-ab zD`$Zz3lMHvu+q<;{2d;+`GmXFC}>IdLt9Kk5IF>X;`Kb z$Vp`oKHX_YVM&c!*K2rM0v!+rjlC`mkwUYTeTJI=1^4Kxbo}$tYh_cVWYcn z`whjPo2jJp}GVe53V5sjXMOYZV11IV1Rb?+2- zPljI6x{IJ`dTp5MJk~EiOzJfG9V(blnZaLZ`?;nT_llK`IYT+umdFqOiKk3QNU5tv zQh{e7MOAWUb}EqpZ{>dvJ=$gxIa=V5v9s5x4xl$54@u+*KlP^WtCv>N!TYp- z*8&x+DPwOc2wKgyr~%w!dnAh}@Q8q?=a)ib#X~&f9QnG>PDD=c-$R@=kjE0HW_V^r znufWo0Jx6xrDdhIN=;?Z%6R&#-mU9MTZ;FE?~!trdQoYqq%F@5yx-d(K@_Mjm^=|E zTM4)Ir-}Cc=Wp#qc#No!E)z2#C{GF%$Zd=A6n3>ak~Rf`Tak>0CD9<*6s2~8JH!>!AMN8q+bfe!~G-rw@r;5#X7$rH}R%jz7VpF zJfOdI$QUUFFDGE2VSu=(xk9SW`f;Yn2=DP}32P|Rt>*--qIMoXP{kc?@4gl|*@>vk zlEv3&)=#t%_Sm+zsU@Cl?E52|({O~OX z0dT~%*UuQ!EtW3*{)tzEFASajotGbHQF`-`_utc(OB6<$63uti8{YH#DQ6{;mAl-3 zMBhYG^$`HL_tPHw8h`y_N@Hn)Wby+~<&T4l8@sw+x*rhU!X7|x5LO~LD09T=N%y)P zggFMFV1gVAkWF{H!zS;Su&&WwsAc8Fgo_!TO1mR6BgZ_PogB5SWtZpp@?6N=Gho8_ zecNORpiEw>iO@OH>e>1tGskoL?on%gM;#!Fa(>Z3xsne~xt^duh!PiTnjh^_BXslb zvJsIhUwe{hGBh=k7d4j(DQ9Roh<7?hQut(NywN285 za?iyj|KcfQW;7Gah`%UFt4uH>&Mu)x(*#=z>u=YOJ?&z=(F`((KYH#6d-7^R%_QCx*Lo?ATN`RDn6hjeM`KME9J_6CfW4R#8`+Shj`65soV)p zd{QhbuePqPJRTU>SdGas_!V%UxExQ6NIiu!Ui}2OUu!AU1exhj6)TH03KoWIXT%Ra)tQFeXE^ka zFsZ!k<~BwzGviYJHaay`O6-g@Uwy{6H@7n8Xxw2G#o;hYf1rD2tUGOrc3sX3W9S{O zuUFR}y*Ql&`c!#lb|P$5opfX*XKt$4N#o%Q^)%j-c`J)G^L8Ck5-Om^zB2OaMQX}* zfLG^RlbrS9Li5ONUO>K(qP4X(YOG~r!-MUg2L%0o2*Ex0lCVZD8#?tmU7u+56Yh0) zcWA2iJTTOvx}DK2KCU{1+=9cB&36z+-ptlF;P^=L&h2Rm!yn5xRm-gr~ZfpM1t&g4#YGa&;1tt_4{9LOCmevHs-M7LWkDDo&(RC_Yy)O=7%!Fswr@lJXb& z5zkz1Dbf0juK30A*AGz8D(qAEQ{$HWxDFl@I3)5LH@!3CP2KFItW2N^)qokq?rawq zcxK+_mTQXH^?n(*ymCtR=EDbEC*i4 zLF^^x`c6AGJza(zTh}KNuqs^mlzqHlHK3Hm-&pS2O|uy)B_QW+D+|n3nLqmZ9QH2r zsmWenFL&`A$yOom{=#1<4`oGg454Vj;0aZLfo_I)Px}p!5Dv8FR%YL4@!^>|n-wV8 zOcSD9T8NqQ9YvUB2&!2-4uDYSK53X|h&Y_`27B2w277|&OH9beu2Xz--cy3`yl~_i3s5H9{zjv2d2v3yY71?2R_DsU{oj7}VVHGqyS+N))OQ(V8&AGC(;{C{ z1d(2dyZg@2@KdINc4bfG`#H}>G4S5K(J00{W6DNb#QExmliUm4rX=b$lm{M9Q@pB{ zG7E*X7iw#-WQr+pfdlEHB3lqRmH=u5-iJbY46t#arMJ1!gWdE3z7*J@GZcX>y8EeH zW!fL#p7_MGmr2Z@o(*r?uKrA^##TK%!BNZB^^1gK|4rwn^pV4Pxn-C$OKL1*jGT^~ zX6S!lALBUX49hmibaK3*?D*rld5ns&vAso@Nku_KiL}8f87REI43v%6 z$QrH}IxFz~8t>I^;7`3|(^`Pp;?#-rdUmBvjZX42cZ|6VVZEu0&{VC90{B{&0RiR& zN}lI6I*{i-boM4w7#v)S*q{dbMk%%>o7ykLvp zRu`eEv5*6H4ZV(qHfdk!qdaWcM&YSre=iXBa%{EjcraZnd|E?l1&%c%=}#yYevE&d z;Uhdk_!VuyW_(cDXph$OxteF=*RXa>|)6pLi9bRWmH1V@h>n^rUjikp$0c4pxHEbqGL%SMh z^1jm4$#4@hwfRUSsgj_PwXm!j1i&0G9_)G%Lve+)iDqa|5}6UA?Wdj(g1i3d^XFxx z;roA%89ZWhdxg)_a!o&uQ$yi19@V>~nTa@bAdt6(@8iX{_ACfIze?`@l=Jw|A z_?Vg?haNZA(-3e{C?8^wL(QY?eeAX`>6n*-D9yZbnngv<4OwQq4VN)dq8THuDnk&| z=6%YL-Sxa<5YevB@`zz#?Am;{-raqm;msl!d9cM&d|G^E{ev8oU<{j85Tvz=a(vrN zOa2`GZcxjNRa8}VNRg|bA1osU%R{5;Bw9nHq05dW%Nr}D%Jx9UpfvM!u2g5rfxWS= z*L(^FSl!)0WA?hb0lmLbD5m0wdbz*TtYDz}<42b(NR&wVJiD4yv+cW9P9*NYe%TRW zWNYUl&g*J`-hp!MAb>m)CAUjTs?jdisWvXN2s|Po;aQ#eAzv_Nr{W0BVN|>$c)Nx$%VD=2x(KA2> za5e#MCLj+10DfCK>pjZKwCVxLTzeVjc=GMFkgUP>bOP5D*(|meIchrowE(BH1>+(> zao1H5$M-XVY-7_|E>5^RepD9HugYxhc~J5za~J5uS+Ih5gcB*-NKN!u2D_loSMT|$ zZRZ%XE??_^G(`XV-HciY##KB2pN}wk;p2tW#JhKHY(g~0>?YmixTk?z!2CV2y2;piDAWXIO zjKMi$AwHZhlpAWRU!S^6MP437$)i{< zJ11KlDujT_cwJc?wYTLg*BakX1wm}}6DPAg*q;pFg0FbSGW_Ob*c}nU=s@>%o~``4U2Pj1s)}2 zxx-9iBMJ@T5H|dyLgW6;bzSBewx{aL;)w@NV-rM-JtKdAWopch(t=Sy8gb=!&hX=LV0e-NC;Xg#Y8Uog!$>~f)J69Id=$E0)w5pnWUdaX zPYXa8cdKe%u`@#K0htrc%6$+h@lS7+WbRW z=sXp5byYIbjk48Zz8+^vmF)H)`AUc^jmiZ6dTvN>Ts;^62uk%6$#9PNJrC3P(1EU7 zR>H?@^{i>%-m<|(pQ=~Kk=^jY{Iru{<JJ=wW&AooM;u_thDn^3!B^hhaH?h>>fuc8K@% zLq9uFVoAjlrX2fz5u&DA_fF$YOJ?Df_I5!L;T3*43-|+nC$=ACWVG9eRLnNHK0M8h zDR#=0Uf)wCk5^3o%cB4}k%LkeJwNq<85hz`kAsZ@nZNW|v%LGa^nc~V9<$tfo9o1D z8r533@YgvMfmuT6+n%U|_>{m{#jO4`LK72!O=@JM;MUBvmBMb03Mz$aX_ra=5_EdD z-`%SYU{sc54?O&XGz0L^?8A66r+%{5n9MS{YF9I9pkxF^UMscgr|h$o<&Jr=3)(jo zR##Oy<4&)%M*^M)s=N^gQ8`As(|;_^w;k|hLPqVw-)n1Qz1aAanu6BP!N&n+8$k20 zlZR^UTS4uW04dJF?=;4W?4=r&zAPPzpa5XZ7x`lo4bf)Y$~dfsgeQ;=q^*=Z_julu zr_3(z;%6w;FchWVauCVv|KLtW^*&O(Thj-B%NTw6nHO^6Ba9p=YebNpxG#B^;ad87 z5ID()P~?jv(9crz@(+dqTA-N$@PU1=Oi^_zZ&j7+d!r-gnLB8@B-YJXtKU{Hy|{+o zP*Cv>R9o}(OCHg0A{4p*LM|3Zh4D2s^2oiCVyL;Y0Dv5fEGy4@rfzVSVv)26`1`pdYN?XQT+ zq=)d?N_d$EiZ!XDh$y=8fm!g4+bXUSyannI_D>i3(SN{X?t*e`3=ZeXmF z8RD7(A#_e5cVhdoq$m-4hkAQemVGliz&aD}!SRB9PENh=bL~3k%dyX&J(`#s!(4Fd ziTWY;r4eLGAfb;~o?>`F!h!K9M3w>(r0Ii&a?gFqotM^0H=7cz=9jVlB!LbX6_gaH=$ z=EhD|h2IVSgR)!Ge0Jk|ex#b}2R3M@0Y_gzX0lvajLSy1y0S8R=>&|EMmwlRpaDH->tV(nZjFr`Z`F zWu96DuN|0>e&Ejp4m+TOK0lEQ&0$^9gxiF#`)X^i(KcniGq1jG$oS^q$`5x;`4JfH zBK(M{vHpxxj&Yn^NEWoY{5RuKn$hVlQ_ErKxR>$Q1^D8~%oP=VzMlCg7Gf`hC6VFy zlOvBD%c3SWEU{;6Io?`aGOZVV_Mgwj6mEs6V@*Pf^p<#wBh9D9zZ=#s?_#8Z>UAD3A?$6MF0SC`uJ zTSqoa>Tl;?c#5G=gUt7#5C+{mgh=!MAg{b1;9Vca*J;8HVv|hVX>DziYYkS$KN`zLj97~i6i7eS$Y%O-#mm-dRJNET=J?G#%GtKY)V?NDHXM5l0 zxu5&K?(4oTFiYy7lLNAnYGWRjcLb!SkA&g~21@A|G~}{m>b2p7PRP6h=d_oZ^lEYG zy!R1*VsVE&7b%n{%kU*Z4i;UpVh%J(r(QOFrrfMZweLEz9p#6tEFvJsbw$!jOI0NW z!HWWv%p`2aT`d2jC{&&o%Ae0g##moE{C`h!j=7~5tXm|~mIKaAPpVB3@V9AsPPzz4 z9ecijX8K9Lx2ZtXw!dxsWg}JZl_HYJH%KA{rjp3bpg?#DyTMUaw?0#7h(XjzAtz~& zfjF?oo>K|zg3Lv#yQ-{Cr>i>rL3QM4{4QMeE9DsnX?Ljc5o%+AT*|*slDEHdzubJb z>WzSdFK8R}Wl=OzU)fY0y0(W!6X+wAUTW_X5vcM{wAb>Fmrl4~dR+js2*Js}n|9U| z0{9N{6J9bc0T%fxi_cL8a`pkFHB0mO>?PvSKFjR)?e({}Vj_Ug{X#T2SfedDn0M+5 z95Z$tsjPn;Fm^GcQR$30d`&?e>Zqiw-uw6Y$VZo`jBqaN)AbxF84z{(?Bx|_**54P z7Z)4*(9lI~1GBBL`U$KxZ8gv9)NXG-070K5D@MruPcl0^+ zQm>aExVsKpy+ZBO=1QKD_`V?P-=L;0+uaxGD0|^RiTPMP--OkZP*&P7FP0b^eoos2 zR_1S8?CTFK_7TxkUjZWLf>5KCotn~ope zD5evo3#o0d7JZfKNq8K>Bv&5n$6%>&+Gr0tNj020L!UI5&)V3&Rr5f^cq5d=)g!fO z#(93*w>m&JDy8ty<4?W4y+7EU4*w%?T5o`zNOIkhW9>PgMiY!0izm^+&Z%5UJ~T(~ zdL!Ek>1|;ue1owY4Yz&lW>ss)W4K>B1xySDcxZsSOtuoxyf-C|A8yP);D*-!BFB+< z$4X3>;jaV>B&}l&BfMSdFfp*#V<4H{IGRAPjSD+~<-wwJB3OQ7yJo80oomQw!o!9X zsy~x?n+tx5j5(5Epgi{M?eEV7(dT6u=IX~sA^PPik^FuBBwpbpP*|G$G$JC$1q25` zrJN$dnO>=fok2ln{I+QAyc35(kfo^AFZN;FW9sNLsUxb8-S33L0l6+oLX{tKD#uK- z?9p$2VaL>4D84#*BEtz~4OltLK#JVTKJgollq;myb>YO$C+if zkgcEv0~c~aENGSOv{r4aO4wSf4GH$qOykiT=sJ;WfdQlhF_>8?yrhwE&v#za*^eL2 z70oE!hm|AKs|Fh7AKiHqGTHB#5&Z?Ca-a=t1;#W-LBGq6r=pt$VpJ$NT!(FrAAa7J zhq;48Kk-n1T!NE)_HfxgzhzP9i+F}ofkU|Nu4bXT&-EeUC1T&a*P6;`Y;=aenllhh zhmrVMl7q<%et-M8!o-tB8G42vk47{TbvaoV5BCRoov%n@5l2@bMb%F-b z`O;mF-p%_OMXUi%OT}T395v$+KHrF)I+lzeDa;#6I38w1l}DT$F`A`0KkKZ4Z-j|B zpY6BWVEo^Mj-6-H=j!dcbz9PmNAEAj@5{S?S-wiZk2>#*^d63%hR)N}^Z_Lizc zv_P$dZSH2u^mcV?IPsNCzC zJOSc!(AQd;HP|h!+xR7_d$9gGH0K-vY0lznm3f?QogUQs%`%|Rm*c{-J;iDd2d#JA zVmJqVboTm`9`rQdZ>QZ5e4Rk8<{HbJXwVs7v)hALE9&8J`Z_rdB4h5}DmxA@3F!_< z!d-)!lAJ7wOXGBj8msz1`Ahy=&V1TYok;AHGW}(3XUg=4+*ff4dztuRVkk$VmRtJu z$h8G&kh9FaXNhG91fW1 zx+%{cb5wrrbDhx)oi@jD1b@j+efdWc_^7eUy*Rw7eD-Gfr=ik0JpMKrGrGD6P1ksB ztIbRMCbnxPTy9v!IhVT$lKgH6SIJ2N9LNh*B`C*T2lIW?9h}1wX(P48_RC&7r zU2kB-C41ub8Ar#N_s4XbuFe$(1zA1`nDghrtXz`dpFYrU$vmaNSkU*L#&_2s=~L=s!u;jLpgU&qnq2lxfFR|@RWBg+z6>mq9f z@W*A3uw=idi7#kiuFx*V@A9w1-2#rKb~{dV>dSniS3cQt%bBnC(oPwR0;QW{-VHR% zv+~BqE;+twvxsnE z#Ax2c?L#UF8<9U34}Lk9i0DwlXBIx}{6`@u7+ai*0qAtkyyW8wE4}D>A-RiP3t|jPokZFlZ#9bfN>CxF zHr#nNGqYjn(ULfiWum&jubuPouKA+Y&u>%bN>C1ddK>=yu%)G?_M2AA55-~Ouk}m5 z$_Q%b2{+2LhR;2n;d1Eoha1Y^VjsD4&7O0^v%516j(P{q+qXB!>WxF&yd;RIj|IN( z*b1}2&b3=kpidZ|F)@}lAyMm(E(B4JNxQ7K66|_M6SdIpco>d$m*+XewVB#<)45&4 zKYxY_)Pf3niCRNS`(BGb#xQ{;RO^7*tw((Lk83cq+9dTx{DJ1qGc7^_3%YcrO~SKE_U!IGbFzOz@blZYyr-4rpPg@O?&BS+ z9kKc1d_k)Jt)szb^NTuH4p`XfWX+~?tTEb&2(n_lMng(K+wS61|1JEs5uGY#kfjc& zSO03>Te~93La8YI7MsJ^cw2|dUe9^MspW3cX}wQsteS$L0VC%(aeE@d*xq22*wHwd z-r?x;=IC>f9%+jOT`FwIqC~0ht@u8Efg9aN5lAnH3bc%!^DJ*Ir(JtJp>u5J0*iv! z+v~e)SH#-$c9Xa0#*ot{hXG4R3GKcYjxGj~hPStyx)gjh?{b{=$vGw$I$o8WI^(Ed zU)Ci8f+zQ3NO(w7e5nso+Fy>&KaK_NNZ zW2p>UXD_GsIjDMhEI6(w=jXtXaNIhvCZizxsQz3ju- z+vtN?{bOvB`8ModxfhJyERBNaaJhUTb@*I_zei3nBML?b4<#+$Y<+C3Q_Fr@Q1oWK zY}|@*2lWq8-ZpZRfBM>G`Le`awyHLmGmR=fs^!Tca))Ehmb&X{P?i4V%Q4G;f~UPU z;kY#Z{`|-rr^CM2HcbD^);_z7;-h2XOOK2Vr0(&ExRDm{^%9=A zN5u8Cfe$8lcbfFQ(<3hN!{hV#J`Nn;{UF=9;9g3|&z_#X*;Cu~;cNE1QtlVuYr8H;PJj2#6A?U`^Bv{GzL$L|;I$1|zFy!HV_;mr z#AN+?v32qGBmHphs;x|05ow)cQ0IlYgD_q5=-ifaR(iB-ZDl;zM}N(xnr9X4L$$j7 z>~EM!e?i)4Nr{QNEycRfd2t3`ZMR#G8--GMNzcnjZD71tbwUioJeX<6gsz*aL(hGJ zy^u?71G7u-Fd8Uw#_~+AL&mGiXVN`|@-h(Y?bNOyg)F{@OtF!sBmrk>GK%W z`d29AiBIQ-=sy?Zm&X}ZuYjPU;~LC23;?gR`bbs3;}Do<-(fl{#Q~A?xEAxhpM|=^ z)eLHd3x+}3eVFMg>~b{r#bNy#&sclB@rtiiRem0r9_wxj=1A)|-~jmNx}YLOT6%^% z6n6M=pbHo!iwcI^8kC<{2RTBQFI<#BZl6y*!U{dnu5(qSnnQl3*#Wm<|lc4#X2T(PT~AoLDo}@BRnJI2S(_T*GjiM92}*~^O3Qk;Q%s} z={^7}jj=o=%%{wmwPm0`toEMf+hKqY63LYj5w=7JGAAC;^bI{13T5*NU`|BQv^vQ$ z>;3g$h9aZkGdn!rs($mnrj-tufejy6pXK49k5a8dvy8x``M?y z^J%k-iwv_S1?h>S6^?2_aCGDd-aUVGrj$=9&s17(O$%t^9VDvHg0gE`s{Tu1+PrO< zl+Q!~pHh{DW_ss%2b}jJA|iH*?|df7#Om((FdKVqZEYfAI%(rdrRNq{yhH@iVhJJ~ z2gTvaVPtPwQ5f*u*?(?b$gGMKTgxiXvBMU3G#OuL(w5ncXg1?o-GYxgXD$v?u!_|0 zH~Q@kqhY5YD+%lz6faEA!8|j)-h=wZ-~Mev&(=KdVYL2On8J0)HXGj}-coBkY+Ig( z*3lh0=rirw+cAd>#5;iJYB>vGP?o*{&=ExE5w6x4AS1SC5hk!4p$JtHDUx0mB_j1F z>9sAGCN{kR`wVtu><$Y}+GCGdX3dX6AY;O{KH{H0{X$WLN}Vq!Naj%}i9qRIr;9-k zs>1*u;f1Uynda@PRe}@O?dA*(IOk3WiO1j^G7AIbC+TXr2v|2VUY) zz4|ez__am`23*n2XeM4lqx@qQrb&R=U~-WkFtXDj`VL_rmaz6bL+HBRaLgE|{P_=! zxmWHy7=Dh>gym*@Oo~b}M$BB%{t|~`Igx2z*PR6XJ>0$gQtSMy} zU7aw^5&w_L93e5jK&!@3`J9gE6(;@V9AH6VdWJp%9f&+ux%gs1o4)@s*mcA9Sv}nO zP47XToI{iS7}9eh*V}h-#`0h>?)CD_T(g&-*v^q)t{9YG+YQ*TWk>B$d!l(Ok(;&- zvR*4FmPcfBi^*In^YvCcO8iG99D)yDgzc;Rr7y=l)3T$_JTd7ec`;&6J+tX*1eEp@;)!R$DqU(CI-`V{21e zUWD~%>QO@eA!l|p3j!+eXBL1j)lHoy!X}NUBP?NSNk5yz3cN?`E!z{YIT>>|MW!Ym z)=zb?Z}#~Dt>|@=j(03lOB7hjqciSoWbW#z0^D2NfFIg9kKwIHRzhis`o_d7HZ0!c z!LRpdy}JooU&1# zzGZ{1ASpk^mVBUNAdi})$U=f-21C~u<24x@Lj82b0pCgk4<%xbWy#j2 zgjYQ)jnKkUY$2-Vv?64x3l!`res*+xKp?f-?co0X1c6Qi9{>bqH7_Ei1)qRV@kk|x z2jm(1(o+krfzk$T-K<_f%hPf))OLScej8f>Vg)|j3p%H}og)Y_Um@;c#N?#|JC{ps zI|G0AT`{%te^MDBaDWL(=9?A9mceQHNhn3_0P|K(wZ>wEfFOEp+TaG+S}C9Vb6A@v znh<#-F<^@wbSTubKho}`?g*rmIN$9p^;gaJMuKsHvZ!CuC`?hSkKvG>Vau@1@kF| zpRm3(EAygE!vkkVULHHtRXl}kgMx=hn=#3v-G$($33Pu0@-$kp2w-E70c=3G;PHVW z$Z&6z5k>AZ-H+q6WB77R40_yrnQfrM1p%F*Ml>HU)^LknI9&2h2A-9zO`RQOWo7#0 z{WL)`x~+VFZ3YlFu7Mg`<4q^Zu&dJK01*|6`+#HnnDhx+c3Nl_|F;rzNfen#Jx9w4 zh&R+s|E#TUbDMiPR8RdojdNb)uA0HxQ0CVagVLC%;y&66Bp6SU#$_Cj@1sppS(v&3 zSf`#snm=J`;ir#rWnSUV_c8hY-Z`mbq ziP6Eyh}E`5@Nnn9`n=pns0_L5ip`w2nJa>ga_1@=npQPkH=LJmjh-taNbcsAY2h&r zOR?H`5yUUf6@JE5Z`Qz-WkaX(^ja%!j6^IXTE#zvwx*<-Nm>Hw?H z83-l&T6jd>TDSXzz4>`d9SH`R4JO8B}3lMSq)LC^SI$t`E3>!bs0HtDuk03E#rFoi#LC=wJCso@P(^kDbK5SHQ|F<0lF=+l|_3PIj?SDp8! zbziZ{yD7h-`#WbuKGbfj>|UP<6JTuvqSWlL`uDywm=_QwNF6;)Ba+~oM<&5wwZ3Q_ zE^s#sicFgf;%ip0(?(i=ZWpeqj8g(LMO*~|X|#b@q^(u;ml-t$w{*@MGSLV2o0fJ! zG;nE5o;usU96D*&b!Q{P`Au82vvkL^bTK3d@lXFD-F!H{I%$^hmb}}1e0#liJH}@g z1PefHwTB)`v+&`^1Gxu^`!Ogw6H3fd;oD8_-Biocl@MZ_rVQTe=y<20`~I(^ap5ru zRwm~`<;(_+SX~g)MF)wi#v{a-w%RnbptZP)o+^paB)>Q1L0#wtYX}VNVPJQkf1bZG zZ@ENUiUjzxhzDtJ0S$}^Ba;(LeD1GcMl?m`Aho8>3!tBSdmPHcfih)LKdo<07&ku& z(wzs@y`D?Jv9}rCEhQz}=H0*6!qQ%|+5yLhD$K`WwC|yOz;;{3^iMO?5pom%odIxQ zVlclZ#r8BFBJ0=*Y03c7d}15^Hcp>-OUY2UB`lp{N=n*=W;}h`bX05D#(tUt<5?#!re!jwx}2BF>b&fOGpQwkQJC@k{=P63 z64tC-`O{EDtm|P1qM|*ZzJ`Wy( znFAe?G((cul7zkUm5s9UAiNnNCRXX6_c8yZRsUF-JpdhH?w*DI){B{ravsr$K(L(ff)0oE&8u zlMx3Bo8qd@3l3R4mL$)LY0=mIpVB2{uUs=osqoV&vo9M|cMfUgwfsr;F7r|_w`q%V zX181UI7$>;B+Ra8uNN~mFbL~K{uH?Ji?4(TNGha7<6iCNCVDpTT4K^oM_s5xDNjAz znK4-CUsi~@b7@p`z)Z%_Fc#wj3pogI!^o8u(Qys6?BuoDA7>n|PEp928fbibYA$+% z4ySa;AwpPaPMjFEqSr6Fw&VfHOpEc%Sl@A;etC4^8$IRoRq(k9M~{h=K(Fx`UCEwp z+Y~suUkhpS&$-^jBfn}}X75oxI#u=aRcE@iKgNacDMlULq;B(vnh~gsw{Wx$$m^uo znB#ZlfiR8hEl0)cb&%b_ntWZAsQ^<-O55s`AB5Y{YI_Q9X2~K7g};s%vqH=< zwm{0!A;!`IUJ&Wcp;Vb+eOSd((Yy7WO?j4LQ}*=K$ZFO%e^J`s4zufJq7HV*|GM7_ z)Yp%uEYkfE(ruEvJhUNA{Q~$#?4V;D(L>S0W+tYC8PT@#co7_oOXhN7kVk_lR#-U8MlJ>RYBSx$8bd`0PXh(2D3-|Nf zyw{2hW4mBde5<*lZ;c5BvK?raHQqlfzP82$51ot_rgI=MI7T&vNFm92Pm$G6R%<({ zpH9R)>-AA&4F8=)hb#-aY(D?-hH91r-y+?(ye9agB2Vt7njU27Lo7R?6GY$)xwAYu zBSyS223`-tIRBN$&g|5ev$(hmOf2HFg+;Y&>)CyN$WheiQ7GCW1V|ha&iC802EM}Bc3(}Z`}35VY5-EW+DtNKg1c=3g3V4@}Ab5wqZjtwaund{*j z#uh#DZj$hO5zFj2&>bhN8!9M?&~eBB|Mt8a#mQ&-WPT^@Bd?=hgW-Hqm7pxAfStlc z$DQ4wmBVMfx-P=l&8oU}({hR$B&b-R(q=0vWA>s2E&4A>ku#GyQ!`LwstSve%)xbh z&f+>3Jq#r#Bop}@G7D!=#FeIa{TNFHpvCJrR)m}w#EWVwH_a$L!sdGO-pUH z>yu|jb%EWvUHevii=}o}^D;J_{2N7_h+;l|t{g&X{>MPpQ*ciS5CZBJPtEon4_TCo zWO#Ax$PGVTKXxreQRwb9oG~YD$Vy~wl?x#4X=+w>3g!gr1@^>!dr|O#=SE*!lCukRw6Wmou(|&E*RF>Fxr_@5ehz5SlsjElA z&rB{bcN_@_*~CilKPE>)gNOX@3e>}}GB19K&(2${1K^1`5aDm*Th|$Ttn+_$HTK{7 zm4Tr4)h3Ehtf>RG4siRq`W!6M_m4IlnprgwCvnk(kr!`AQD~pyxxd8=Bp|6I*Fv}t z)U$XQ)!bwqgomeO6BHKD+F+Al4DVvH8ux%5@#|WeMa5f@i?zJ6vYrbm&v>Ttj)NMT z7Jvemljq>+K`^wpcb7v{nytfl!o-rW1`K^4{z|!UZ8FnwDu$WW^_rR)I>*PlJ}~KW z6!-kWZnh4iiWFIkj@$ai@%JzwJM~rXB<@J`KDVv2l5>(ZVS5y`W{SAM`@d#bs`8f8 zFc3D60m#bzKv9&#s<&5&yd=0Gt^byne3gsxeEi@p7F%LmDWA%GT4Y{$-O@drRvX14 z4X0xOQ=9HKuWZG?wdqQ{ggh>K+@EPJBzXNi7Fy%-9p)_N7Pl>xQ6T$du8fyRyi4r= zkZa~TyYq%NLd~o{6nXn;b+stHxc$ijdS!gpI08q9t|+7^Hp-%yc{T*lJ}EJHVG6v8 z=0~CUOuzL(ZYxWJbG@s-zdy0CN=^As9pVzNLykr&e#6Cvv)Tohx{9{LktokoNXqAf zE0d|b&>f7b#!+wS(_{7Z!6~wJOCn$S#Qw?RYO%ndJ7e#^6QQf0)Ahbi;+c*X{4U^c zIK?6hi>|z2skTb?qhc!6iIOD+=YimGy$BXsYeOUg$o1z@RaL?k_H%UD71Z^9oS-0n z(w0WI5B`R*bOvK~1AI;Hl6ktnP-1sXp6*V6MQHPg+FGQSpF5}Z9Jwrx_lT+=$PIF( ze6sr6KWtxwp}VO;xs|6#ivvOmZ-T9-kM%8ZQM9CUTA?Eim2S;rl)%KPTJq1b!Y5Bm zKHj@FFaX6-WF2oIogH}Ne6^>I;lU=aCLULmj*Bpz1F~IG+?v*U#ea zh+iBX9M93>h!SeO1k*o`{rk}tVM0I^ObE!H6z2aWGyHb=(m?f-?oo!oT2;B2e4GKX zid(O>upl`}7tL92ygaqT@7U`qBvM6v&gHn^>>Pb^y>3ai-S1SuqT*jz32=d>LypVmt)O#{jf^D12J4iP0m~|j z$uq{BWBJ4Qa*T-H;b&GAlUV3UT6wDd+v;^^4VPZoIk~U2QCJeSss2u482~{!fu$mG zkEe$PrwQWA9FEb`_xtX-E_6R}R)@a!qs^zv=x9s!oecz&(t0nzb+w%7U6T)b4H)1Y z(4@R(DaKARF*RVIvz8*n7XLDpn9V+(7$YW}@NAuK>B}qZE94!H(h(i|^f4-hQ zxA&JDUiZhW8;AVhj`TbS^#Xj(yeV~V1>NYYp0NgdD_!Z9><;)Ii1bwu%H%{ltYei{ z4kTHe1tV_{wrF4kK#exNs2e+y_SSEw@7lLK#lDM;WcN<>@s`HP7fZq=W%(WYQ#$)P zwcorF6V@@hI|D;blc?%S(MeqWu_oE+>ywNfr6G*yd{+RCSeHBp2>JcF>%>I22j4;c zR9(2pQ(heUUk)+c682LL5pGEKeuk@nZjyWBC6?0W@6=JRHM*ZYcUpK_<;$Qz?AwgA%#AC~dWX>6E2KQ-4(hI-RV5pJ^tQdU zMY^*|Jsf$Ri&Q-Z%k%gvDg~evIsFm-H()KryqvxtTE7e1Cp}bdiOIX)pm2rM^7&i= z-0NOMj@~CDuV3_Vnv3g1z2|46d}YcFfL}^Ti2t#yE3Y>K3m_IiaJb!NIdFh8Q=x^G zH9@OM=JmMKiymMN4WA<}ModcKpm8=M&|!SWG1MTbvalY~*T2N`?@4Z+<){Qt%{jf& zd zjE;{8jQ8mX7$xOg#*LiHUlsU0ncYq9R|L$UEL(ByM6!2#drM1;<9LGzb#yqAO6VsO zZPl|ME$QXO9F@&oVYdxh2Ah2g~h8(h?FpvNlQAnRn!KHjF6f3$8Z`P#d2z8iy1ql#JFwG_QxcVxM4j-_Io2!NY^aPM`)r#FpOEsl33?leo3^W}&dH(YfMVUoe4ov> zpDJybO9ekJ*~P^^d;ckR;q_a;-lV8w(fa#zqh6mmLNe zPEehzDP`8Q@Y>Az7w&gszy^9^UXjNZ5fk^6myIIH3t>E=G}<2@u}u*^?^E@N+IxQo zsk**W0VzxOWpc?K26H)u_l2TKuV)6J!Eh$J{>xkSc{@|NcHWq$e2x=l@`l~ncN3b? zYzR}2{q*|`ipBJqH@mVT$1dI3Sy*d@uo&OAi$}R`qb}c4R*<~&rn3r7x5@T;VcTrhug+80oa_&E7==q!&JEp#L=ZmmbbFo?oWL)Ru=d~BBdvJ)mU3c|AH(66@ zWI*t2J(xdCZeH?f?Nkq54*0;{=GB^rrSsW~-B_#Cq}-amMDTKkNOe-^brA)Jsa3Vu zGp#hij=J;a+H-XrgcJ)jK`B&)7Pkb%7*7yoB#{k%);rn%Aij_Jz$g^`?GWrp?`ePy zSg{tdr;%()pzEI6#SH=Rh8G3*6&XKXK4hCU31*Kg|ML) zp*D2VMFVfWGxlFPWbX>8TiEZw>U=UxkY5ugqI|6<#KF$ib*Yex|NMv(nw5KkulDa{ z#;BmppFV#+oo!p4%PFFL0@hcES0#~_F|KzfHv;CTPRc6~=dIqLeC}){X@8G!H^mKl zO5H@T4Q(1KUuo26P!OH`7z*qCBCa=nR0`DWYv)4t%ps_;+pN7spHpCZ`?BrUU?rqKw+S$6qr!!U~-DyU`&~J0?(5j1@nl)b;e7(^gh_}^g@)H9$ zLy_0{!vfc)Qjy1059c2*6;o`fqm^k-MA(;aSxpl@9`h{sER~Puo9j!TJA)EKlNs3g ztG-W9PY-~#FLBmR;s1=xZy*o8_&&Xn>hW@7`079fe~16#1TiSel|fDlkNU5*$D{ZG z5Vwlxq(~W48R-DK!T69FekP!Oaq=j^ZToFxMs%&i$aWuvv&?lqbygjP#*@yVB9O1$ z+*T*6OD`B0#N*IA04Qhy&O;i&BODX@kL~CsovE5YW$vxn3!A|G10AcK`F$&4Ejz8?Pb=U8L#?&@#NX8D0T z4)klA?3G28KN)Ej$3bUmxkcBvcg>^F-;Ya4F4g9sa&wq`5yfhkYRLd|1VQU{=xI%M z%6b~#N_P3tOH%SD-#YbPTd3ffO@hJnO-jCCAOI^ONaEZuIL=%r)OpMA9i~YnFXG8L z70UXHb3i7kRtiA6K}2`5h<-B~9o2E8x7OWLG@~Y$G2ioKzvL1Y=C*#7Na`|^Pa8Pw zM|Own1f=DUHnBTOPNst)Un-7q6SoQPdbO_rMFI_ z(!@iXay%kqtr*r<--DyoBQwT)eYHsIEO;V4LBdj(tDo{z8didLEH=vIav4 z19R0@z$W%v3g=CBKJCzJy0Wr1F~tk9eKmWu(i2QhcXazBMJP^?Gz+^cU|K}wGkhod z3*`p_9doG#?)nlC5O$=wQlj~|_C(+HxtvdnPJOuXwK?!8b)bWTM=GZ4PZA4o8!NR& zx0^NP+u)C6^O57bLNRNbwMO8N2VP!TkP3KZ$h*>jxnZV)^ClJ3#vf0!csLKJ_s@V- ztaWJf_00kO^|JHfr z$vi$e8RS0vazh`TOSShNr%^9fE$c4P9X;NSg4K47NTN_BC|d?hUpk; z)uo;31yt$b428+aHC2?&CZ36WO$R=h6bZn~Dq^d5ZuPjeCN zjoFk}0#o6jz3TmS4z6vx+7iyCpSmgDJN(}8x*(5~nf}-eC>D8&qtgpO2h`Ok`hJ_cG^FVJhnw*|*|1*Cv=CFBhyC=9 zxCoj;o*S=gDSOd5lerHS-sR9n9D=NO##xXw@=-8coRmrm2rpQoCR<3VH~&Y7gSyOU z*VgU|gU(1emM1iSV5ia=+#XhqJv*`YASLh?Iin6}Yx6?QqGH>>Q>3Le{TXZa_vPR3W^NAyN#P}fZ1lhOZ30MuX(g}E8Myyiv4(Zu zN_xJ7;XMx1h}&!BJ%#&@Tu^4Fr;l)$vTCHo&qEaSB2#+O{Kfd?d-G3R>$%D6B01VM zpkq7M=X`~YadEICX~?o7OoK5C^~2)q*6x9{Joi+49yLl}`}Br6sC;)Qcg|j9_!@et zBOM&URO$nXe(gABP%6+wxtDS#GIfQhv3AGf!kr7w-#;~jvb?ZNM}j5quBO-c<>P+e z!+QSA{K2IL((K&E$=$nYXE4PL?RJi$r?KU)C{FXH5Z3(MxjCq|ZgDl@Oo}?a%S$*( zMi}k7Qf{knJLX%6^KIWEu|E+%GG^LRvc0$}q0>^#_#XBjqT_nO$_5i0d2VW&zP4f~ zzrDTEY=$Fm{!$-#Z85guk9!c zCl&$s?Q83@1qAwQp$k?mAZ6}i)$)9%$JYZ?p4cMyM#l-;bm>mXFBFB(EoJXVW^H+# zoGi0Db>RHkpF^+qLA_KYSSWjXJG3VHa(pG|6NmC!z79$Iu<=1|jvP+QrOce+OE5}r zOp-_q9@4f|nzw9W_xrgCFB?N?je><0|6KU#9Mqo{ep&x+k6iKH`*`OM*%sDqmaFP= zMRFw!^pE6&H)vY<-t;Ov11baV5h0v+Eie}ET{?T+k2yK#axq@9^j`;*ekcx#;eNxx zSTdzQaf5XdH5YGqyw__j&6f`<+6AxA<&kdSCe66;Tmw!WKi?WV(_My!@2GPruiQu? zh~yNLWJ~^QVV6`IQ!%^FFt<2P5TUj|CW8W4ugdgB<*YIiNExo2A}<*g<>#oQbjRIa zyzeIsJ}6o9zoXk3ksN{_wnhI@q(u;p`B=XzjIu^sW%|ob59xsCZGpxlN;t)>eQo72 zrVQt0TlD^v(gG+#YtTyCZP#JSG168aRxoupq~fM)%Vak$GFeg=5zpUWBkgFpfmuC_ zO~qMKG({*)b%88?xJzGFYE&M0e@NBPisq$lv!TXeBw{jga|sR}Qkj#(KoPw$;X}a$ zWD5ssbsnwyh0->SH%ea~>q}^U;h_REG;dX~KY(HafJpv}jQUNI#6i<>j^ zxbf0)*p8W1H=D?b1`(LGa~(L+;EG)`Z+fw(<8D{k7#KP9{1|N&I`QTbU7C2pQd|-t z#uABP!B?8|mTlg`-|qqkPkwD%p%$4;_ne54EcxA=bjHVf^faOU(scAr3TyMIU6Nkv zJo=qoIZ8Pl0990HY6Abg8((N6X2);e^J3PI#XLE)7F0<|h>Ue!=WML)(==9Ui?4mT zU?C)er@od_%UIpLM1Q+2k9E>$hC@lTP9pI|S>Hn5A%hUe7dA9Bh~nEBGAPIX2}enX z{%_HgH0!H$Gc%93ZHOg{+h%3oL_I@Yl)Z@=WTzmA$|x9+ltdUD_fiU6FuC3e3zCTd zIsp=<0$=vMSOWoU$Y;;b+j*h88HmvUkRfFUZo`i9yH`A_o15I1|HO>rS{QBxN!B?F zv_vp+jCXH3_J>KsLymQR`CSh827pIw1gf^9xTGW#=2G6dosgBsQQesRi#J#xe}mS( zw}-#cYc6ySn2@Q1h@^_tdHTo@8Re0vkz51YqR zHjZu^KdZW%J)B{ykF3{9YUCXfJnkgJI^5>e7K~n-hw@nnglsF5eLiB(Yg%2tXhM3E zfPt#kDB+lw)9Agr9xkKo9992hhCnQ8fulY#@*d9k!*;Yb^A||@INm`n_p}ILal2WH zmyY|JzLLiI?3BP&$0ucI7e}E>ax_)uv1$Os32KHCacp54C0I2;4jJaGm=Yv%PK_2~ zwQocRnyRcrk>x>V(#})JRk9}=P#VAb%P%^m3n=-Er_ax**2uj5>r}ohaclc>^|!f& z&0XI=1c!%*FWC)jQ0!Cg#x(iBu%U17t`#-!6-ilaoH}&&S}V7MRY#PAJ?sEe*HRQt zO|7Q&2O$^^wN0}3*tH$MzrWsWhNvkdvjr<_CB96UMyFX3Sesc$@nec!HhwgJ>UH9t zZ@`v}(wUU?(|cH{r9{(Ie4XWO{6VAPPDi0!a~pASDi$~OhQ6#qjJGef6@5Z+`CkosJ#??vnh87VSRTCo4p^hZ?^dt0PemoT$u5Vg_g?g{VV|930A{nl;@ClTnsh$7^vkz3V$i?O>*`J90(+4Fs(+ z>#UI_4tq;!^!t7_8XDB((P(~Cq%7ig=eZOXt&yM}jlFP0Dh;+=O2>|QpZ;CXU09?POW*WU;Sdz2jI_GGN5 z8P+r&yt2UhV?}X75W=HbbK#oy%l=hgo5Ek`x^&lPQR%s~)1y{8PdulvS@@PO6c-m4 z1s}Ed?iV&a^o{?9&I+}%J7(|qkc~4@A}BJSzuCH4@2_E8+BL7w-Ov1eG9={NkkKND zw+x`oGDFm)F5RjDaxFMjY7g==*Q8mpvwOOy1f)tm3#%lp;n8NRB;~=c(mJ@irbK7>Su5Xz*6c0pd@8{lt?SIe{NH_7Cfc>pOly0k*vz$V- zcC!-?*s|&lA=`oQAX$PPKWgz7b4)ze! zBS*wjbtww3r{m8b%JJ_?bv%G?*7X)?LI|v{<%ZWl%hy2(G~DM1H>;%f87XPfz(Qea z1VV#t*e?6ds%Eq9x(xG@z1pfJdrKU3IyROBjQ5J`2vGu$O`*DrZ~br|`0PoV9vnRI znDB)*ECDCHbJ_Xi?C64}C4@{&F+5Wd<2-v2f3D2dy1{yZne6VQwCb)Wp6x_CqxhI? zvzh}VM}it}R2?V8g)*3#lBHCyYhBZtR$D&m88Tp)% zJd%nX?~${|uv0T!SzM%CBUAb-q?8(^A)$ICdfeE7;Vaqkrk=m9&-u+d{>)4W8t5H9 zGQYn;kn+E?6m^{4$xv>mKUfV%vRg@F@qJBB=I2kS#>^!5xu|v|GjbnE7;7VI-n8vv z*+QOL1AG5{#<1R7ChuTp2EV`WwB!+^afzmKQ;mt)cb2oTk))Ni8MMw+Uw^u>@s~%U zkkEc+6J`W@<6yl<7LL1kd)~4d%ud!5k&g7TB63g>C~>6iLU zr4v@CfW)Iyf~Mvm#%MGaiWjliXCX<1kCc^BNak7Io0!>-+bq;6Wo!hHd>6V z`4m0kqrCE-{8{+At+ikMQ&M+INQYO{^lUmNU9}RE)9lc3JumT|wfQq#cDDO6Sp-6c7u<-dwo3fJe9M+lg~M=(4Tu)NHrX80HyJWlXk< zurKC32HJ)ROXFvR-I_G>S%~QoVsXZ>awe8JVqB&b#mVB;# z$H~%vWt#e@OLYQ#Rs8*Y*V3}#Y{ra7Ddt%i(<8vO9n$H>=jBH6l6b`Lkz#hLT~j`CQw?-l$Z!CsEU{zG->k ze0{F()=QL?VN?4zCMb(9Z;7p0%VLx63`oTqx_rF9u2gUfpX8;Jm`RqH4lmf(Pa}y5 z0x!zGUc{RAH6sa7jVT;!1axhtzDdv^!ErT(@~SB5_xa^E2EhYuZMnONicMQmXKHsx zQfDSKb}GLy>zI!6il*w3cV-=#T=uJgR80+d<=+_Zx>zlNuYsZM*}|Z^uGL~^}sV8;bpXKhR18nKcfjZ zI@sMjT9U-wt`B0VIZn~;;y6V{Ma5dT6_o7FQ>l@m-e=E&xjF0L@bkvxG#Sw~FHx3y zP;1A?DS#cVO{IukXX*fO%gVS-SbmOK$YFwp8slp&Ts}vN0|krAHTD%zStZu~RZ&f3 zL^;VpNl8hHCTDMh+`8CWD50nT>%C=D(rAxEK`=GlG*4-?FEWQ)o^of}N30?ns+}4- zJZLY#2}=2Hw#yR1mT*&?tlN(2X(Jyx8D!0WYyuKN`)L4OhA+s#m~cKjF)E_y7b!9h z?RLNIFgE=4m<1-Hvw&8w(4Kg3=rKg!n&~&&=N8`Yi-Z88R4bGl<=3jnhydG;GZ&vl z(VxyQ4MX;N;p7vo6LGxNR3ToMi`g^AFs9>lVJ|q}h0&fb}6`Qtoq89PdqPn|i8Dwi9b4*kz z?TOg9absa~%=sc+7?C&Id>pRa+wavTA&QKwvj)9KC7Helk$R?+p@YRwS=Ic=^owX# zOy6co#qED2P)QwEW?jzUS+w?ti|ijj2g6?Hx+|@%)CA2?iU-~L8uJ@yo6wny$4Uty zOnEYd9k_g#&ShMzGv1Lz`gCtP5?kxu)VsSLQ5GMxy|(D6m zc2Jdry0yt@cUEcT8Msy!jjb8kzdEP5Getu8+uJIhhx-33@t7$L#qb+devgq!z+x|| z9bG!%43s^dy;eCWb1$z%BSsxl0Zi z9p1y|c6YJ&x`5^P#temx2_x0YWpi>jSa7~(M0o0t@^sq0F|UQPp@y%3?1oA zbDiMNUdpQ1wH|cx=l!S}l~uOv`C&H^{aM%*!v+#VLieNv*3iCKZrW0-ZD^RDXwCsY zTk8RDSh+H5ikcEO%ATE&Y$nQAKc76`UM4g@#k>OljRkJY#G1w$sN5#IXnj3P9`5tr zvW7-9I015uG};FMW-qo*odD`_H#{IY56#cY_4Orr zuXC3hZj#Eo-^e@v`HL5OLLWYSC~Y5r1yW)yw`GGcYEgpUsMCN>63rptg(B&E6N6dM zWchp=&3+QUdOLQ16tnhU{AgY3cwb~_>$=MJeW-zuwV@_W&vd&!bPk*=<*AN}piO@{ z#4G`%?wym-nrcyU?A%xD@waU3=^zfDI9HAbd>VP-`R?q$W-sbVRV~c8gp^mhS{?h&a25jGK-uhfjYs2uu{A>ugnx!DF9mYTsg?o^enKYwo=y(heQcbB$kj+o)}$J43i>cjSU9n`n;(@G{|h%WAR*lQUi*EkeH8AS6=wg@TMer!mc8wPiORIO6*0mQnj)p7s#ARip z@k}mD=0}!9Ma-f+xB)I5GWl!qIsdLC_s3hCrW-^m0TGxL>08w=w_)LSzyqftsMM#e zgK+3~`O^lzHvu8pGR;Q*ZS3p0r*^kr795UQFhNN(n7oBXRCn&>Blu;>Sky2f9DvoB zALB|Tm}VTtsmn#JdxTTW8z=Ls2VYA{O5*xf2IhMLivv@uq8}lW&m-7f>eH2(BoGGX zj5e!sC`V8W#k+2Bt%%=WNE@9FB4e1PlaABClyS$l=JJK#!8 zlIjh^;jTUyf)ag}F&B=Ynel_SuQucsBJDQTv#2u%DT<1-g?L(acYR>b!kwJBQdwNG zn{;)7439G8(bA~ixYMw7vUx!IayTAp&5HInk7fWE;*oaYT&c6i$2Xo0wpiq zKsdnEg^xor;%bLgYeYO!J`x)$x`IEdM;cjK4|FKcy8E<~eG7~H8DzDF(~nH6!s0|? zm$|ulFz8zF9Z#jkG^R+fL`rUi6%j&f%er3u0 zj0nT8T!mkMpqD+;zVbMnq0gZzcENYA@W=i4 z^Ms6(3!>}UC>5MjX{V4r_}7@j1bZBvxu?dCxC5EcF|UKwu=|0JDn16JOlJZNYY0t^ zh%tsTkTtsa|#O2&41OrXZw{>v1IyPn@;n&4)#R~jCb>)Bs_BDh!hPo8xPhD z#2O2ebA9C^WIR3~zSwIpekdNJI7Lhs1A*0+9>@_x%7U9KQz77hiF3;k2F{$MZ3|g1 z1?$U8J7hRpz#|>7XK{!kBW-jIs%1|eK1#mB)ZJQt@KC6V8{sLSS+?ZbEcTjM8WXI{ z`0iVK8>TUrzgE-zxkS(ILo9?=D5u`Q*vNnfVvR5@0LsF_aoc~~aX#&StIQzMd0oJ! z?9G4M3wz4g7D%3~lu;;QZuu#~&)_={euR@ZgNk@re6_5N*qEfokt0i<`dS0SS-*_6 zqJQ}_m*FK8mWE9BDo+g_{3AHR9oEpuItSa^-G*DuFB}ZvEjXZ5t}>bLA)KsDYy7Ja zFyy^uV*2@UbPxG)tBQ7vKHxh38IC@FR=T_L{1Y_ukZH%X>)7{wErT#3vo(wf>kRNB z5)If^$jhBSchqqRc7@&d1KKK5QLki54>vhe`6ALrlWN^Cr7-vMYGRX-MJP`^(iXSD zO%<}JwYSt+MbW?M8Dk=Q(K;+;5%Yr;Zk%wmH~$zr~rHPIr@N zM2coqBlqIPi^B8BzfaA47^(k6Bl3M4IbHqi=--Bf7Z^Szd*-;7R&|r%*&G+iZ%)p0 zh1WN!QJe@-Rc+zNC9gj6$=>_9CD&1fIdDMJ>2f2x{A?~1d32I-TVRp5aF9HL^3vkJ z+#A(4v+#~~^vL?JHlwugzYxcJW!Y*jQ#;H4{&bS%|R@I<`?ZeJxEO#kE1~MpQ5R4pzUKjVnu}C=lq2kHcX?JVcVDuD-P6RY&;A zH98Wc8aAJsqZt_de{_8bIF)Jt|EZ>$X*WqLibM-0k?bl<2vNvhC`!nlT{V@gMWx8D z)fQQ@w0coaecp`~H5vpUr{HD~Ms#M)Mb2 zp2ozqVn}82!7gB%bc2I2CY}tm4jO+Sn-(Q}>ZnQ6J3Z7VW5|~Y(%XIaa7RgSAd`)S zE$oen9J}^xtHVg0acet**f2{F0pj-RIIKUKYy)DLIh8{<=})$-ZN>3qkpiX}UXqER zt9ZChv8@n^b)a&(abCdANzP+Sh;`GHNYFBdR~ZA)jn4=AvS!ViGZC;>t7^$?o-)5B z5P3E$lC9h(|_nOH5%F*`*Wu?^87g%(i;9mSgWE$ymsb2 zKUN9x>>`s_-(vSf`nH!F%{~esthN06V99%Vae>77;HGSL%$b?iD`n31IZ@?V%S8tY z*t^zTdTWGx>fluj+oB+*Rehl564ugCv^A!#R6XVPT;Ph)jpYHat&^S*#l`#WkNzJm z?J&3A{hfS$4MIp^2y#7adiHD!yy6dc?A*CCDjY5Lr5U~{m%H;?r+CfPiKi5#931wq zv?lDOA;^ocMRwc9$?jva%~M2KNxA^-Po_Z8rQC z2Men&k>p!L&_8@JJhln4Sx63Z>bb+57zx5ky4i!s{IcKfKZqdFrujZQpyCRwna^P>&YL}?`Yo3>?NiUwCFB6NMDUcC zZB+jb){V4Mgu})|N)4sZXadw^k8-}I-MIBiyIBMZJWHxs+Jm>PKGm>k9NLjjaIu=L zX}#AEWrj3c6>;|$!k&AftitYkU`TNF7YNA(hcL>pa^d352me*M#dhaRu-)NRcC}}L zO`W1dC;_5?Yzu`X#KwAG>aJjKc^tJQSHFO3_PqV5xy8Y1zX@%6p+xcA5r4WYlo`Bf zk22@>JwEm3mC?YuSZV9}(IbC5^7YmnmC1D|?IIpJew!zd93x;QKju4 zYkxPH)vjVZ2 zW|NWGIwd$g+0iiBR#-xnrPo(+3luk5sgg(kD?8nSh{7a#iiI*{AzuCM@rlHi=F`V^46838=+ zA{=@5mdYnbr?t1l=`|WyIE{V2mXBS)5Th`H=gC+I67V-)qMavsAtmWxgiz);BxJ-7 zI%C9QbBw+c3vNXhH3P=FCD_W#OKNR0hF>8*_K7$-Dl7vgZ^Elvq7AO_h;B)m-naU7 z#`kaXM|xivuJ+J;u66!AMY2N0q?9K(<<0sw6e^z|(=W!2Kj1h0p{BQ|_%LPQ@ zBmcDk*TGXeK`*YU!g=DrAbLzR$NJ5W&7g#N@%^<2z=8`MAxz9Hlp{urLC+P`ihh8@`g#R=G%h`}4;7Q0QGUKC zS9zQ8^uK`mb3=9`f=%E%%Fvph*7+URx(bby8Us*kAEHR-;s!JT9)W*#a_y)T4$W2QCiciH zzFpRnx(8+2MEd&em%G*8U?^p`+sbY=*)!-;%5DW;>)hxt=@Hn`UI6RKuKvjR{4$R2 z8Z$ZGRM1$bGhA6KhQ6t=R(v0Qv|)$9v9phj@_du-GmxVKWDzqbf`iq@Tr-ms4yyXr z(gz>CY9R*U_Pd5H)?pJmulzcZVmvrjla`aBF!7>0J`*WT4{Y!B!1RZld>^_t1oY+c zbbW9^v0&jOZurid9PyXCd$E)6hM&J#86M*iVw8k`2WK$b_+?X7VbhE;>Tze!Hv;s^ zAM5Fr80wIq2{j##KW8Qx$fJv}Qr_6Xd0X@oUH?>F&8gHd;*41tp^Qq2Q=2nvF>>y2 z@#1aQ9mStbOm~T66p7DrsLckR2I_fy7bIMRRbC4RNN#ylzEj|P$Wgea?>Jv7Y$a*f zV2xNTZP;owC?_t}gLT$5Nzhll@Ec`7pqOAjsn8LSh#6v(L#-lD!69>z+lP2IIbi1) zjCVq$7}PNfD#zR2G+?K_W#34G8n;~A1&3zff zAdFvhM@ef70OZkN;plfo!A86#nz_xyInJ!iJ~f^!yuJViA-)4L=agj%JeUiAhjaP{ zk~uLJs}e>G{zYH+vCDt4zmwL$$HlfN|IDLmDcUr1@T5o)#>8&azOSfT)%r%&u88`r z8-yIs53&hG5c?8bMy|&@_1v~oa2`gw&~(p|)%I=ncZ6Jx@3r=AP%QF@o782`Zx*nk z|Bv{oJYQrAd<^wA8TUREbYffgw&4XZ!eUKZBkfjh>?1)NA4l%7&eR*=CEUWcPn~YY z{qWc{%pi33anqx#@-rX*;b|#V*^hSy!Z;*Znh>iAV8HGBt@qv$1Z`VfkHPcvh10Jq{QV2Yn zimoebwT4{Ckx+$M-##cmaUWMG_z0YM`LMI{ly$5wRfvhVxA_wLZxl~y&7RU9iB(?# z!{rP;eHFI?WaH!oR*8qqFIpFJ1m+>kBFjuuRVUPDBa5K9j#pL(Nutp01+wQ**RMX$ ze7V|3ZQ-qHQ7?K@sbswzN(!acb-WNbHr4v6=7>+G?Wr1z2~`i2R;) z%}J|=yC-hbcaj?n5AHIQ=%!^G<<%@ZJgqCYXq&{!hNty$Pgj?+2Ti-^d#+dcnikO* zbg^)q|B9o9v{4Ze`_T!H(L#?+>s9L4C$?r>SAFl%mJ(*x-XmD0j<7Up{N~xf@n$aG+gT->0S_J=sjtW(nA4cD*~E>R;Hb zX$}_0L*+2NM=}r7Cx$g!UHBqj@Vz!(p8Nb9*P}myvnOb9-nGyT6NucLd+l)VG5-c# z=a{y54w0HSsnJ)Oi#s%L$)FINuEpW9*xipUYqV)dt3(GHYTvCSqd`FDZf6K28)tt$w|Y+QwNWnrZmh_Ui0a zT83QoYw@3NieuE|YlcsW zR+N0ZhzB@t&X~c>Fa(dBhk&V;IsBWbh|pA;!g$M%oWizubkXqlN3l*7+@$~N&DMx& z!VHWZUY?KR8dJ>Mi5DK)FLtd{<>Pc*`CfH`e(pXT@>SzVsN#3bJo$#r_4{Q^(0o98 zS-ALjN^-idnHa^hmOHJ%pXqk3`DpL}xGe7oFgA2nhJIZNf{+ZELz{~JogHRxK{+l1 zBBKy5V#HaogRX^Z8T|HKZgD}wxTBS&37;YhOk4mL#O?LQ#EUSmz>5?XpQtO_dp0=w zT<6yqZD$jTB*PICrOV(3`MwycRKWP=G&PB|gz-r4;o`0RJ?w|S*UD78Cd)1JZ%EV2 zKf=BJEooQGdg$@fyV?rVTRb1io68}kTbVGRS|$g}{L~Yl`?lAwJn-B|V8OgU`Bp7y zdk{z*VXx{_U+j*w?ob!{Ky3I)3@I#^M&H5^cntx?ZjYLk{WFX|S~i7bFk(ODQz+N% zL~KuqB`YXyO1|rse+6&UF<8Sr_GitOX7S4}MeOhl6Ljl(P(sZIxGs0op)6D^N)e&6 zeWA`rg=!{ZTP zFx=*=cys-1{i}8KXhOhyEE@w|tBs+fFsu?Li2%BpcDBhgw<9C`?`Rb$1Q2Z@%J#quOst#@=Om0rpz|8(ei@~g|hdXr{b?HT0}_} zy3)?!>UF7ywUFD~Zd*EEKjB7JQ>R`#puQs$?qod;dhbQyQk~)AG-UM{mjtaLX@r;@ zicPsV#}sNrCp}-`iP=(7eckwl94~3@7v-W)7_@+?M5Sf6`8N!exe<4TI~xES+!0Rg z$~69nBmCGS(=m0>51$T-3OP(zTkWlJM~@;%dfFTP1ehT<7Z|5xC7Nl1O@VOwsk8N8 zvYseGV|K~gYlQT6mcYIq4U&upn{Qlt?&p3+)-{V>!s(qD;C|Q+j59dR?R4bbX!&lvG5=V7K62>BkSd^kqaWG80cY0U?ukPu ziRj?U(>)1$)9tzLJ%^KQD$9Jy;D0Ty_b(Bu+esB9G{BLD=Ur6d<7}QY-fokVl*FfX z8#6Dqq+1s$BK{YTP(T^Jb(=$auUS(?7B-ijv(sc}!ZdNCu1V-!{_PqjuB|?G+xeAe zz`xZ0`2ct}LRtl7N{IOTGiRJ zO{rM1FOwGz5n5+$D3aQ9tkv@0LxiP=x@)rzqTRo(#EV)2?f$`T77IFIV&4~dd42(l z8YQ@p<7hAQPK|hPpy7KRGZ(D%26<4&aAefH3p_db1!{!E6Z`WX1g3uaL~-9cr|8}6 zN44-n8{c5|Y4I|XrjtMpkX>{D=E;4Kq~CY_(bYw!@d0VdS6!%PH~*N~Zn&9Y%_4cG6Hbly(P5j3 zEpy-dvzjF7#zp8e;3<7qG~Hq1khxIqPrW|%%x_~~5Mcva435qgJVwe(OL9S} z5OmB$%D_mYpx{t0O$;|Kl4N2NJqi8b!VN2T4>l*PdV|h#kNuZ}>^*Ri>Qm{^6lNGo z5MGMH%IpQ#&YfOR55M)|&H31gd4UIxyM{?t+D9t@hgyMiq~S$6Zd38LdyuXvO)^Bg zCNXcRPc@i^;Ha9VR&OH^CMlQ#$$D?9vopke#uHjnuuodXMt8hJE_y|@8E^Vl!4SR& z95UD+ulSk`#7h>|VfKfU&P~1}-B4VKA@j-8Ow)U5Q$a+rA0 z^)Q%#T$TVH-T~y=V0L<_Gu6q0OXWc*9$#FjHNrt@CnFpp!Ny?0K>l%jye}D&`!w|u zo9N*u9{DU@$20iBR^pnQ`C+jvd=A%+p?NpdemW>)Z|#BR-J|X~dwlvfgWRd0G(Q3> zgsAULQ)T8l`{4dX5XKfASZWV9*bK#${lK-Wy0Fi#wa6Y;k4o#V;!OXYSJ181N2PM+ zWYnI+XTELjGIDLOWYEy7%Q`qn-0?_AFZLQB#hqigc0F)v2BVLN501ec8yR_u^&|cY zR#ng}oHf-xjnbxjZhJU#;;8>qPY(nD9W}s-Z0n5U$*!tGR7FMQrcz8yOmK4Jc!#V_ z&xXWvn!WImu$TjMQU~KKq@BMW#VmuL_UxFN+Jmb1@8$EEFpbmr9}NS|yib#PTBCp2 zK@iDVA+SjKM49KVKKCGg62lMpcZ)pm1kwT*5aUJdY@=#)PuX4aHAW4um=+yDGF>2}lF z)Gig^0o0o6EV#7SO`K_UbfBDAYuixGOgUGLoSVf93iyR)svd&nt9#B=KGAXv7&p62 z5bhYg3R}p+eB`kF?gjHUC@eZ~@mr=v-j~1*;b2bb4yv+9s*9$EstNrJAf*B^wqHXO z(?h8%MOT6HAO(Ym;5##hQN?$X>e~cEj~M)i2J%!fAJ`bKgr!2SQgecSyyYh_^P^-1 zU0BKZWs}plM8cXPc9lcLwGoJZ4KDyYVEd1eo{hv`iJ0nIVsygqNbRZ$I512UhB$6L z1_qdzcTjG{a+v9??3P8sG+r;AuB?I_4s?`_}EoCM~G(iQnUq5UIsFY=sgUcyL4YgG#WJH{2@ReM;d=!L5ty}-$M?bi8Q7$_#F<)kr4asS(%yKCX z64*%$pmEAN-M^jOw{uZn4)yzhL1*fUp5sqWZI(P*AX#t+KEyI)B1U|%QJDaQZhI(y zootI)Lh@JtVZVVJ9-~f}w?h@Tb{?N6f90pMU}ws*!@_sny0xL&U2Qq`d3Nlc4X~o> zfOq6Gbdg0)!)JSLO3H0dS@xR=@cQxRx>9vl z)1*rScAhiFt>EImeS)+m>4OA#KCu>b2p$~cgnmV4e&hlOH|#cq)~_%SIQ*P3faJre zc0q=YFxLJGzrAj(C^WikkT6;S2^`l3x;!nDD?NM!vrgrXnax~Thyyfn`<_82O1a(K z#&%Cld!r?qi}BIkMa@PSBsSVD9e*FKtG~MtrL%Ea4GUa@%YD_dMlYVhGB6Wkq=c`r zJy#+)b0N64JvWvW9o32N74ACQwCNwD0M0M##2jrHZxgXKxzt$X%hN}jJUA4r)?VM% z(Wm{0^Zu&->&lF35#vO%2ubq<1hY+^pvCi_nYg`^9l-Ti7;O)Rn@k(GQI4D-z7?3n zdci6x=;xb#WmyBPu3rD`(c4JM+NV!#gb^S{j>yQ!D52!Lss)nn?WhtZb0>83`rP#0 z#_w=TtC_fhF#I-r|K%3K`a-2E2K7usw1^x>ZJYSSIjLM%21LmBgw8BDFNhKTI$4=$ zND5}(Z8tQ3K6DuQ&bPkvKrD8NV1!JhFM%V$^Sf6Hz+0vQ_A6Ra+l@9$TB8Sg=4`+5 zy?uqrMtcD`NRgfXm`SLPU+{h5sqfd)%TDxUm+Q0EKtt$K;yO;K&G8jc zX1xW!ruA`@uO~#kvgADoeo_{ym}!#iv$SM25DR0txyQ><^}xl$+={`pCONN@;*L4Q zjJ--hG(2z>d9(RJ(xxMgXSD?flcE*%cRzV@y+C8~W`Zdj<`J8&v=bI1*f)t_0p^nu z5Y51u%2ZiaX3K&t3`DPJr|q?zfx-orJOr#zh=$Q7JN_dX+j(W#VRLHE13mT$2O3~~&ZHt$*gd$|r8*j>%i;dooo^cQvz{*mX=e8N>@aHEB{ z9dg$RlVir&XE`ik3CLmgi`r0q$NGI2&JU(#|5|!T8yjEQPNeSJZ{x9tuD6cZ=`1O6 zBgyihH&z=+IYRvH75mpoNtfW2Ig|Lnd)2myFWwu5NWJ@I&!X{&N;rcJ2z z#hFQhlgJL|SkZ_QAt<@$#uf%ymU}jDd@hWAX4=R`IU+f`C%YeZz;Te{!p@VqOXE}d zrZNedb1~Fr*|w8;*H=ez^~5d!v`_9rJ(9R*3;4iNn7Rv|p(Z7HNK9^Nsn$U7Qq&cA z!!gcj#Wo-HmuZu;ez{iP+vK%wQ}ZRDWeq(bQZoy;Fa29-cb= zlfVO$m1GB3W?V>K?(=i$u9*~FH?h4^*pYHVWbSJhAJ5vy_T+LWm;#YrDABOXzk?>V zQv01Uz-~|VoXA@tYm#u8-&VjNeXU<=B65?^PTo(9i*YzAhD-qD^4z!ATe@rog^LTz z?iFD?{?ZSa`pr}hXzy@0OdE>>cwBK}%QD6}OK%t1X*5q>c)`3S(u)qH&Xnmvx+YDJ zl=HyTYJge1=b8$hnbJI!FuMe=|6S?daEFdzgi2tvWcfiwHl^z>eKDA53kWbQSR}Oz zf+RC7)pZp^E?eEyh2V@>rJYCnywc4B3^;zz8i86Vgb7W3Ie1M5aZOC@C>rgOPb*Fv z3?ffl#*gQ{VXD=a_npnTZa>yzXs-u1p?%?UR92^RdF*~tldyOW8$ax|d^M#$o0JYQ z#vh}Nfjjw>Gde;^}OFfX|vYnH?!t3pV@#>}fxVb-9&LQ$RxnHqc$h%M5M zQV5^33#t0pB!o}NN66xqtL9(orE6y?;fI!QTGx-~fG{->(LIA*D|!|?F*&^&kzPwQ zvhn*k2pvJOJ_Ef2Ob4Tfc2-X@-DBG z$r@4xH976$U-R5`=^lD0NJv1uuv?`Zr!-_)n>05c{kgAjDt-xIW-Q;WA;oPF7FTy? zwpRu72&!jV#g$!W{MP?|go|&LmLz;(*#Jloi5xxEd!jd!w+VY-XF!Aa)0;q%*v@!N zWAr1~$cSR9!hB{XK=%f8uRNlyy)hwcK>j~|WT$p72#BmCZau(8SO{HKd*x!2gPO&X z?r}z&lil^QH|XiL!ShVy+cQFe(z3xEH%-uCLN36nYBavXOz@Wyf}>BLCJ_0QcyPA& zMeV5D|IaYy^>P5#1*y?d4hKE6qUe25h&mp62P$;T0B}6Y+{Cx*oV)mH9wqbeET!#8 zB}jlR)MheS^ymn)Xc8xWp#&or*4Nb3{0OfgN?i?Ntt|9ST#=yg4wRIjWHZ_{1Jnim zH~pk;-A*Y-lb}0iW5jiaRG#f64D7Np&SP&d-@}NDnaI~M*We}d!2>Ii6HyK6?u#vM! z#L!*w7+stG!6*D9w`S7TMawrN_l(b~(~kxFqNu{Ruem+Bu+4+rO1C5-lLsEr?L8e3 zDjgCDqP0p)8&2lCP0B`GBlTCWu3g%7*wHjmep@G+`XT2L*hBYS>DWKoC1n!cNY91`;q1r67YpUV6AT8EU~Jn36U8BT-X z443T4*y6xN6wix@kDyl)msg74{ZC)fvwcCprNMSG7^x}AiWO56CKA}%AZ^=~$wkY4 z<3iFVf3$H~sHKdRO1G14N$}Z*7x0o~gSV=|^kj?pyO}|c7%3+YnL7=k#D3`auxb7u z;f(a1V1^fzWKi}J!37x9>QeI|up?!U9Dvs##-8`^k!-(C&s| z?0nc&A6Krn|z}Mo`CTk;%2`mal1-*dj6 z^6w#}?XHz|?mUPmey;`VTrtw~&K|(8RAxW|@fl+{bt+D51F^oMgF?1e!+kusM)Q~d zPWj03&j%xZG-s?bhudq8{Tst7wBhfod!8bT&B&Unz-u6dAkcoI({a}YA_-q)Ge@Y~`|tI$+2SmZMAHdVLkxA0oNhGO#DKU>V9uWw9E z*>S1hr-sUe6GqQW+**vYG+e37Xu2UaYvHxKOL?%^vBmr z+2gbKg8T?)WJ&FvF>#8e$HJ3$VHe02wu)(M8%E)Nwrcn7jIHu#n=*}6uo*_irjJG~ zGGY)ALxA8NNvYQ8( zxzbVDF2^}|s5Tz9aUb--U}osJUlqS%0z$?OL`yW(S;4LcW)rcc3rNlFj`v73WFM*D zi7_^tFq7s^HTnRiSZ`)CIa2Ab9}Z}Ax69b69sh!*Vvp}R-(KtyHqB2E1zLX6(`ar- zsblDRGKYIJm-EitYK|wz@r(!*NSbUJFRdO7p{`AY_8NzOA1ld?atgAHFtu_yr)mUO zN@7SPJ?A+(cd{|2p1Te?sjd@L8O6B?M&pZZn@DDHrLPkaLM3a*Xb5IyX(Y^as_SOo z#DT$psATlPJ#Xs?Skb;#_ZECo9+ZP}7Zh1wzH5H6Mq$quRyYFKOy4#1m9tp=#|@63 zyE+cO?>>UWze(!@?7v(l+TC1C1qY)GvW%U2r{mCAa&^p@Y;589P$7DgF^D$wNCHuG zr=!y=K9qTbz&u&&CVe=ViQGO+vZd!p=(0rDPA!Cl`}u#|t}ySqI~j5Ay;IKs2;@<^ zu(H)Uw*B;GNkh1K(BEh`CR!gcIA{e+*GtH{9jDxCWp8-nvANPFJPf*Rx&HCIqCY*G z*+Atb5S6mmmQvyF*b$e*^k!w`L7s4s{mXGZ_$cJkY=T}Lqqo0}>~o5G{$_D=ihu@Vcb?%wCh z;N$1yiq@qj{#=%(^hB)&QBN3U~;wnd2|#P2xdf=0#RhH0M#$g+ZjVG%W&&4| zvVBMuY^~9@P|OTkxq<(2;RIkQg7A8rDuUg_b7`0^euJoh{vMLpyMh_gnnQ!bMAOG7 z=!#0Pvbd+{T$S4i&;1Ie|yg0Hg*uhr4rU645)6I7A>JD;zn_KM87FpxK`&^uT#xLqfc9| zN!_=tiElhG1wNXWfN4!0@dzbuDRvemK@Zm^P~AL3NaTfKf=K`(GOz<1 zg@ttV8+CX=s&(oM>FcfvD-{be{qo*OG_h#5sMU#D$cWxTlgP&YBboK&SWvc)y~S6A z4H1~*GhH)qpB+BU7QL~p5fE~yqVZ=!6@BAR^y80-CxLd~*6OBTz!4)D!R0aoFP`C) zZ~d04ou}*;)a`ti@xeKXM$-g(wp5B-ysG?w1`GN!L>=*N^}oqVp}QUcvN9W zdGzS&!HzQJ4kX@x-g{GWMiw}G_NSJg_o79H4W&#%n0&a7jF*iiY-D>+8;sx{iO*@eeP7=RD z!l3#(xfoUE5f9&X?M&i(n+f|ba?H%mk$BK7j9jFCOR1f(e^!56L~|acg#HxZ7nhr3 z)SbsEelr-0gk4Ga$@m1yni<3g(sx*vGOIr$atlV}DjE!bwR4k%Ar8ypP3I-yinbTT zXbC|liNX@2JG-`bg6$kKC%R4ENLob&Ug=GaCV8|`T2Flr*@R}aMkpoA5o}IPNA;f^ z;{dWnhUUbf6>qT6m$7UDZ*n0EYKXa*L&?nZwYm(s)3)Z*^``8g*G}gUpqgm9w7#83 zdBb?l8(*bFwFLmL_Rp4x#{qWhpkxX0FK>Td_u$5fB%6&;k7(4ms5J48yyT=98hNA zGEvi<4^%_+yJQ|xDe`J_o4!x*pK?>2HEfKzR$YM&tt~GX&Q-sTTT}SX=O5@l@DW2_ zAWRQ5ji#GX0(~?b&fL6$Ws~Jlg_)Qz*B_;HH?f!Ths3YhVpAf5k>n%h=LduyT<6IZ z)i8nhCKQ2GFy);PnDup?gXfH%c2~kv7&CY-IWVixO-_B$AG<_mVd07#Yj=f#+h7R| zCQ++AUsA%GxvM3kn+=arOAS(fb26l_E}=+CrAOhmf<$dVT+Dy9S+3FBbDsEX_ta%V z8batf{zIf>QqDk_MyaIxfg2ZJ#y=$+A@~N?w*qA+b7_H@?^3xvi@%Uf>MxRa2;cVs zm@=3m6ism_o{TP+jRhPW9{;{j2p*j;%7<{4C~|<0dG@Z@~%= z1U09QYt7C?=5(5#ujx?8Jtya(sPt2qbwNBbQDpz6AG{{8A!vcUX&`0D-yz4RBtthf z&Eo#~*MD?PLZoKz;0D{f{w`%`^Yq;@vaq#@onWM|`f#(YX%YyaXaOT;Y^u+?$aOT+ z#xvU_;+KsA_4iSSMR??(wWqof?sRyCo0|rN5 zyhQjgr9gD<{EZ~GSrU^6%>f4b0B?QWlSxE1db6|_E=~IWC~Z#sUxtW<4w;w)@*JO@ z9A>7T|8(an9a5gt)S8QHO0D=90^I?J`A_WQcd*LaQ`VNckYcBsKmXedo)Ufn+E#=1 z;@2$Y7+Z=EtIjzxib~SnbHr$JEG1M-+le1n`e@dVvvBus?^R89v^5VB^|u~Zc8+ZG zJw+yOu(KkQH%zKDvuhWxIQK(b-*NkP1K#2>sDg3kn;8!`Jb^j-S?#;)8672_fGYKWm!O)^e5Q_-M+t4IsYptb&DT2VJbg2sC7mx>k;UT9i#xA$5ydFSBOM@BO5)p14j=<0sBr0Y0ag3!N^8n@{; zzLNA4BaeSuNUYg`gekKY{#!7ebin9S_NWZ#0_o$X2_^bR6L&sT3}zyFu8Rv6zk{)5 zRNEJPZyT9iuX?YQtKVNt0Gfz}J>L$h*8NG2wH|wiBP;(r zqN0)*hp@k{CcVv&ZjiZ)+qgoW+qCg3yQP$SbM1?Xt!{>tbIWQXm`3tWO0g5*Cv4aq zp*F67pl<#F!j!%GGg72nTZS1ToqS~1vw;LtIM&7P;*fGR8q%T=0-Z(+PbYClZJsY*Bi_u+?e|~JuvHBt7^44 z4t;kGv_qY;NKx3)**h=1ZEhZ;RZ^dV_C1T1W!-VdDPeB5b1#D4eDv+K3ZP1yLPZ7A zOr-RZlA`x)1h*G=VQVk(8XI-v{pFWwg>XG$62oZI3X{^w)9bs3c=(1gMrDcE%v<<#EV zezdZSXo)d)w=lXo)87q$yQVdNxInV->qolx z4hAAE#e2#cei6gQK3V_bv?3vTw9_D@0`7GxAJYBXN???*9|g%v{j(_DI)Zyw)QnvB z=d)sAGc7Gw_wLmHjztt; zaFvw?_m`;svNCMw+&sMJ+i@QD|5rO6(zPJhnJ}MpBWhYevRsAgjH{7 z^dbrj5ta?`N$>@)W*3=iy@0ivR-4P|Qg7lA0)*rOGRGV3EpDdGbNBxzoQQ6;=+=3V z)pKCLN|vdB@h6H+&BtXr&$qP<6j=|3O&aB`H%|WcVpg!dO}bWdFwrk%`1j4LXP@gU zr;tBNm|EQY3Gwmfi3bM3OaM&~bPe{L?^isUX*p-8MKh$fnO((**x3H3{=u83>>%N5 zC^>LyYq{$9iT^MWMc+<1HAAW06z*3J^ma_^_*I}5-2BQ7pf4sO&7MglPtMZ8DticZ zFN}$*QUm{O8Jc{zO}%ZjVRiqf%fN1H1{OPFZUdH{beZ!)j4ml}cOW$6*B&J8Jrjd+fiI$C9269UcQoQ)?s5ifQIxv{_a@>L zfErEBx>PvtR9AEV&}*JN4YOSai!26+W^1N4Ev2;06;n%kpT@9(8eS83g&;@C`^2O+t=^lXNAWo3NlVTx2a*$`rL<3m3u@F?W{9x zB3;BX#L3(%dpRXpd#-0(_XCTQbl=`t4A z=Cbq;RWV>B*#Cx=>2w(J7wmKCbJ~r_9^&>ulHrLJ5pkx0NA|-ivi#wQc_ZG9*cHL4 z3YRT;C&G3%SsfzR9o!PsjfK_85O&Jmp_5ZVBN(#QHYaH!C0Rqi zqEG$O9^UH`()EH7UobSGbK zi|1>`dtl%HWLB%}S>*>UE%XK=pi!g87=_ZcR(CDj_@xr``ji8pic1;p43D0vh33aa zpxvSq%?TGGzCBLGB-A+%)D-qmhixrMMQqbQYCTFrx)Xl3N~5pC`dqNfp=>pvO|iR< zy?JfJSEgYkj#1@Ug`X=U*%XNy1fm3sheer?+FLXdcg(_R`KHy%Qj^`eGS0&>3}MqY z@qBFQjgt-?DZ>T$y+N^T#gn-~5V#}GYRgUEIM zLph=DUf?IRZTkv8Xr$q8s7oD1;Q|z;aM-hWFG)f`#0&FzNrl2UuM$~e*OTR%3ztZFq_{9e#jR%=eHe z``E-W9u+YCn)7QhoBH+Z#oaEBny-965h_uTY5bGtyZ&db`!$X{uSD0Hp;Xx@dXqT1 z8X6Bqmy6c9&Guru(TjB@WALKZJSst>-}z9-9WDCP57e4B{4e!0TrywXs(4wt{W>_hCkg#1X>@5*z_1%^9zJM}0_ZfW=D853L~p3_ zyy15dA`XWKuof+n>nD11oboK(Ho~ccPzqwQxhagyb4P1HobE;t`h7j?>yySl=BL7o z`-+eU8D@oaK(2coS6JYMx#|s9Oe~@YQNlkR{W0r@Ti-RU;7M;|uftmCpaI;C$*7Qw z@?vsEX=AK~Z_AO-k@l53Us7dmV8CCOJE`E1_2a8Kj(yYC#S$M?pRw-)5kt6)7vj&X z?k2cg5pQ9bc^QKCy~6Z-$Wq>{XQhLHWBB>NvztXPBwx*KReQyrT6d&&Q5Q%C&ke-* z3qekqY~wuDHMvG(QXu=mg$R0_#vt9wwh0CRtxz%o@_>1`z1tgmla!p+={*(ykocbM zM6^UpVm{mHGGEA=J+#fQBXyJwrV>?G+F^4~yb_2(IDv&=A|)8%?hhVMjIzqyvHT0Uj*@mWOa zAckKbv>&CW?nRcA@;eCZqOe$D#@3cKLA9K~z9u`fqU7I<^)n21)ap+|jk|0Eqh=C_ zZbW}w895N`F*%w`bIC5d0M~FORs}ww)zOOd_wwS+{FZdL`ACHe4B2Bn zh$FFOtB>G2TWq|TAMYwSK0O{5;QN&%eWNz8^FXO^Sj#LY0j!X39S;UM1t1qkg zjZc9NAkzaNn1DTF04UW_dTRIiPdO-O(azkoi=^(@&h@drjg4-2N0^j0)8uI$)H(?F z@go*A4h30j6M@;*dWru^UB&bl$-n*E)m&Lzfd%$SMC6p$ztCiVD8FoOZw6pxg1+oB z&os*gz+~r;M(Odje)UiOk%>QSd-k<@-w`q)uwqCDTx6(wAwMsJ*Z*qB+qPl%@q5=H zKAOI=p&0|IZc$d)$=%vT7s2owp=7EobcVrYq46tdvL&()3Ei9SL_XJdwziq$VaaDW z;359iY7ndk*fcDx!()!1Jjwmo@Hxm#??1c#I8Yru*Yq!YzC*tCba3(_3Z!eSuKTd4 z*RIt$zdsB6$C<=2w<&;gnpR;0A$LXW%>i_nKAPaAm-Uu2Wxe_h#9{v1=U9tBlmz-} z9T&%7$HoyUmJb(p6dJ$$$>W&mHH^C~zdlJx#JS8780#0bL4Z#(b>G^#)ZDFZ-{e?+ zNk3Zy3ayT-LtglPhJ>ujr1kq;C&$RpC*%~;S{!~HhY2D^?|Nqf2T=X?*ht8o{DB8k z#aNRzJykUoh|3vKJv+_VS#TaPY$6T=brw#r#!xB_7UU(5-NQ1FO`}p%Q=3v@;3{9M zP}AH*`~s$!4mddECAjIXp6H!Wf+(ipah#gT3L`Jv=DFRiEyY|HH3rMg$>kDHDw>SL z?uZ=^5wZ8>b?H)oKV_Ow7zlmd2)Kig=@%5<R7{@Snk>Yp-*Rj97D6`4K~j^SS)Px6B{{xdEyY93u z?-z{}f#P+0zh&=CoEQH0T>Clj&Fuu|V4Hdu zS(?T7g`SrV7`YX?D%4zEj@iCq_f^!MjE~Th6HWxi0g6<;lGgEa+27ntxfS`p zt#~X}du@NepDdzhzzmmxaG*Pf_6o=$X~>BNZ&)uFZO0E2Z@Q&sPODj!LTTxenP|!p z4%{KwYH4&J!$9YVMaW>O2rn52V2F(@*|K~*PIv9y=;p~~zNBSas>8($Un_=Ynk!$b z2lH9#yzm*|71!o5_D|nKXR}7@I0c)7&{Vb_4f}CS%P5r5bESO!8-X_-4D1HPHXUMo z*+M#8D=#Y}62yy?s3zkK=HVWyAfZ^3NciaowKI^Fsqwx0TW4?maKwuFV0 z&6nqJ=V9l5F9r?0^*H@y(CcI@?j+VKutCUG&itSV2}yfd7%Sa%Q$N?$r#^@7x`{%N zdrGNYo4!WtxcHTK1Sx#Dx_skT(Y}C=Hzuh5g{K1D#slx}F;;4t>UjZuO?MQ>*0si* zKGjHNZ^00}jT?5MHs>? zt@7u_Z;Arg29ZkSIK)-Rdk{*BuZ)*-hikN#$B)9kiI1p{;nXdILL*O~_M>*+Ur&ab zUinfv! z_9yy#p3Qn$7y45>3LE3Vp+0Jn4YWOv6-Mv-Fmp2mT7u8g@%CHY^QhVt`DR#kO2D5+-ZENlc+nx>SDAawXRhf(QxA6Vxey2Dbxe?zD z{?L(C@HoCsV!lE^#F|>_7^CApJJhD1^V{WmmEhyUS;u5qzMr8nBSmi!oNC_@<4O4V z$r@mPJmmEUY=E1-O`WRTo!`bHIX8-8>on1eB&*~F@9#~1NJ3NT#fw~@@9kK(w`(*$X!3SB;{bR7?+LcS=vP0qE!MPg&-1+c z)A6)^^!ppK`-WC{ zj2qfFvS34)g7vKmIr>Z5zIJ(xfy={8jFU1NN8;&+SlpL7V|L<|0E1}z6${))=91Qp zKEg_om3E1)sq zRE+D;;I$G@s%rr;(Su{}1alaS!AT`i0NT8D`bo_-$$+mW#~H{p>v>qpSM3Fo`)AKS z_hl0`X>8!9v89WQE4x-Y_QB`i>fOIi9w4*rtIO8^Nff6fiE4Neo`568j>GSD;1q{g zbfA^-UXaknl@3H797p40h?Ee(0&t%&W~!3+^IOVW){klhlZc+Fz&%CSujxNPZW^pmj(d?hm6L?#rY5wXMZg+b_fcA z;?N66YjP<@Ka}Lf>+wVpS8OC^FvB9wIRB|y%-5{F0QZI}))7}z!laroU5cSV-cT2V z95N9lHbB+J_i*p}B^3VP8K%y6+H-r>6H3RrS@YeryRu^#_j|f;4nM}gc+B(I+e^%E zT^Wu&i*R!~_^LbGk$dxwxAWyyp6uAnygz!WyzDLCp;HReW%q7y)Rpv}4m|w6baYU3 zL7@?!HJ@Bl$bE+nG0LYCYxTIewYLGI%`gK2XJW%{gTD<74Tk}&#sE}}pwi`gQVXuB z^N*U;B|SiX^kekX9bMz?qo?KNJ+YA)>VDg_A~CU2mi6A#ua#vI3Q*_WIZdIQmpO!> z;+faWi*|kDE+RZUHq;Oaw;Y@=#*MBx4s7txRr6NzL#w#uZ(Av=J-z#9v+DEHs+o4Z z6Dv;cxXG)PddR`)nR>mn zvhu-6(5&j#x45F5EW zRJ^BmhiQbzMG^bn4=6%GUo;y2TCIRirg-Y=yv_5^Z=(Nqk{-^Z%$ZuM2M$~U)j4{4 zYQhxAO3+@jAVA;`kuK7SmS6`$<{{dBuuU=OF?=My*2D$>xq5Y+i=a`#Dlu43Ibwf1 zhM}WvZ-dY-%V+i$XDYXzd9&!rku6Q?lnNii{T1QL=sPg?AOPH29>;b_(MRXac zY4;vHc+dspCV89nuQ#iHa`O35y=gDCFPrl=_r{=gW2p9y2M86?>WsslTz!({^GVn$ zkLG-|ag$`Z<;?A(M9LW$MCSbXx-$f05kywU;6RCAy{$;(u;pJunvNryg^ZB`YNhiw z*Pq|jDY`Xi!SD5~H^9v+48p4YB$ci`5m56+zAIQ8IGDd*$Fa)`5_`kF%d+=7FJnqLfMS#%Z$OV|Yy)3@bl3n28c}2%7f2OJ`!@|@6LZrqNOO5r@EP`csL)uc(D&WO&Q+YNvq={p6N_u z0m{*L*NxIvZ~NX(p|EYdjz4E!`y?O8t418Cq)p?>n$kv;LPgJT1O^)o;@LmbR&djE zw{azd^R<=6Q|o5d!^_GX7pE!Ibt&d6Swug|i>|z;z;koaa;xT?4wf^c=1rR^l5K2+ zwmkD%eBt~4Gp#aDjLXW;^#R+dB+D~W>JP=cf9;o+yO3ITlU-9nMw$*a9HGi)!w;ATf555Bt)HUGXHi( zs2r}OazD%{=Qp%$rf{+^ntz_pV6*#)J?Znm6fJhkRSIeg^={03VD;q^>jFh964ae+ z-Jui$^4Sj%#yWQ{vzNAhdT&AM=Y6Z(0~i)uTvgb6p=ol_=88%05RmhN_E|>qV3~H) zP>aYDUDBb}@btYOMG6~q+?DE=yB~U&7(RHzWW{C^H%aWQZ<~Jmbo}RMQ|Zp`MdDDR z1qoV&s;w!#AI_^Dd;B=HgG*D5ALrY-HgJN zRl{VBxPRb@EDh#twDueN5~!N(xL;q4^4r4vOQi?vPR`wx^D^)Wc!KRDKSS(v@j6e{ zzc0WCg3~m$m3i zC8hzfg$;`+kG8C!U(?x(tGw(!JoI|ANA*mb;Ars(rIm1lqBYBopaom<=e~>Ti)W0h z12Vl7ZyNJveZLf+-oAl@lQRa(3IgV%1+AYZ`V<~0C=>-MfL4-RJzw&OtN*k4+y$l-Xw@aDrdwU?i`afNTJw2y z5wCsNp;WIY10#KIH0_DeJ`H3%4nsJv#o5lV=or1c7``g{(w2j8VkOcW~mlo$8sjSCbn(QGR*&E{zfM}GyjHUYy z`=>Vo(0YswTk*=0HI>kCsG(k*5L`l=*<{<4kMXZe#~eo+*Cf@oTPaLj7Xc{eJ2%dw zNdC6vpPkR0YqUF|GQ?hf@ru7sJDuLwPhU4K$U}_5_7PF{*JM;ASQC^RZaae}4&uWL zoT-!oVL8>D2II^MBI)c~G>_tO0!P#A=UeqwDfAY$#vh&RUH$nz%}q(Cifl09Kc40^ z^6gBByX;*R`r8+TJvL$4+dkTh!^xBXRYVGwo?@(jO^nY%cF=W8++_#4}cUPRw z_S@V$v%g=;cCsGrZTwmhbPxbKfXe5BZX6B|4Ah-U2xyJ(&oK9<8hf@?9r>f z*Jrxb!CZ;H#n=^O8m*W8t+5Hxcz)y3O>4Ndj)D4q?1}1w&eh(GSX2jmTP5kI{{aur z_LCanlJm%Er5L=o8i#A#&1}LLuWWvUYQ%?yTk#&7uGRHpPyBm=e{5IgLl_Wq#9rSgXCRQS~&ff-?iF%)vq6bAwnnJiY(*m zNa>~UZ%_uUVnVYSV5!&rzvSE2Y%jd&AKlQaX zQ2w@mkYFGaxBre~&n&caCkMQG;rZV^Ql%^r2g6os+U_>RM@p*szP*)p51;D!Z0KDn zUFiQ!1-Ec>kV=K90dBW4LXofBJ|Se)c8J)IZK2v3M5cFAk7?Lz2q22iD0mUYUCNsB z>a$gPvPHAFDc`U6;6T@Cvtol~GmpxFAN{g;ileQ|QY?ZIwW9{dFL*or0k;o1ME{-p zVVL*wcRssV5$cIv9sBE<64N)Pr?t=4@-%QS7~nRT-O{C`2R-0eAdBL=IAaQ(@fiqz zwiMlbqM__KermL5-hybw-~&mTl!CueSw}<)xrZN8RIEnh_1~TM_bbW#XF89J%6%Da zmJn;+dY5zS$F#;;H}lR*TyO&4b!He$NCW?90aVw2d#{i%EZH??=G!pACmuEm z=VoOwIa0iVfqLvrV}e7$sNJ%i?L(S9fjDfG(?2BN-2N3Wnr%8`PEy892B=+>5al1E z74s>2oXoorBe^w0K2U5Z#830P;+Qe&+5Jqmp+S9>i3tY#(Q9NY##LIjp7maxG;WP# zAZ@EI)Q*K3NvAjlz*44^!z9AA|1BY9-{86v;yL41(5+gk6{rAsvO}1;?cJ;qn{hUs00ov8MVkC}-$QRnV zoKKFViw&bV;T`@h8~mQzYwcVEuVjt|{rX5%pqcwT9o7&;tz=TNXC;{WpqM^ICP;3} z7ifpqKmjQJC#fKz>(YGz2c|f#jGBCxiFY-zF)^=!#=QYqP&?wH06!D}P}q%Z^&)?j z#4q2@g;uhoF`NW|vmRKb0qrfw@9@x|Jo4J1GP?A3r(`Gj;TA6Y@tN#bGTHt8QG$m^ zqW)Ho$SX)w(+OXi@CdJTF}{m=48FXZuvP0p7zOKVK=wL7Ee18KwcpPok=bg8Sx~2rU}C@SkRKE<|KY@Zv|JXa z@!bFtl4df7taTE%+Tme6m?bGi6eJNoj?<>@# z_IEvzYd^RQdtZd#mU4-G_!A_N7U|N%E)h9V4_P*?&uQwlI5ue^l%v=HvM zLAlja_zdUE1+|OWIukEPP+ykVeN*XW|5@C_4BvnPESL(B{-Y$Cd!M!^KL zLX)@tOLoX-hCgJ@6zKuKZh`J6tCBze_L(-IQ;L(d8XMk@O};f`(_O(6DcN=MK*J^) zyjx3;+I4>mGX*yUNt&xXvMbFW)YrYr_xZ7f(aOqY`_1V-?1*NR>o3q|FE&DJoVm#V zwZRXk)vxq-|t23FB#AXs2N6-r(}|$8dIL=EoAJ-l_jP-FP^zJIu!@Wuf-mS>l@r>HYPD`PRrFlc;oumw++uP~Ynl&Z&7qSzw=KHdv8LyVUuO*3Mr#&UA<^|n8*TGqaQ348C3(1S(n>C&f_q-S zayjws+JDZQm&iv5Y{^LFQ0>wLoHc@3UAc#*_nX(qS0JCCO~^(XujA7E)Bh{rzPrt? ztEix`GG=pk;VF4G+B17$prvLElA#jpH+CFch67Om_K_*a(_N?k6!w><5pI0DX@5s* zaRa7)7s48tC-n+&JLN;ZaI4*8_`u$i5 zteQYBps3GMBFNfKzIFQa<+hhtpztD3&+oF=9FQ8zh%428I2&;B%K{Hr1%BWxNCDQ+ z{$7BNK48qUnv9$gLQ{X^+x$vSO3v#81=g!R2LP~wbpQ9yeY;V9-x##W4Wl zEM193PrZOs2J9cQQAd<8?zs-(kd<>hX;s+sPnZn})y}(fStXfY0jaKlkwU6UV6n7M zTzD)R9ufB??v%4P4t}gGCQLY1?pYLlkd~tIX=6hYl4uUW@jqic;0Z2CakJPm1nVsf z_oP_`Hy;5MaOI@=lq#=KdhoWrN`~ZwXq$xj`@?ZO*&XMIe+zjOXMz_#PmFvsQYy<7 z6({I)+_&)DOJvt9khbd4S8m~9OHdxrXdX!f{tji+M`-Cu?oRZocvAZ!?N* zw0I;ovYTX^70c2x!A2ro`|(Nr0g|;RNG#9SWck0bJfx;T8@ZZ%+ujMB(G{*bV0r+* z#zY+vR+s$%snO{#{!j0b-4DY`>zzCFyB|ExgJbM7U0*EX6utG~6VN5~3+qFsL$F{2 zMyitx6zRjhr#E(F@3e%3o?;V>nI(RPpv{c^r;gZ9%lewi#WGL1Tn>?MhQ|Fv?GgSbhZiqi zWF0(m5@`Z}!3gh%EbhHHsPgpbQ&G2XA)GK@*?GKH9h$<5bSeh5Gs%IB0@_~|h^Ycf z4r7A-93XfnIp@Unmgx+As$Vb;+xz~}(&{x!jdfTm%Nd04kpnzA5mY4KV!ur_W`#&vWwjVAl#U-ZNITqWUAn5YqJo)liL|a?i zMM0;;$7ksPf%CEUN2VJYpgpn$l41ua*|hF}j6on^!wEoBfH1a!WrmK~wY<z6bBUgv%21u#f~32maS z@g+&{VIoCHnAcn?qju4?VndcHC)P6;t+wgj!^I6r`&TazLY{A2uh^Na(6q<4zT&*f zsidkp2%u#uYg+?oVhk$nqT}=?n@}kBGGx>9n>`@{jOxhx631?9kfGlVbG|K#sm|ux znPkcTIZ>DTDPfkCms+TUu95pAB!KAEWcV+z!csonFe_bC4uF;+qz6>iT$ahY9g+3u zRLcY}Nr>@(pXpuXY@I)=XP$@thyuQ>+c~^BiSo zg_10CpU)YGQMPaMobSY)=_7R;(AJ#?uxDYks&H=?r*^B3(|CNK)3wPsEF#XJ`m`^U zYd-&q^qM})F`~Zl^ufat2mJyYyms%}`9><7$5>ySo`o?KUj*C6#pj?asc`{^)-jmS zQdYGKsAFLK4kYzr>u5ErIIS4tE5KBdg#3t^c$B4Y92k~I&cIL#LW1{oW`VFZPN#ee z>@1cFtYRbyb5als`jN}{tzcAiQD0#)0^uXTJn#+x_{SbPpmHifT7?dbU2*Ce{o-GGK zUL?Zqv^*g#gxui|Vnkk!z6x8l&NMM0;vS%6_j^?>%nrYNEpkrqZxwm7!-(j##%)y6 z5sBQ|kIewK>cVvRJFp@S5818|u&KcxR`+-rvtahVrH(fqGzR*Vy)w@G!TEuH2&JJy zGY`?&#P1fH2Gfkn0kcC8@a79xlxO3l7Prum_jNwn!%604Z{ygszs&7H0}s?(sXk?wGDi|*YCY|)-K$AE z0gb-r#a@pCFWZ@nW=~1UBJbY+81|^%8hOn7`F862PqAA3-2jdo7XqL-wrYvJ>BYCE z5BvCvF*_h${|cweVx4Rk@Gi-AdQmTVV9fj9KRP7?^^z*CC z3TtM6zgy)|H;TWAl(`uvGjXxB1K0U6=d&*Gj3W97#S+J~!okU@3ZJbarh|AjgRqJ* z1(mN32=j_$y-6$54S^tl(fA0rd~^8=d$x9ARx5q%WB}KqWX2sz`p}3fUz!l||L~Lu zhQGKVk1WCsi)3jaar;{@Og~{-XMP>lYGbJuCLy97cML#8(@gk9t61S@-QAw4rYVPmx3& z`Y+G;ytgnRe{I?D7P|(m_}x1|B)0P-#RY;3dcQ}*P{RYI-KQfYd{>Mz{GeVqu4wqX zV7z?umZw}IzkLiW0GQ=lg}l%U7Oz~S0#cZP#S;UJk>WAS{v94*rTLje02hhjVkE^_ zHAK{v{n7>v|II1rV@aYY|NAIp&dUabpP&4a;;*+gH2O}q)lnPOE{&r#L9d_%R}v37 z$JRd(%#DLjr^i8PTe|||D>D259MKZeQsa|xLY(N{-xTGy_x8&<^*v~3LwDZ6)NQjO@S#34jZ}!LI4&T0PPPsKO zfG@a;O`A+)9-r<3dZ72on?_jNrB!Mok?|1JdP55kC8{1bon@LDhq7t56W4Htnp+wQ zdBFy-U_)edMji^v{&h447|o0wtnVJUqVr%H$s*AG2t1E8haW#}mkNwQY|l{dLWCFc zJ9ol5QWrHJXNF;gU;ticH`Q^UI(4Ha;V>S#BJd?>l~^07*1tdzXI1&zddR8ZPI$u zpPpCx#jDq7MyKI7&2zz@f)9KLLufzLYejH3k)u|ip7}T~un|_bD7Onb^j{lWmkSeg z21=`(Q#>l+H(>z_4*y3UGjqz`zW0r}&0V|s?Zs?z77BGE&IB+Qy@En&{NnaDfCAN6 zgt!Lh)sUJLF$*XpR%i}SoH(HqhXeFyr>I5inox^r%2Wjg(9_N&J~_Dal}M4W3;n!R zft3l6?v6n=Z-0N+*Glb7-nMw@>}pu zl?6J*1&DeBE}NpN>c-A4dGw(AbhbR%K8}Jdm^+_YC0pW?BY<0&aQSs*Rw_Dy=A)8e`<9}0F1 zE)17tg0f-;mv~`&G+=XaDUrN8r_N7jtd0F|xJ9Vwb4Q6gO1_xo5+@g4kM9awUn1S# zNZ&gs+4k*N`R6yBpvQr(%t4!Xf1$9H9|GXw^g7fpgUQhN<$-fd26AmTzQK;q?Ipio zISfe9NZ_1uIYaW`w-hJIgH>7*WP=wo9%TQ&GI{Fq2xA=As+cRm#rIWpk#YtnApHR% zo?ZA(q3oR%J{F0!>P9mWxPb`Ve&zzNEMRBlSW4h1fg2qFY@rCs_hXgxnrXZ7hAusj z@M0DP6V0ntuHwvCk{Ayr&OA7BYMk=4O8Ha5K2sXUBZm{*+dR{#K)Ks0oTp7d8@GZuASJf)2~M>10r zI6NHX{Ch?tyllv&fL-@|nPY^un|(EVxb9vep+2^wWfr{e{$M}UE;bN=%Mqj#RZ2!@PuQftXQ%@{805L=(GWPRGQ?7SDXJ zGcU;$92!q4z=FUmPkp5^Lm?LF3*7$s&7)tSZ9aX#iTS7^$^TO9T&jdF(z-oxj<{F9 zvww`M!&$t!Qw&qkUp1>J{~~?EH-Q@hu%>BWzvPyjNQ2{ zJvNbh3ejWEfexbFZ#)mRPe29BDc3__><2QhaC<9yDOmS*=*>r2AQS*VVWB@7c&>#> zV+4G!;TkVKl=}4dNsJ~73%oWib3`~=c^V4r&d?A`Af-Y3o?- zD@=B%#i{uqbYT0DbW)s@*Qpo&Iq>}E>A$VItu8Hp&`Ip{x^WKfI|ZNO2;~0aQyqs8 zVNIcEox*j(HSK~IphCz*Zka=qIw;W)Cou>z>%D+}F{CPgqI8KU#G)$SEy;f7!ZiKY z_6G58&%N#TXDdGb59{yXj~!6`2++}*dHx?!6C9OLI}pKuAVHOv%fx7Vxu!3&$~D6q zn0i=gV4;0N42j~3X4r>r@Vl%dVH}LD$c&~n=NOSI@1|Pz=28wnIe6se1mcb+d3S+# z;4?$;rg?@Rv;HoUmSS2DnL~#Xz>-9Rwb%q4#1008`9n#AX!QzTZy_`^AkO-kSxICg z8><+Gveis<`u2cs_M=nTYElwu@-A!G#lg5P30;J5dH=8o#e0avBl3UXQR(3{wwzhr zY{lL~r1E?=wd@++%LQ+E9z5(yyUb!(WJhP0F24Y1W+kMavtWU0%ZR1o7iY~u+wgSLZ?T19&5fG?NdwoL&SC8A29 z1fvve=hOgAvKLI7(Z_XI<}@*$7jS0tUhvv(ys))0%!3f*L_&7Dgj(c7K$kq5FVr0R z{gx0PjG!3^-qTO!Z+wwfHbIAK*jQCU50#HlG+saMYzJz-0?GwDDDMD5CM`IQS_N9Hbi!`$ z7IL|%-giUm*0&Aqrg<^f_fSXd!7V}OQN2DuE{hQscA&HKxHGmo3Hn8G7!PG!2iq8n z4zJ5=m!?~tEbC5UR7t_62k%epm*;Rdpk3;X(06Lilf0(-AIK*pO4uD?p?xj3gfQ|b zqW3nBXCq&sEY9s8TR|CB)LI+MMP92=cvGuRb|^q$=Y^y8p+dmLH!mKuZ7=!51YL4S z3LgP5qyPffHd6`>bJDo!O|%qq+qjlc_x$wT*f)Ey=W_vR=HQ|TK>mT*;W~&Y5@D#$ z(2MJV8Lup+3v5Jez$UWxQm{^!c&y}mxrEUqewn)fRlIRPaevm+{#OqGBGtfQa}uRL zr<^8HXZ#oXJ%h5ToCOjV@CeDxcWG8GYvuv#F}EY*Z8}dnNgxF=EEn^FiJAoFD!DSqOOS1zn1vXUhq|H7{!$7ABL zlDba0yw+KIH0@DNlh;+!uXE^K3fe>0UqL60R`NWZ))*?$sB!7FPEa^<@^Ez*$R*j) z0bqjw`>1YCt6HkC`I%Lx9B}r?16K4ENy90w^kxg$$Hq56GjA})!=g=^p*RCFu>yL( z8M5{HzSaVpv$BW(a5P;Sh4GcXr8_m!9&7VkFp;tD8Q&VE=F>p1Z;Wg_vC;zHuj64s zzN?8--`Dc&dS2^Dg(d;~ZK*AS?6j)_T3rIdl|M{B^aB0A9%Ca?)@I|^S(?}V=s zpN%|lIQv3GH`Acm^wF9G#dt5Sow}JU7o0^_f?wxa>O+#=X`&;H570wZCX#(sQEuj(3bt$7+Yb(q9yXH?0Og= z83t5341Ce_{DR#~HLlkHd{_qmt2O*;P~7)5@Z}G|1O$F?Y2h7yK)o>pv;T4+TJ`lbPdgk zAcMXf1;-&pIJW-ioz(=Jt{0=#aiU&sY66aR;jD)Z`8STeNB?D-QxB4?QF&fX6FD(c z=D+T+`0{TP8tN6WAv^Rm``7pEJ>rCRy7=@otOHIdn0*^Cx3d0ldmVnc?!l)Te1|J@ z>l=WG(7EpixBpr}$mfCFNPZ71?3U03Qv^NT^M8;N`zE$6WTN~kZg;9BTWr+|!<<0&gm9nRfSi_^?{=lsg^ny zQt|Ixdi-<=A5Hz{Y1~_GV;1^E|He zPB(LN1HgUe&J5?y{k;P*13zwQ@yk&|cW2B6*#Jh&hyZIhP~fqLWMA0pGT0BYf(anV zk;LB*{h+vWi;()bmi?-agD3}UTUwM$PPe4y*TjNZn>5G4+QPIHz?cbD^s8^@f;+@| z#+kB)3gULucS$Z?1oh|NJoLG@+8H^<_Fc5BS8r_(HD%Rce58WguJ-z36kzK%R4a61 zDhTa`A4PxM1IN--b*Z9z5T+1j{}zvcmgq4lLkSc!KcK+qftSdYqxX|Vq<7l0 zEP2tHOYC+9i2=I2LdpZU_m8rT>$B9Pyw$MT#$M54^YF>DVf*Ipp7fFvfMK#s<;C&+gMBc$8S&@=4)vmszTVn-r{lzK$RfSp=IZoO+&p=3| z;HU@V9DH)SOSKEDL5i4v^U=v84L~dU%$K)w_5nfMfc<3m^V`lq6G|$Mhvv|0%D=i= zAXZ+>Iyn`M*0{Q=0F6BWCkf;S04Eqwc6WO1ZOs{;n5!B02>&uNGEaHXc~pL9L*djv z@_jkz+&K2^43v*rj@bj2p7HCxyR}|jB2%lRheM?QNVbtPbr#TAe(!>MhpiW^8b+!0 zYcUt{y0nb_u@?x-U1>4_8tqKgTzcFJ0UJf@N&EA;7-${d#dP>WA5jAh6~fJ)-O1k` ze|aM)ri1J+u`GOwvXku5mv|1x2<9Q6Y(R@6k3=!RVNb#6pRNKoWIsE?%Gcq#8n<^= zeH1~J1}1;&h}(0W6qhzZU0)0i5-W5u+`D;1H2?U_Vi4BU?ZzR58nynyG`u0I((|~T z(^IYVt<82$)&;(Cg*jalKYtO;<=v?#+5YR}16`e7ZC9%W*d4O6(Xl_5trk=ggXKuj za<4(a2f+MgE|2osop+)ae4($+|Bw6U{Q2S;;xn6WpMBH%YPc8yynf?31%6E2rR|0wdMr2I zgAw0@5dU)^pN_Jnnj0Y*LHoKVZ!g|6c|m`YvLqxh#V%cY4QbXxet^1tC76bUtN`%* zIxsvJw^2yaf&S%oB)#+OXX}nwq!~J-$V=b`xJqSzc(f{4uAK(R>A3IliHUh`7X_6s zitBCf1wi!j{fKMYiwWMmj4P)Rls|H+u4x-RPkNf*csOc5%8vdh0ycpZ0x^VKHe&1n zYOh#I90qN7`=9}h{{hqc2W&HfY=}H!Fxg3F@b>S66S5l`Hu<@P`=JP{Ae8w)&9nD? zXHiZ43c0(=WA-Aj?#z^&fzn4#ndDxU3A0%^2?`D9)yAJ}U@=vsqB=V!2o5-_!9{2+$y7j}{ zOoq2GY+dK$uYE8^OGc(hz5zq`4TM00^hxGcj#e-c8t9YQ+uLzvui-dG@}SlrA#uo5kiDU$K6W9dF$V9P zv!1larx{|7|1lr4`$9qP!}OnTlPm{HXwmc7%g$iv^~qrJ#68&p(?-?a9V$+v;bdR@ z5A`76SN)+~>r?x(3jxN z>cGHbuNTG6IjN%dGoi3K1Yhr1^L#1BZCldK|McPAc>o`u(~ewk3v?@#=hao*4P596 zi|Kgt{zyZ5*<62Hj)JJ$yJrTq0@FTAKt2~q-dbIlNpE9H3HUvFz?pA%6_9@0GpCl4 zO2E{jU1B8C?u<0IbHS=CjAYanX4G_&sq2=A*!w>9AA@1y9ne{yJGNW~%zDNSz=7r3 z6y7i(R41l(o`E-(^_m-4R`Nhni%6-wkv9I;abDiY0G8yGn*}{HUtfvlZj*?mvT?hk zvnrH*F!@{QvbdO-PfDYl_D=+g4b~%HPmES+JU!p$4~eVDzB_$FL^9V1y;cXaB!Ivn z6qw7f)r~!(M(l7;)XOYB_Gc-O8wdXS=m4eYdQ(1yOG)F&1hB2z0X!?I`xtap#)vMW z+v8RkMNi0qLq(SzOF?AjIBfDA;zrrt%O^O2OHf2l_XBb1BSq4B<-cB+$Dd}}P(Si> zS9%Jx8)aIK!lrMofjaM;_i8gr3OsRWqLk0fXJrN36pDFGPwafJQbswOaFS%5*b2Ay zyU0SD&MHwivn-vW&5R2SQ~l*rH21!0F=HS{VXQp(tlVFAJZ%PlQL7@G(1}@WeF9B8 zxY~C5I{e+Rq4&&R7&l*pGk*ETe>xU0Oz?z0*x)AKnpBodYt!c=-h>F$_c@voQQA9W z8jS(Zsus9VR#qko!A`r%L0cQN3PE3lUmJf~m#brx>$rstxcb)9L9zb`)p%*SwJW%2 zBKpg|D;Cziufe$VMO|J#XfXFmrq{7l0rVhA$!F5{l@fe(bt9Q}Hj0Njg=~f1TiSq; zW8f4Qz7GUM`9EA3EfDV${ZRxYU|Rw}sC8Rrt`mNoKG5wid+q~n*Zsn3r5LJ$K{py8 zdM&mafG=?b_NTh~FXPEA0RHL^QSykd??w)-`0Sk!ZDD7jZkP)S9lO7-WK5;F#@l)b&;Csvan z0ygLJqHPiIXC3&DEUp^gRg*$BfzrmkZO4qA9FpVb2092|jJVCucd2-L;+kxkNd6UB zx|8l7nN*(jAdMB?Ly%D&lTomXuR~hVr~IOl@q#tVVNXcY2LyB_fEm~-$_r`gX!kJQ z0!FrYXdG6xDn||!vIy8+;?R3zoPm;5J9d^L-m4_*Xi~fYF@~!t)sJZNQ!3UAAedem zQuG{X@rgfM=B@DM)-oy2g8d)1j<+i{pC{1|AODdUIgmFc!b+hJLNk!=#cyhHFaS6D zyqkvKE(+SMoJ?$Kcn3rA5rh%zf;D!Ae~3qtZxpw}jEXYlYG!_oV? zvCpKQz1`3_HRJ-dyb-|5!2*o(+6Cs3A77hKi=39_>DzA%CW8N|ZFiW*m-~-J^K%9? zLQ;Oc5?7fY*WHr7gA_2iC(-k(I9-Ji{NmkhALV-wpibAP?B-JiOLEU@)BXx>7m&xz ziRxT7|4)a#RWq3b;N6#I) z!go}dC?rk*W6Y(UUy3!$Te@-WVI>pW<*|@48pwX6>#YViQqFUa2F`kfOl}=faDssYm>XGiX z@1NhXbe^!YmOK!&`O|#yaB9;-4;)>)BDj_jP!oRT90xyT&UQM089M#bMbv+oPin@B zGyGU2Xca)?AQptxF4F5Uka|5Yd`^TH!{b>!tW5Gh$>gCnzmT%K{P|9q$HI1`eF;|E zz7oo&x$~>-&lS#knDDbu>pj%z;qxWRk02a~*P}ITXj%Bmqw_`7+ z+Jxg&P^M6TEP_lEEg$c84mV}2)OsC$P&B?C2W_LJ;T=z$)JL#cao zmyU|=5NjUvnKn(~gU+dxW>8a)5}JUMF9FW>NF{wms=j0{L5ibu783pJ_IFauoD@E) z-Mj57r*0e@r>;@zDy~!9wb%eXm%ujbA>j1G!+U<(15SDS`a&V+5S8g+SB4opx*J$z zDO&>!*oNo878&CReNqY7L+gMe`3?W}bGOco zY`u9wb#Lzep$zu-&w$iF+TaUEVb&7`Z2X|8r(yo_rb9$5i&WbI%RIf~> z_m*U&6cPVTdB9ElO9EF$J}(+ZDdfEZB@mmI^2dBcuUGK-yF|k;7!Lcw`UqnrA{L12 z;M3vejSFsIhg~A@CyI+LnL*_Imw1wDz@i@v%zZa6;7ef}K+luoR%nu8L|e}KTs2!= zE6Bc!8mj>(uqgdSI2@XY3Mcn z6*#Brd&G;+856u+o$s?^zHM3K($x@;8P`{P!g&z5GdSAWPZ3^#A3yR|H%%l`=lBtXTFkd9S^8fF9Z^In$ zjA_F-3bAnQ?TrOZzmFAON$TxAsV_6*voNy*+Xbh2i(6BVD=3KtdB8BwjuTA27U-ni zwA!ECEzP6FFsEv8AH>u;n3VY^IA!Q$BLONQWzQeyOWJ`RQh0r-JZ1&2Z>p?ZbWzA9 zCS4YiUnhPa_I~8cM@2=w{9pW1tT+IXo?rwv_)9SLK6z@~7>I`IWKo&re8l;yih7HD zi{JqU1Hpoo@1uHj7=5S*loy)7_Ev&a8t)ghjW0WIduW+l0Ll;-%1b|W5PYBLXT3J< zFjV5KqE8t#3`^bpr_oIh@oxhN20=#9=v6bD=IpyfcBeARJ^%5$NPFS3UAv~SD_}`B zYNn+8qCneIZ3KcBO6$_5Jqzm@6L__{LoAze(@2mbkkkBN^701M1x5n}V5F z%c2zHwwe3ZRvRkhxtap=y#p|kZC(uja5}~Ns!i5(dQ1Ewi_u@i<8FqYCR_8Ct_3O1 z59&G~yVZ!E)Nt5kcET6q(ipVtz8k*gqRddy^$2=Z7OU7a= zE_A?uR%fWZ4%uo*oQWw(i=ixi@vU(WpOe&MPccp%of2f0SVZN?>MpqRj%oM zh2|il3F??y?a;UV^&OGt$8Dcovy(i2BbZ3vm#eIu(J0|*a^vua(8Fn3Mp34yZOl8` z=ZsfaZx2_KdI&!k{!UGnM`H1C-SBfI-*72r?gVq2+qDS{~BYupF(bct=_`KQm-T>xUY3cIk+&0u64(>mXJIBqDb~F zzIVqT(r;XHa*Xw}pU(DVlo9OtkP8-4JvsZAL>hNqnpxbB-&Xjf!-;Ynrej=44=Bk| zOU)~psIyml9E)X661TjG-ohrv+dN~w8b&qr;RJcn`s|jkzlp^|x}}xK;~y@32z^;r zD0M~X^$%gh{~#@CCd$>ZdIL3gwW7c-9fL}E#2CqFnelR@_1@AGZbI=wiz!Q?pA zvw-^J`_1^yXN=`kIS$mzkwPsX_)pE*xP(^#1`z8|Neg0t-oUajc|7^Q1 z$XSkPgZUfCbNM4DnG9+=gB3lJKCGj*#JeeSH(`!O5!oqN>%@m!>cFKOIRaj(Jw&D>LVezW5 z4X4<6F1&dC%K?_9c|!r4j^Sc(rb^Y>MD%C2&O3i(y>&vAjximz;)SZ(x&|Wac`Glq zn{{&=1G*_;-&d?jX``5x-K1P3O9nk|S!(B59a!{1duYT9NNQ+m|J0(FUJ ze13k4%o=zd=G|;H?LwV1Z9b6MG&7dLj4wH~SPO&Ng9drPwxC;EWb$@2mjf%g1UT-} zMKBHvB#kw{38)dN!>1(+t;P^zVG$EK80Ll6+Q97tv9&j}ARPa_XR?3=X?dWxh20ZWQ!#8&y-Y35UX1 zx!N14qhUtQy^l9&EFUg@VfP$AHfZ5t^X&7c?^X4#FtWY9lF7T4Qmr3ColKi#o-@Xb znk3;Q-QKx>IC0fxf17be_&qLZf_2B@1D`Jb@ ze$IH4jOyP^xyD8ptTansc1mu=a9iMMzEXC3*f%) zD!{1NGh9K^T+-q@X>UmLxB#_1;V?k2j9FKTuQlQ@sKwmBC}31#8#Y44*r-Fm*XNW@ zSoSd!hOk8yMkUDt#wEep9#o7QLXF~Mk-E}zt+-H7I?c9d^w7@BINxQr;C$7V{j9sK zwyS}+C2VQryBNQ`YRA`ilTZy8wWVCR`C(_F@LA*&3w_4P#V^I<*He;{9XKhTCR(eE zb#il0mbNS%>%PMwQapqyUm4hZoDW(;y-2@!fib+(kZ`R~=SAgDXLmMo=Sv4z2OqsW zal$<;BBG4*tS7oxt1-}E`%zL`tESq(=gKD|$7~o{KYcQYZ6cnFbtq}8c;ZK;srI&Y z(wTcT+;6ovJ8M|b>9H?OLzCw#rw0M+tb>9c+mk7B7EUfQs+d_}+}pDJGlSI!?PQAD zt&KlAD$(@z_Ktm{Q)_6V@u576lZUsgnI+cAY!NF*zs>E;+dpWkU8tls$JFqwSw3tc z1S54YuWBgS3Xwj;{;u#Ufl;vY8Jz{`VnCuyYB|xhHp7yqf;82KHhSRPy=Wg=vam!V zXfJYIxz0y2j+6BU-=md#$bQ=jQRCX<3f{nfiQdl2PFil=CURI2+&?rQYSVGQ>?uO$l#oVb+MSgE)q}`7b>7bK@BodL$8^0aLTU2*&;Jph(KwMZ`twst z?-eN=yO!|laL(4~|uf`A&)x zVRkreLPh>R+8;t6Qk%1>!l6<_!1`hb@kyC$Pfilaa{Z>=KVgKTJ!oMz9$EC7A!FuJ zWBZhzVBfF1hdEQ6rL3aj&kUhY=B5w*WD%MZXkchxk?5+OOl_ozesi7Y3NHB4?Q@L0 z!IAebsJ-U7eYduI-iBBc^8L8bb4Jzg!qXj=vdTTL!`ir~6WAq9?Bya2nozvK3C={7 znf3bX^{YX7!Q|Eiq4B}sPCV|8xzmG@+n4;*FN@02ln7Ec6`QVlHpxCc4-U8&=T#%* zv0Gh_R%o3EF#%#bD!ZWP(C*9WEb5^|^d>4tI=)5=YLD(OES9cs44KZ=jvL0%#}KPs z8A|0CILzKs+0^Z>=`Bj|XtslGbbiCB#_S$N99!T`1qFA|33Mh)231rMm&cTTd}|K( z{`oQh?T1(%DPQ!TPqo>4~xVs z%;nfrIVmJ#Hly=tO5uK--5!{u7GOP)Sf)4h*o=Kq*FZb2fk2+CFz;eW-{){V2uqg7 zYSfyf{MM|lTJyY62FDEyb=A$r?884UdZo6@YSK_E>bsH}i~<88Xu6^>)~BdAi(DpD zNsWKOR!TL>i=zhM59=u3Sd7UoSR( z1m=CFc<;ojNvfyJEBN2spq7%;06tGRASX!$%*ZNqP4q#nOi zfvs@Z3rUn>!;;o0xx16Ha=KJjt&U|IyMJ{O z51VCCV5Lc8VCpX$?zNEDw@AXnY?FJE!Y(;@zmgb#Rx!Ug_H%@6H z%iULHV!~?~r3MHS#8d?b>IJLJu^s&0@;{|I^lsinef*ScdV8hTIpGXVuNF(V;D^?x z{LM|>;<34GoidTzRszaF>W>s>*;w#3wyB(4Wo6-UiNjZkdpYDjpF){96kTPMbB3B= zRxcO{l%>-1Tr8i-56;zWqNGkzMSg50)ePJPV?IuUee55&%113MR2euve2mb%`To#+E7@LT$Ae6dI0~XOUH66D zC)42(#(5t;s19N{>Mb|09Iot_o8$7&@bI7tr0?PSR`>6|ejLGb6Ta&1w?Dn2YAqi^ zupMDoxli)+DF+4l_dPn?cw4yhGcq~vRX1F43i*u{!CE6`7hQrrocO)|f#2V0=0aj^ z7jiPVt_k|Yc-I>%(y2gzrx9ezSG}KmCdaa@r29M2%MHMcG&$s?WeW(>%e~-Smi*N1 z^ek(v*zhUa5ARB`X7ULZn6F; zIYMlxB=O#YRX8vtuCEe&5{|NB?|(%6o5X#Zn7*fiGxJKOg*roUp(Gzh_0xSFVw(N+;mZ|GCb2JFSy&jU5z| z>4?RAv3jdSh1=5H_Al$tjZ(y8ql30PN6pLQEc zMqZ>6j5}=!LGLyo0$m;>>|p4z{_>*C1=cs4KG52dsB>%lwlzY znZCHY2RNL*?lU_r?hk2N$+ozpuB7r<3_T!a-5F@uWa?{u#0TZ6zFUWi$LG zPZ0DLJ3Bmh+S>Q$e`pw1=RHtymY4L?FtxUO&_r7>Gg0qde6!_44EVWV2u@v+#Rq}5 zHpNQ&dZ+{Yo6pdAJR_sq+mg$s5Cuuj;Oko}n0b&Hms`jZg)lG5x9U;M1=__0%+~D- zV8PYFq-|mFxyhI|=8=g&p$-bqD}|mr&CqAQ7V2DF&uq%-%jo|m8jUYL%Y+V?za8PB z=7J^H>wH0w5U>tP)AGB`>CKeuKw{dvTuFc!>@!Cdqu>94lQn^mXC6*T~>eDE1gr_>l*(fOFoXIT+ z9P>%5GZ(c8+X?!xtLKhFE$_Lc@%4u_h-XHjW$L^xJU07H{ujU~um=Mr}W1;#-guUrlpLJ%%vRlHo#Ihcni|s$7P5Kgp9e`%<>#MJR9O$C z_ZUiwty#T|Jr&s3JFOPPB|}!xX?w-d-Dn=2qRx7)*>D|p7x8EcAskXu*{=1*FuL^HwRDX#Tq&9327&GocLB* zDLNl#ax$f>>zvmCuvJCC48qib$IxHtE3~EyYVn=&KU{pd- zj8xb^5f8ts&2gWY&G|m@8%I<-Z_g8JwU@VZ&}+Cm@Yvhfi39HxI(|j798HdWNAm3h zwH>E_`^tR*l4jp;M!r&4LYAOERsly|6JWi~C2Ms*cwZES_lf#+tXWWWyUTu8ljPB5 zc$D=z00f}LC%<7|(`stH$MdDkU_O`st1D;YFT$IT{~ZWrNtE~WNEL1Wup`x=c7>%C|(eo999_TahLy?6nt z3mM+=uDxwTrrnx^mEzsA6E>>XnZ!kQe_=)SfHP393x8)7C+BA>OiQi3ksI>Y}< zyrA+>oWb4~8H5h@CYe$2QBzRRY>R1wAte*fhm$Cn zhfzu819@=Os0dS(Y`lb?7JEMOEL(Eq8Zw(EjRQvMm zeiaIpYEwmB!ZK5mN9Tm{X;g1IbGOZv=1aVaPV@a0YQ3MaTw1fUwodjfjm*XCi;fIC zbnzv!Cp;gy6&Y0KyiFzeqKkcmmWzxh19u-4G+L-V z+pITt)HawhRN=n-DlSKrE>k00qc@Uqx-ld7;{Vg$n}<``wr|5rL<1qokfKsTWXL=g zDKZtJP-Z3bn0d$@Nhpz-RAkOP4`nQ4l$j;N63aXf;XO`G_j7O0yZxT+eg62qZ~I<< zh_!O9>pF+yJcj+)ck zLuG}NZjeog1ZZ%%y`VncTT7W3Y^1N>XU658FW=CgB$v$Ge2Md&-5r<1r3*t2;e*d) z<2W>zY_r^7-%VG~{Bm8rM3|~vj91>d+u_53<4aR^Nq0Niuw9u&R|MD`%CFcsGw8ad z7i^gqo;l5jZ3fE1;lh2zb&sU%mQFeARn;t&=B7@wi<_;C#%e51;ze-P&&w@u4i?0^SwUbnENlQ`=ovjbt*{*dAck~?>We}s!Sf3IR>wP)< zj)dw5;rAgXZ0D#qU}&$ zoSD?uD~xZd^rxN(d2vkou|KnC3-7^FbROa6H9FOlBCO3-2*COA2)^t4B%;kXoT__l zTN@17I1h`BJJ8;UHohl{>GVCV_L>PvTP zJFrf=Z`Eg#(HSml@2kxUFS|%tV48D$p1jdRzqBLzH$2ZF2L z>@jAmE}MyxXh6oiaf;(=(0e@It`xh{T;c~qiL2{gPt-&gFgyxQvI zZng1YZ1$37y8tD#J15@4AuO)zW$g*Kt(hBA<<$8ZY^DIXQcX3%5KhL>do^*gvi1NR zDsh75qmgeDuUgKf5@CB%9SMVZ!e#kzEeNuED;JIG6O-@FfTEuvzREmcPpA4u)`drM zSNCG8WXt8yZ@QIpCim1Z>P^qHrSk`zO>r9 zp7hw&siR#bP+5LsKglx@Y5HKERK_0!H!(|6zUe;v8CzNP$=)M^@rL!uT{XVSRnR26 zH^~luq1ls)6i(_jcz%V3RKb6v2;#y0|XF@cy+=HY2YH>CL zoY-VzrDEca#`wVZ&E)g)<tv57Z*orVSt2_DPbJMi3 z`P#ur6RAt(u|}F-&83K{cHPzV%sHB9=k3@fk3H_gd3Si!@c7twDZ%HDy{ENuI1cT` zHeb?cuWPQCdB4oNKlI$h^lo2OQ4|P!~}%UXjZW4a?PI`5rN@eCc$@vb@tNVz_}k)tj*t0Qxtgsj_@$18Qqe#583Q&dX3 zDV}x2K7X{QY`w`}f~urLV(@Vr?|3_zsbQrLzGCS(l=6;v?&E{cy1KQAej&IP{5^K6 z4M;bp@De9p+ul)tT5!~m4yT@U`jY3Bcxg$zcB+v{!i95Kl6L`=g;4Y(*rZjw<`q*m zSOoP+rW#+-ayJX1DqrYnDzG4BS z-k;fgT!R{|q!nwuY(|SxDDr`r8c!Sch}KGHJU*_>t$EY-x^{F|$>!;R)I?{Xnk*o8tP1r!%M|l zB=~%z@(n+4>^rT^b`W8UCKh!mYc5HQ`7Wznd^5sE|2@NTnaA|6)v=DXqh9w>lM(>& zB|=PNHY~_gpou!KgtmNG95ArWxBpO!rugg^8 z+7WQvx`*)z`xRRT0w$;CM;J-wW5heIRQabb3B9!;I zUal!!PUG6b?g;&Nk};nX9=mSrblao8Vz@`S>&pX4D!WJ2*|}KeOjVBy`fe-l%Jc|E zqiH&=q4~Ksz;K-WdxpAu+^1y2%cYPuV561X3QLr*lz*^UK|n}+>JzW`U7{La!o$wS zkLymr0M34c67$i@gqgx^0}n0Q)d zpm;HwCihBNWjTLg`4ii`SSof$Llysm7gqYExt1^TfV^`JbwUGQ3@0eixDKy7*3D=l zF|DHc&GoclTMH<$wUbpoC=7mapTnQ~Sl9-o@#*sc$LG2kDL;$9+XYW(=I+gLY(Q{_ zhjCap5`oIs^6@*RkB9-{Io_K5M9R>N^squSR+)3cVw>heRk~2_l4aSSL#nk zL}xdwCrRPt?cp#yQXm(i*gW)BOoC|_^d^S3_=|Y>s*6-IK8Do$sMBG(*x=_W4?QG7 zG_6+nO4K2v)lD}x8bfCa%6I=6BBv1RY1jQ$HzxTShXM68nJrJfMenyr1M`)%ek#xX z`}e1^$yD>ICUUbp0s)0|Lp(gP^up)hU0s75qLCJg<5o_JhkRXsSbZ* zyaLENx5@{Lt2n0jKd^bz!_DRI96Y8yg3AdNx4s`z#FCV_^}DmUZ<3V}DsJw3_62t0 zm)DmW&3f#$oR?13emHF$ihZklOZ?tOlUlyHHj=$l$tu)+aGU>&FL3gtz|n>7J?2Fu z@5%5;lW#_@KkgcHjB3ME@G-ziNzc*N)2yBY#xZ&*eUiPK$kchagPlN(0-+uYE!qv+ zcN5*Zj>od9ghES6#Y&eV~*qP4EVasuc3ZQnc zM2}@LvnjGTZvihZIp{<$PLoT7c@cPu*B+o=X=su;Dvfx!pyP8Km_KIe_yv@W?rEuf zh+_W9r^+<xH{1X4GaT|&hX*0=)-3QLJG?U%R{#k@HrJ*yC|Xr1B9DpI_qpZEOR&{V)0IpB+Kr*Y!nXi3kg`LObS)b2nPw>V+Gv( zKcr9MTv@0~U~6dBhbq`eFUCkRpFEiiFywY3Z>nGPA4pN7jgo}YL~(-L_UeV@+ft;q zM}TE8)Bi&mz|~NdE!#CfmTmeQ-_io;dn7|+6~6WInZ@GoQ9?F|O>MQo8Mrc<-}Lz^ z0ueN*v!%T6BgO1G^qc2PL{Fzi_q-Tzq-@LHw8e00v{m-`0-C;DY8d&4k zA2Tin1i&4Dn?7sSeHQZM^3V#_0@Qqn7!ms2E(7)M6`y^FQz@4lcGF^(eyX(mPz8Z! z@NBWX8f$qrng-yqO8)hrTU1P`c-DQ><*jI_m!_cPJ<6wQsX8+0c;#vQyuo?BgQ

LX)?cEJ3m$ST?Zu>ObttpC_$?1x{=Ux+(HqTC_*7sUkzcAH>N1lMTg z)7wW*!qBtBGEp+L*nE8*|y;KxT8y z0Xo&#o?DsflelI6T$#WA@wnaO^O(FVl!8y5+AjE+dGgKhJ@_LDJBn8;)@*CU z>&DR`Bi6V~5F64)JabU7aMWj*>de${C&DOR_#5r&w!GDEoh!wQKi)EaF>DzIOt95U zW(8X5yiRU_Nk={WaJ;GlrCN4!Bnam9IHkI*&RwIPhsj{)55-gede)!+dVKemwi&Kt zehR7))$qCh`o*;M@&Mnzz68W9B){o7{=CKbhsW~wi!N2`{X0EM*l(5y7L$9t{5$LI zZ#|psA7BZi5o@`w2Yg!Wy^k?oW4G5|IeKJ!(adF#eYBMvtOg;6wBmCb)+WxF!B-{4u z#=!1^iWSH$LF;0h#XlsE{x@@?SK0Ub(|`RSY4Atc=hthPf&2evIgtP1>ihj4#s4t9 z{eEr5ce!*g#u2H@*fU}-9{92}hR?MVKt#^| z1m@zu%2WM)W&XOcZF#O!u)#nsHW+%7|3zu>x8&lFAAT}%Hc=g#BL3S={rK_N-hX#% z|IOmQSTe1RsVhd0^8cnW2A)W~Jv6NRAFN{_cMJJ{Zj33XooVVAK_ALx3N;_<-hHHk zNG^a7vwad)&MEPjnH?3^egCGrgw!_wtbwr~Z_zr@&EMk1)pm2#KvUcKq6d z0r~~;O5P~wYC+_@-uwT@gI4McE2}KfTq~7h6BG`GoW9Ou&=Uw}x(5Z%t#~MoXEs*? zA$UiMdf+m~L;rs~leo@82ScjmL$aRGe>^gXojm++49mYWqW_hp`mZiH=sLYavi}*N z$q?n!s0||~%KLA0ynoilwl?c;`!Hl(sP)zSpKb$&$-3$Rs2%*{%cJi6e|ob0H`e4o zc2EARv-dx}L$ntq&vqABy#yA#ctpqm7z;LF-vi{yOX#9X)_LcpdlzJNTA|r19+5&8 zJ51-{5N`p5e5wH5r3USf95V#+gQz!usT#<})m~kq1dFVhM7>NbLX|GtF8P2z*d` z9D+4LG_cc76C@=Qk@_&SMvVheI~#xxp+^1sGG}4c$7WA5Elhm7_iieLsJ0K!$SO|) z;(&C}$!^Joj`^TGJPc0yQ9yS$iLXzb?y+wY8ZGF*H$--~i+2|4>O~+!KMnxk#o}l< zEby}s*$_=G68MrnIY1}?y=|z+SsO9wr$AT0^}G-AC*+=4ip^9Iro&hcJBdJ}PlJ7* zkOHVbWsWQoTH2GI%^Z=}3w~(k_|A%HKu8BdWTrzJs8i=ns=tfO@47 zp%xBtMQB0zJ^%2;NzT9aZh(9&Ey)-a(ZDe6rC285{%{56!D$c?4%9*j9?c(%E^CH#(s&=sJ0?^!?Nwz-bYg*f14+5j^BVa#yYTi_0r)iqnlhDPB4->;n`6Wf-z=YjceMR< ze_B0fu{;o+EQj9$d78Vs{6Wi4qD}cQ3Vvd;L6~04>@IZijbqEp`6eC+GzT5Rxmy@P zwP0-w2$Y)^@jEDlj&Tr29JOj}k=P8oXWVV@fEBPS=^zH}dIUwYNMaI+xPP165hQWp z1}P~G8mEub+%v#ayU&3NBmD+JUs2~cG;C5o1kSgCDnVEW9?B1^XlXeYcJFB7g*be1 zqDuuhSn^;wGvJpHjSM1l207f1C{;oij#P zeiQS82G$jqtRf2N?FP*EHM~Q2HD1E6xWXbt>FL zibkg1Sm5h~1f(D9gbO!L-4a7Ar;AwXUJOKB5I>(ws5{(4od&SmLTIWA9+}gx@t?ge4rJDVy?nrsuRpiR z6Ej^1yCA!vQRv=G93H9^bJsW9H7&$mcI*+@-{^UY=XOh+{O&fP6MR6Wwk+PveV7u* z?E03^toyUE2v7*y1~k)w>Ta`*{GhTTh^YdJ4tWFqJ$MJH=`Ltl3H@AFT%TblN2LH~{S6Q;+;>Fu4Klt+Dhsl3DamRnY~4T* ze-3&g7Q!9iwDd_6!yJqJaE`lN#O63BXVb^Zy%+ZRU7v>hZ`-swzzrRGtu6*M*WWWY zhSj_V#9-ETr&mCr=?%DYUE;M265z!xjxDRGsPIM`pPv$1rrAwlD8GLxM$EZ&8uF&v zaAcExD?~h3%^P5|iF3_>T63<@9Vl`^R##6*y!mt+xWub5=j_(xgFhzPmjSP}DpF zqeiAZAXE@Jl10}8<%UU*jB9-AN+3+umKf0oIrJUoEFLp{U^IYnIb^Odwk;$A6U8o3 zQ_(vpNMV`&{Qx1Pph;5nKOaAO^c<|*eK5}h^5C#y9Hqe3A_@TaW0e5bxHo<|LEG`v zi*cHUf@oNKNtd1+BQUEyt%AX${geg-I9T~p*4@9g90hsHcgFolXhH@*EJz_) zus!Y|z1y$YcRVF(~#E%P!0dR@4l_diRy7N@pSv-2rE9o*Ym$Ah#R*^c4$Tcwt=&B-3WXY*34J7HrTm{F8D3(06x?eMTe;M#w%kA7#O zSu;tLZT4qgO8~&)30e>1ycRP9@7nQM_vwtjfn=u&Vu*lBNLLmaX6b_kcgqlL*>=Ps zWamWe1sg1U?lLKu+n2b;NMmOb3mJHjwuD42r>UqPPy(iyUgZ4F)KW!3-uJF#i2QNBeXiaDzQ55S9Jsm}; zVCl!MLulN-lSV5j(awrt7t?;U?+U@*d3^Un`;H#jJ1;sz)aTm{eCDmA;aX2%mx?5> z@7!oSuEbR`CoXfwBS^?<(7Sn~s;WvW<0T~2!qdM$%*%@&%bVyX!9*P*v^frGGHBLo zL8sa%>J|e+9W^a2ElE6=tSpM4HkWHj^NkWv)e{gzNMGTs)nL^^ZKF-BBaW^V1YOVF zyMQ@=lwNND*iKBzK7p!VxO^bQBZyBMHB_5}N^VMgM&SSg-N@gnsY-$SMdaq!&F`Zb zjXz3^s{(@y#KXo~jZs6Am?z$_8+S3lY|gR6mSoDQW`Rv$l55&^75J6Zy0Q$@%C4$Z zcOEzt2}K<7?=w;x5c7rm1))e-iQ0!inuOPB0T|d^ait)?4&jfr>hfNrUd5Yv)Kf6` zWYl0%gJ%60R1Wi;i4N~dEv<@*?V>6@%Dvk zuVXxH^mUkG?hWp~d;6FLXf7vA6$}LgnPOG>r-3xG8F|s(20fT2t(@A5)Ltii8ah#w zLh`|2h}Vy7Z>Dbj0Z<7}r8H{FvA%K>wowJ=0?7`^CN$Iy@NM7#9KX`{;>*huT~u@Y7|UynPgFNWTv_KQGyT50A;_tM)GGvFO$=b+XgUqfUYWr$$a%9qj~& z&PAq-W@zSf_AHL5p*<12btK_N4|qU?Ze`coN!_O)z<45mnDj{*Iau88vaMLRwWLju ztXyrt!%_i!QGz98-ew9dl0A3$9{F^TaMD*{)s7KUcRj~#OTeN(75?CZasOe5-WSS4 zKgS%ih3-G<_x+2cY8AH9bAR!V7y!kr%9dILPax3&6VdE)*p;zoB<<7CRg0Rg+oaZn;u+q?6eIwwch#m zg3ctNd(X!Lam*9;tNL*g;6#Rms7_fIwvHmP#x+O>Wcyp#BUQ@m!+N_QPD-G?t-d`E z>9-$y;=G?O&+0%B0rf`Lzv#iQnUDmh-#`Yr$q$g5jFK3gx+sM-=*Mn5P~o}Y2!Tr( z@)>|&$=fzj9a!ojM4@Js1CFt#^>WkjcV)=*LQ-S@FJIIOL`m0s!LClbD&8iY)&v|)`kaPi;Un>s+t?Qhy-ZW!V-h#M1h2^4cu+|JBI>4R*9p7AW%%`JI z_2crBR1%XRNe8Uj3b7rKhX7>Cjm*riA`>X>*u0S{6M&%zk3T`&O&1AfCW@V2KkiO! zlF`^C1kUP$mOnT3w$YIsfH@0_y#i6q+whVCSlD;L%^I6;5?j_{$8u`zBv#lbe`=11 z*SwZFf>F%r?11+YoOiFWKy@C%D};eg5mI@{D18y@$RJJl=|ro( zj(PYH)XCQ%6N$9wpi*ucTZ`g+7zf^)!^QzraGA924H^wb5raGf0W{%QG~ry6*z#M* z7%F|`RC(8{N+dwtSUCpS=2j584R&*fX}y-^sEBeEu{I(JLHeK)U~T8t8MQ2EH7K3e zd>yDlk*82F?zEu!st8R^1-3`DDbyi>;@_b)VWF_znKnGc~rgGalTPN8#1}#XaU|;?%7$< z#r+n5F+foComV|$2y%zbInh8$E*v*%!>~(G{PajJJb;lIZnxI z@={na#@ukO@s6seex5`rI696Dkc{^VW0aTN}-zLJ-DVr(MrJSwB+i zPdAk9qNNpe7fg=lXEtG)j zMy^>8e;j)#i1GoVO?8$}egEQQk2-|Disd&pz)$@Q^qF66{(3^UX9!{?6($HL5|Lz4 zMq6dhG|(oZf?L}tg5U*;&iOXKJp=`=_IZWZx|mN;V2h(m-bOG(vLBpg)~i*1HaL^k zUK3!$2n`HpKdoTbfIzKZ1VwDW#lcCPgf#Bz!8>S39#{p6Mh*2QXLgkYe^ft%eARG~ z8Rq>NxU8`wm5;Ff#2bX&M>RbN-FZ0jUDxehp^ic?Z2JaLpvTsN6Nr=|PEl;<1s`&^ z#6tA03LcE++J+xB`wea`aq*jukOT5qogZEIj~jtrr9EO$iyB*ISA7!)vB`R$?#Y8j z0#BJBayoYXRrRloVQ8Wfg`^<(ubR)b?XRzGKvwJqwcY$NT30XJ_tY^GQE(C0O28SD zK`VG}9EqO;vtAll5z&FsFdHtM#eD}h9Vj{i{PvY-KB45d@D-!MsC+nNZbC}9Zn5Ym z^MT=N!NX`e_E&)4?3!M^X#qTffDn9QH&tEfKOt<|$pwVxA{$Y@Ne$*W)D$4@=!V*R z!7GXOxLJva?9FOljffISnMw?lu4UbTUFPzCiorSD}9c-95T zVXQR>?EZ`!=ptp+-LVO6`>sb2GLX;Zf$U}`UeXC}yPc6Qd&9pc9l{nnuHrjw3Gz}J zVcxZ{aBys_^C($}ihubpUqL?LT5ICiL}MNEO9@ER6Iejj@?j2bSSHnwyrIKJEX%vP zNFl)tsJtu|LFE?hXuOYy(3|ErklF+6nNy6L3kE8d2MISgEDnHBtMC!tN$%M?hT~4R zf`-z-MYv|lV@7&lJ9yV4T}TAq>6&qZPvJ_J7Utfz$E`SF+E=vbZ|_~W3pW%f(KKfT z(S&r?rAPOX0}h8ye#tpF`4hEFhsaS1IJjItt>hzX!U*4|UJWpJC{|$)?~Rn5;xF_# z=@w~c*l_C(A`dSX78vzW&?_p$unf8Jtn!@1wlJKT?knyXo$c(@a0RsMwX4ESTK)B` z1iSW95!F6El!%N*@}4n}j5j%91+CDXz`!UTM};?j&rlL6p^O_Y7kazQ zUeOKeQESarhnBc=*Uo#&@(X12RWft0UN+#Vj|g6e0!i~1dyI7Gr2x;bLEwyx4IXg^ z7*zs@i79_z@U{N5pOE~puz`Y5>A|+$NW6;e=+UF<_|a{D)Ymh?uV_^vid(jKAM6m{ z1IQcwW_22ZXyiff6X>&5k)-Jf^AdpL;zA5(-tGtf)$iqtn+?3BEKV-h$Sa90s-`~* z3Qr#Qw&GRX#TbKa%fX51*jb5inb%j2f zC!e^Z5b#=E znAjI9Xg99uI*P`%W4MI2a3MFK7kEk`3%*vO%@$V1z{VUJf&%@ zhy!O$ZSAAI)@$v`5#6C~6cUkv{7Rk2P(;J)xH3HTnKl6$Vrr8kF*0xc$*~z6{oa z1|-d`sK|QzlE>%;g@tcuMYamGyF-m(Fy^$F+DYx_ts5FeoVhtUy-OW-Fk0y*MZ}xx zy(BxnG>v*<90Hur#nG?IQxpV)#Mu)%g|k}%0ui@wvV~;X`PEn$-MipqjR(IvmMr!X zA(e&NS}(6yS7v#6xvya2o{t&N#mN<>o#8yIep|D(;ZD$dXG}OGU7K}IaoKllv}zvL z$WH%`CDsM>Pupi^EJ^q6J9uje(wcI!2Ye1>OF(FOL7;My<+u7R#)EBWuMehy|Ao-s zfIZg|NZUf!2Ex0B*KPacllpUQe6eb$hlL`>v_AAz#F`9 z`SRtZWhkELJ>SN6nfU&k;V>wEh(E;Pa39woHxfuLzM>-|vIw+jwUE2Ndfma^UPehN zP1BnNM->^h4gSD@L&Z+}Yl1ySis2P1oH`>#nzlb9a z`8Z1^z?%Oce>!=1|Ss;fO3rVCL|;vGk8`&pyxXJTMv3Z z<2>y4RWQmM0Gyyl(UqYXKmS26Z5Nb5DqwA*7h2aJHT;;3sGrc>3V*F)dk$e?VLct4 zIP281GI6kY?06`~S3wST;uN~1W@{5Wxpdz+^K!D;}c4CYZn_`S89i)_U zUv7UmpLvsM`SvGZ;_Mq@B_x)>NgKPTudfdkUyUEbYx$hw=j8-OfZioIfKNc67UJUx zW^{=P?ejnN;Tk$28_WQ(+DO~)fbjH(@^hQwb{A9c2|2)niuPFi=xKW62DQ@1d3kw= z!!ln#dH=jGLaW1ek;j#SzsNAiB&d(%&p=D>rs zwYB(^6!uL=WfQn{7ntK&&?Tq^2V)Sl=tFFJdU}Q+acKogZe2ge-|#15aV7(FxH|4Z z288{IV-`&LF&-tT7*c~k<`huC+45Eq6lf{O`!w!$bk@Xx>5zGWu;e}RwE{mVJ@e}CH^Bl!0J euaU4SEhn&(G4~wH5f4HmAbUYkI{mzk*Z%@0(`@Si literal 0 HcmV?d00001 diff --git a/example/1d-linear-convection/weno3/python/05/flux.py b/example/1d-linear-convection/weno3/python/05/flux.py new file mode 100644 index 000000000..feaa723af --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05/flux.py @@ -0,0 +1,61 @@ +from abc import ABC, abstractmethod + +# ---------------------- 1. 抽象通量计算基类(统一接口) ---------------------- +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass + +# ---------------------- 2. 具体通量计算子类(隔离不同格式) ---------------------- +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + +# ---------------------- 3. 通量计算器工厂(统一创建逻辑) ---------------------- +class FluxCalculatorFactory: + @staticmethod + def create(cfd): + """根据配置创建通量计算器实例""" + flux_type = cfd.config.flux_type + flux_mapping = { + "rusanov": RusanovFluxCalculator, + "engquist-osher": EngquistOsherFluxCalculator, + # 新增通量格式只需加键值对:2: LaxWendroffFluxCalculator + } + if flux_type not in flux_mapping: + raise ValueError(f"不支持的通量类型:{flux_type}(可选:{list(flux_mapping.keys())})") + return flux_mapping[flux_type](cfd) diff --git a/example/1d-linear-convection/weno3/python/05/initial_condition.py b/example/1d-linear-convection/weno3/python/05/initial_condition.py new file mode 100644 index 000000000..166b7dbde --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05/initial_condition.py @@ -0,0 +1,81 @@ +# initial_condition.py +import numpy as np +from abc import ABC, abstractmethod + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现 ---------------------- +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + + +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = self.config.get("domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + + +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = self.config.get("pulse_center", 0.5) + width = self.config.get("pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + + +# ---------------------- 3. 初始条件工厂 ---------------------- +class InitialConditionFactory: + _registry = { + 'step': StepFunctionIC, + 'sin': SineWaveIC, + 'gaussian': GaussianPulseIC, + } + + @classmethod + def create(cls, ic_type, config): + if ic_type not in cls._registry: + raise ValueError(f"未知的初始条件类型: {ic_type}(支持: {list(cls._registry.keys())})") + return cls._registry[ic_type](config) + + @classmethod + def register(cls, name, ic_class): + cls._registry[name] = ic_class \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05/mesh.py b/example/1d-linear-convection/weno3/python/05/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05/plotter.py b/example/1d-linear-convection/weno3/python/05/plotter.py new file mode 100644 index 000000000..9f1a414fd --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05/plotter.py @@ -0,0 +1,107 @@ +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python/05/reconstructor/__init__.py new file mode 100644 index 000000000..fa17547a7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 可选择性导出具体类(通常不需要) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05/reconstructor/base.py b/example/1d-linear-convection/weno3/python/05/reconstructor/base.py new file mode 100644 index 000000000..3cb4763dd --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def reconstruct(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05/reconstructor/eno.py b/example/1d-linear-convection/weno3/python/05/reconstructor/eno.py new file mode 100644 index 000000000..e087fe578 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05/reconstructor/eno.py @@ -0,0 +1,88 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor # 👈 正确导入基类 + + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 3. ENO 重构器 ---------------------- +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def reconstruct(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05/reconstructor/factory.py b/example/1d-linear-convection/weno3/python/05/reconstructor/factory.py new file mode 100644 index 000000000..bf5795bcf --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05/reconstructor/factory.py @@ -0,0 +1,35 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor + +class ReconstructorFactory: + _schemes = { + "eno": EnoReconstructor, + "weno3": Weno3Reconstructor, # ← 注意:这里用 "weno3" + # "weno5": Weno5Reconstructor, # 未来扩展 + } + + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + # ✅ 关键:将 "weno" 自动映射为 "weno3", "weno5" 等 + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 检查是否支持 + if scheme not in ReconstructorFactory._schemes: + supported = list(ReconstructorFactory._schemes.keys()) + raise ValueError(f"不支持的重建格式:'{scheme}'(支持:{supported})") + + recon_cls = ReconstructorFactory._schemes[scheme] + + # 根据 scheme 类型创建实例 + if scheme.startswith("eno"): + return recon_cls(order, domain.ntcells) + elif scheme.startswith("weno"): + return recon_cls() # WENO 类通常无参 + # 可扩展 elif... diff --git a/example/1d-linear-convection/weno3/python/05/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python/05/reconstructor/weno3.py new file mode 100644 index 000000000..035c25b61 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05/reconstructor/weno3.py @@ -0,0 +1,56 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor + +class Weno3Reconstructor(Reconstructor): + def reconstruct(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + """使用左偏 stencil (v1,v2,v3) 重建界面值(对应 u_{i+1/2}^-)""" + eps = 1e-6 + beta0 = (v3 - v2)**2 # smoothness indicator for stencil [v2, v3] + beta1 = (v2 - v1)**2 # smoothness indicator for stencil [v1, v2] + + d0, d1 = 2/3, 1/3 # optimal linear weights (for right value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 0.5 * v2 + 0.5 * v3 # reconstruction from [v2, v3] + q1 = -0.5 * v1 + 1.5 * v2 # reconstruction from [v1, v2] + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + """使用右偏 stencil (v1,v2,v3) 重建界面值(对应 u_{i+1/2}^+)""" + eps = 1e-6 + beta0 = (v3 - v2)**2 + beta1 = (v2 - v1)**2 + + d0, d1 = 1/3, 2/3 # optimal linear weights (for left value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 1.5 * v2 - 0.5 * v3 # from [v2, v3] + q1 = 0.5 * v1 + 0.5 * v2 # from [v1, v2] + return w0 * q0 + w1 * q1 diff --git a/example/1d-linear-convection/weno3/python/05/residual.py b/example/1d-linear-convection/weno3/python/05/residual.py new file mode 100644 index 000000000..b4d4d7dcc --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux import FluxCalculatorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + self.reconstructor = self.cfd.reconstructor + + # 初始化通量计算器(工厂创建) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._reconstruct() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _reconstruct(self): + """私有方法:界面值重建""" + self.reconstructor.reconstruct(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05/run_eno_weno.py b/example/1d-linear-convection/weno3/python/05/run_eno_weno.py new file mode 100644 index 000000000..fd7f38744 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05/run_eno_weno.py @@ -0,0 +1,52 @@ +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + #mesh = Mesh(ncells=100, L=2.0) + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行ENO3求解(使用你的链式调用) + print("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + config_eno3.with_reconstruction("eno", 3) # 显式指定3阶(也可省略,ENO默认3阶) + # 可选:覆盖默认值(如dt) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + cfd_eno3.run() # 求解并生成result字典 + + # 可选:快速验证ENO3结果 + # plotter.plot_quick(cfd_eno3.result, title="ENO3 Quick Check") + + # 3. 配置并运行WENO3求解(注意:WENO默认5阶,这里显式指定3阶) + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) # 显式指定3阶(默认是5阶) + # 可选:覆盖默认值 + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + cfd_weno3.run() + + # 4. 可选:保存结果(供离线绘图) + # cfd_eno3.save_result("eno3_result.npz") + # cfd_weno3.save_result("weno3_result.npz") + + # 5. 绘制ENO/WENO对比图 + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" # 可选:保存图片 + ) + +if __name__ == "__main__": + # 主程序入口 + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05/solution.py b/example/1d-linear-convection/weno3/python/05/solution.py new file mode 100644 index 000000000..92a46d3f4 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config.ic_type, config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05/solver.py b/example/1d-linear-convection/weno3/python/05/solver.py new file mode 100644 index 000000000..b2024d2c1 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05/solver.py @@ -0,0 +1,90 @@ +import numpy as np +import matplotlib.pyplot as plt +import inflect +from abc import ABC, abstractmethod + +# Flux +from flux import InviscidFluxCalculator, RusanovFluxCalculator, EngquistOsherFluxCalculator, FluxCalculatorFactory + +# Boundary +from boundary import BoundaryCondition, PeriodicBoundary, DirichletBoundary, NeumannBoundary, BoundaryConditionFactory + +# Time integration +from time_integration import TimeIntegrator, RK1Integrator, RK2Integrator, RK3Integrator, TimeIntegratorFactory + +# Mesh 👈 新增这一行 +from mesh import Mesh + +#from reconstructor import Reconstructor, EnoReconstructor, WenoReconstructor, ReconstructorFactory +from reconstructor import ReconstructorFactory + +from initial_condition import InitialCondition, StepFunctionIC, SineWaveIC, GaussianPulseIC, InitialConditionFactory + +from domain import Domain +from solution import Solution # 👈 新增 + +from config import CfdConfig +from residual import ResidualCalculator + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, config, mesh): + self.config = config + self.domain = Domain(config, mesh) + self.solution = Solution(config, self.domain) + self.reconstructor = ReconstructorFactory.create(config, self.domain) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + def exact_solution(self): + """通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界""" + x = self.domain.mesh.xcc + T = self.config.final_time + c = self.config.wave_speed + L = self.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = InitialConditionFactory.create(self.config.ic_type, self.config) + return ic.evaluate_at(x_shifted) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + #runge_kutta(self) + self.integrator.step(dt) + t += dt + config.dt = dt_old + + # 整理标准化结果 + u_numerical = self.solution.u[self.domain.ist:self.domain.ied].copy() + self.result = { + "x": domain.mesh.xcc, + "numerical": u_numerical, + "analytical": self.exact_solution(), + "config": { + "scheme": self.config.recon_scheme, + "order": self.config.spatial_order, + "rk_order": self.config.rk_order, + "final_time": self.config.final_time + } + } + + return u_numerical diff --git a/example/1d-linear-convection/weno3/python/05/time_integration.py b/example/1d-linear-convection/weno3/python/05/time_integration.py new file mode 100644 index 000000000..54dc42771 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05/time_integration.py @@ -0,0 +1,111 @@ +# time_integration.py +from abc import ABC, abstractmethod + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = cfd.residual_calculator + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(复用公共逻辑) ---------------------- +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +# ---------------------- 3. 时间推进器工厂(统一创建逻辑) ---------------------- +class TimeIntegratorFactory: + """时间推进器工厂:根据配置创建对应RK实例""" + @staticmethod + def create(cfd): + rk_order = cfd.config.rk_order + integrator_mapping = { + 1: RK1Integrator, + 2: RK2Integrator, + 3: RK3Integrator, + # 新增RK4只需:4: RK4Integrator + } + if rk_order not in integrator_mapping: + raise ValueError(f"不支持的RK阶数:{rk_order}(可选:{list(integrator_mapping.keys())})") + return integrator_mapping[rk_order](cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05a/boundary.py b/example/1d-linear-convection/weno3/python/05a/boundary.py new file mode 100644 index 000000000..ae3fe1d97 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05a/boundary.py @@ -0,0 +1,136 @@ +from abc import ABC, abstractmethod + +# ---------------------- 导入注册系统 ---------------------- +try: + # 首先尝试从新的核心位置导入 + from cfd_core.registry import ComponentRegistry, register_component +except ImportError: + # 如果新的核心模块还不存在,使用我们之前定义的简化版本 + # 这将确保代码立即可用,未来再统一迁移到核心模块 + import sys + import os + # 添加当前目录,以便找到可能在同一文件夹的 registry.py + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + try: + from registry import ComponentRegistry, register_component + print("[boundary] 使用本地 registry.py 中的注册系统") + except ImportError: + # 如果完全找不到,提供一个极简定义防止报错(仅用于过渡) + print("[boundary] 警告: 未找到注册系统,使用极简回退方案") + class ComponentRegistry: + _registries = {} + @classmethod + def register(cls, *args, **kwargs): pass + @classmethod + def create(cls, *args, **kwargs): + # 这是一个非常基本的回退,仅用于演示 + # 在实际替换前,您应确保 registry.py 存在 + raise RuntimeError("注册系统未正确初始化。请确保 registry.py 在 Python 路径中。") + def register_component(*args, **kwargs): + def dummy_decorator(cls): + return cls + return dummy_decorator + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界(进口)固定值(从配置读取) + left_value = self.config.get("left_boundary_value", 1.0) + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # 右边界(出口)固定值(从配置读取) + right_value = self.config.get("right_boundary_value", 2.0) + for ig in range(nghosts): + u[ied + ig] = right_value + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + +# ---------------------- 新的边界条件工厂(使用注册系统) ---------------------- +class BoundaryConditionFactory: + """边界条件工厂:根据配置创建对应边界条件实例(新版本,使用注册系统)""" + + @staticmethod + def create(cfd): + """ + 使用注册系统创建边界条件实例。 + 保持与旧版本完全相同的接口,实现无缝替换。 + """ + # 从配置读取边界类型 + bc_type = cfd.config.boundary_type.lower() + + try: + # 使用注册系统创建实例 + # ComponentRegistry.create(类别, 名称, 传递给构造函数的参数) + bc_instance = ComponentRegistry.create('boundary', bc_type, cfd) + return bc_instance + except (ValueError, RuntimeError) as e: + # 增强错误信息,列出可用的选项 + available = [] + try: + # 尝试从注册表获取可用列表 + all_comps = ComponentRegistry.list_all() + available = all_comps.get('boundary', []) + except: + # 如果注册表不可用,使用硬编码列表作为后备 + available = ['periodic', 'dirichlet', 'neumann'] + + raise ValueError( + f"不支持的边界类型:'{bc_type}'\n" + f"可用类型:{available}\n" + f"原始错误:{e}" + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05a/config.py b/example/1d-linear-convection/weno3/python/05a/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05a/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05a/domain.py b/example/1d-linear-convection/weno3/python/05a/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05a/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05a/flux.py b/example/1d-linear-convection/weno3/python/05a/flux.py new file mode 100644 index 000000000..feaa723af --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05a/flux.py @@ -0,0 +1,61 @@ +from abc import ABC, abstractmethod + +# ---------------------- 1. 抽象通量计算基类(统一接口) ---------------------- +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass + +# ---------------------- 2. 具体通量计算子类(隔离不同格式) ---------------------- +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + +# ---------------------- 3. 通量计算器工厂(统一创建逻辑) ---------------------- +class FluxCalculatorFactory: + @staticmethod + def create(cfd): + """根据配置创建通量计算器实例""" + flux_type = cfd.config.flux_type + flux_mapping = { + "rusanov": RusanovFluxCalculator, + "engquist-osher": EngquistOsherFluxCalculator, + # 新增通量格式只需加键值对:2: LaxWendroffFluxCalculator + } + if flux_type not in flux_mapping: + raise ValueError(f"不支持的通量类型:{flux_type}(可选:{list(flux_mapping.keys())})") + return flux_mapping[flux_type](cfd) diff --git a/example/1d-linear-convection/weno3/python/05a/initial_condition.py b/example/1d-linear-convection/weno3/python/05a/initial_condition.py new file mode 100644 index 000000000..166b7dbde --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05a/initial_condition.py @@ -0,0 +1,81 @@ +# initial_condition.py +import numpy as np +from abc import ABC, abstractmethod + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现 ---------------------- +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + + +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = self.config.get("domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + + +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = self.config.get("pulse_center", 0.5) + width = self.config.get("pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + + +# ---------------------- 3. 初始条件工厂 ---------------------- +class InitialConditionFactory: + _registry = { + 'step': StepFunctionIC, + 'sin': SineWaveIC, + 'gaussian': GaussianPulseIC, + } + + @classmethod + def create(cls, ic_type, config): + if ic_type not in cls._registry: + raise ValueError(f"未知的初始条件类型: {ic_type}(支持: {list(cls._registry.keys())})") + return cls._registry[ic_type](config) + + @classmethod + def register(cls, name, ic_class): + cls._registry[name] = ic_class \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05a/mesh.py b/example/1d-linear-convection/weno3/python/05a/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05a/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05a/plotter.py b/example/1d-linear-convection/weno3/python/05a/plotter.py new file mode 100644 index 000000000..9f1a414fd --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05a/plotter.py @@ -0,0 +1,107 @@ +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05a/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python/05a/reconstructor/__init__.py new file mode 100644 index 000000000..fa17547a7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05a/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 可选择性导出具体类(通常不需要) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05a/reconstructor/base.py b/example/1d-linear-convection/weno3/python/05a/reconstructor/base.py new file mode 100644 index 000000000..3cb4763dd --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05a/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def reconstruct(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05a/reconstructor/eno.py b/example/1d-linear-convection/weno3/python/05a/reconstructor/eno.py new file mode 100644 index 000000000..e087fe578 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05a/reconstructor/eno.py @@ -0,0 +1,88 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor # 👈 正确导入基类 + + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 3. ENO 重构器 ---------------------- +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def reconstruct(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05a/reconstructor/factory.py b/example/1d-linear-convection/weno3/python/05a/reconstructor/factory.py new file mode 100644 index 000000000..bf5795bcf --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05a/reconstructor/factory.py @@ -0,0 +1,35 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor + +class ReconstructorFactory: + _schemes = { + "eno": EnoReconstructor, + "weno3": Weno3Reconstructor, # ← 注意:这里用 "weno3" + # "weno5": Weno5Reconstructor, # 未来扩展 + } + + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + # ✅ 关键:将 "weno" 自动映射为 "weno3", "weno5" 等 + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 检查是否支持 + if scheme not in ReconstructorFactory._schemes: + supported = list(ReconstructorFactory._schemes.keys()) + raise ValueError(f"不支持的重建格式:'{scheme}'(支持:{supported})") + + recon_cls = ReconstructorFactory._schemes[scheme] + + # 根据 scheme 类型创建实例 + if scheme.startswith("eno"): + return recon_cls(order, domain.ntcells) + elif scheme.startswith("weno"): + return recon_cls() # WENO 类通常无参 + # 可扩展 elif... diff --git a/example/1d-linear-convection/weno3/python/05a/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python/05a/reconstructor/weno3.py new file mode 100644 index 000000000..cf12db862 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05a/reconstructor/weno3.py @@ -0,0 +1,86 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor + +class Weno3Reconstructor(Reconstructor): + def reconstruct(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + """ + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 # smoothness indicator for stencil [v2, v3] + beta1 = (v2 - v1)**2 # smoothness indicator for stencil [v1, v2] + + d0, d1 = 2/3, 1/3 # optimal linear weights (for right value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 0.5 * v2 + 0.5 * v3 # reconstruction from [v2, v3] + q1 = -0.5 * v1 + 1.5 * v2 # reconstruction from [v1, v2] + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 + beta1 = (v2 - v1)**2 + + d0, d1 = 1/3, 2/3 # optimal linear weights (for left value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 1.5 * v2 - 0.5 * v3 # from [v2, v3] + q1 = 0.5 * v1 + 0.5 * v2 # from [v1, v2] + return w0 * q0 + w1 * q1 + """ diff --git a/example/1d-linear-convection/weno3/python/05a/registry.py b/example/1d-linear-convection/weno3/python/05a/registry.py new file mode 100644 index 000000000..f5bd068a3 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05a/registry.py @@ -0,0 +1,56 @@ +""" +CFD组件注册系统核心 +完全独立,不依赖任何现有代码 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表 - 替代硬编码工厂""" + + # 存储所有注册的组件 {类别: {名称: 类}} + _registries: Dict[str, Dict[str, Type]] = {} + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册一个组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + cls._registries[category][name] = component_class + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取已注册的组件类""" + if category not in cls._registries: + raise ValueError(f"❌ 未知类别: {category}") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower().replace('boundary', '').replace('flux', '') + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05a/residual.py b/example/1d-linear-convection/weno3/python/05a/residual.py new file mode 100644 index 000000000..b4d4d7dcc --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05a/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux import FluxCalculatorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + self.reconstructor = self.cfd.reconstructor + + # 初始化通量计算器(工厂创建) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._reconstruct() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _reconstruct(self): + """私有方法:界面值重建""" + self.reconstructor.reconstruct(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05a/run_eno_weno.py b/example/1d-linear-convection/weno3/python/05a/run_eno_weno.py new file mode 100644 index 000000000..fd7f38744 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05a/run_eno_weno.py @@ -0,0 +1,52 @@ +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + #mesh = Mesh(ncells=100, L=2.0) + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行ENO3求解(使用你的链式调用) + print("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + config_eno3.with_reconstruction("eno", 3) # 显式指定3阶(也可省略,ENO默认3阶) + # 可选:覆盖默认值(如dt) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + cfd_eno3.run() # 求解并生成result字典 + + # 可选:快速验证ENO3结果 + # plotter.plot_quick(cfd_eno3.result, title="ENO3 Quick Check") + + # 3. 配置并运行WENO3求解(注意:WENO默认5阶,这里显式指定3阶) + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) # 显式指定3阶(默认是5阶) + # 可选:覆盖默认值 + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + cfd_weno3.run() + + # 4. 可选:保存结果(供离线绘图) + # cfd_eno3.save_result("eno3_result.npz") + # cfd_weno3.save_result("weno3_result.npz") + + # 5. 绘制ENO/WENO对比图 + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" # 可选:保存图片 + ) + +if __name__ == "__main__": + # 主程序入口 + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05a/solution.py b/example/1d-linear-convection/weno3/python/05a/solution.py new file mode 100644 index 000000000..92a46d3f4 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05a/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config.ic_type, config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05a/solver.py b/example/1d-linear-convection/weno3/python/05a/solver.py new file mode 100644 index 000000000..b2024d2c1 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05a/solver.py @@ -0,0 +1,90 @@ +import numpy as np +import matplotlib.pyplot as plt +import inflect +from abc import ABC, abstractmethod + +# Flux +from flux import InviscidFluxCalculator, RusanovFluxCalculator, EngquistOsherFluxCalculator, FluxCalculatorFactory + +# Boundary +from boundary import BoundaryCondition, PeriodicBoundary, DirichletBoundary, NeumannBoundary, BoundaryConditionFactory + +# Time integration +from time_integration import TimeIntegrator, RK1Integrator, RK2Integrator, RK3Integrator, TimeIntegratorFactory + +# Mesh 👈 新增这一行 +from mesh import Mesh + +#from reconstructor import Reconstructor, EnoReconstructor, WenoReconstructor, ReconstructorFactory +from reconstructor import ReconstructorFactory + +from initial_condition import InitialCondition, StepFunctionIC, SineWaveIC, GaussianPulseIC, InitialConditionFactory + +from domain import Domain +from solution import Solution # 👈 新增 + +from config import CfdConfig +from residual import ResidualCalculator + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, config, mesh): + self.config = config + self.domain = Domain(config, mesh) + self.solution = Solution(config, self.domain) + self.reconstructor = ReconstructorFactory.create(config, self.domain) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + def exact_solution(self): + """通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界""" + x = self.domain.mesh.xcc + T = self.config.final_time + c = self.config.wave_speed + L = self.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = InitialConditionFactory.create(self.config.ic_type, self.config) + return ic.evaluate_at(x_shifted) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + #runge_kutta(self) + self.integrator.step(dt) + t += dt + config.dt = dt_old + + # 整理标准化结果 + u_numerical = self.solution.u[self.domain.ist:self.domain.ied].copy() + self.result = { + "x": domain.mesh.xcc, + "numerical": u_numerical, + "analytical": self.exact_solution(), + "config": { + "scheme": self.config.recon_scheme, + "order": self.config.spatial_order, + "rk_order": self.config.rk_order, + "final_time": self.config.final_time + } + } + + return u_numerical diff --git a/example/1d-linear-convection/weno3/python/05a/time_integration.py b/example/1d-linear-convection/weno3/python/05a/time_integration.py new file mode 100644 index 000000000..54dc42771 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05a/time_integration.py @@ -0,0 +1,111 @@ +# time_integration.py +from abc import ABC, abstractmethod + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = cfd.residual_calculator + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(复用公共逻辑) ---------------------- +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +# ---------------------- 3. 时间推进器工厂(统一创建逻辑) ---------------------- +class TimeIntegratorFactory: + """时间推进器工厂:根据配置创建对应RK实例""" + @staticmethod + def create(cfd): + rk_order = cfd.config.rk_order + integrator_mapping = { + 1: RK1Integrator, + 2: RK2Integrator, + 3: RK3Integrator, + # 新增RK4只需:4: RK4Integrator + } + if rk_order not in integrator_mapping: + raise ValueError(f"不支持的RK阶数:{rk_order}(可选:{list(integrator_mapping.keys())})") + return integrator_mapping[rk_order](cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05b/boundary.py b/example/1d-linear-convection/weno3/python/05b/boundary.py new file mode 100644 index 000000000..ae3fe1d97 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05b/boundary.py @@ -0,0 +1,136 @@ +from abc import ABC, abstractmethod + +# ---------------------- 导入注册系统 ---------------------- +try: + # 首先尝试从新的核心位置导入 + from cfd_core.registry import ComponentRegistry, register_component +except ImportError: + # 如果新的核心模块还不存在,使用我们之前定义的简化版本 + # 这将确保代码立即可用,未来再统一迁移到核心模块 + import sys + import os + # 添加当前目录,以便找到可能在同一文件夹的 registry.py + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + try: + from registry import ComponentRegistry, register_component + print("[boundary] 使用本地 registry.py 中的注册系统") + except ImportError: + # 如果完全找不到,提供一个极简定义防止报错(仅用于过渡) + print("[boundary] 警告: 未找到注册系统,使用极简回退方案") + class ComponentRegistry: + _registries = {} + @classmethod + def register(cls, *args, **kwargs): pass + @classmethod + def create(cls, *args, **kwargs): + # 这是一个非常基本的回退,仅用于演示 + # 在实际替换前,您应确保 registry.py 存在 + raise RuntimeError("注册系统未正确初始化。请确保 registry.py 在 Python 路径中。") + def register_component(*args, **kwargs): + def dummy_decorator(cls): + return cls + return dummy_decorator + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界(进口)固定值(从配置读取) + left_value = self.config.get("left_boundary_value", 1.0) + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # 右边界(出口)固定值(从配置读取) + right_value = self.config.get("right_boundary_value", 2.0) + for ig in range(nghosts): + u[ied + ig] = right_value + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + +# ---------------------- 新的边界条件工厂(使用注册系统) ---------------------- +class BoundaryConditionFactory: + """边界条件工厂:根据配置创建对应边界条件实例(新版本,使用注册系统)""" + + @staticmethod + def create(cfd): + """ + 使用注册系统创建边界条件实例。 + 保持与旧版本完全相同的接口,实现无缝替换。 + """ + # 从配置读取边界类型 + bc_type = cfd.config.boundary_type.lower() + + try: + # 使用注册系统创建实例 + # ComponentRegistry.create(类别, 名称, 传递给构造函数的参数) + bc_instance = ComponentRegistry.create('boundary', bc_type, cfd) + return bc_instance + except (ValueError, RuntimeError) as e: + # 增强错误信息,列出可用的选项 + available = [] + try: + # 尝试从注册表获取可用列表 + all_comps = ComponentRegistry.list_all() + available = all_comps.get('boundary', []) + except: + # 如果注册表不可用,使用硬编码列表作为后备 + available = ['periodic', 'dirichlet', 'neumann'] + + raise ValueError( + f"不支持的边界类型:'{bc_type}'\n" + f"可用类型:{available}\n" + f"原始错误:{e}" + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05b/config.py b/example/1d-linear-convection/weno3/python/05b/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05b/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05b/domain.py b/example/1d-linear-convection/weno3/python/05b/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05b/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05b/flux.py b/example/1d-linear-convection/weno3/python/05b/flux.py new file mode 100644 index 000000000..4ad8a8716 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05b/flux.py @@ -0,0 +1,113 @@ +""" +通量计算器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册,替代硬编码工厂 +""" + +from abc import ABC, abstractmethod + +# ---------------------- 导入注册系统 ---------------------- +try: + # 从核心位置或本地导入注册系统 + from cfd_core.registry import ComponentRegistry, register_component +except ImportError: + import sys + import os + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + try: + from registry import ComponentRegistry, register_component + print("[flux] 使用本地 registry.py 中的注册系统") + except ImportError: + # 极简回退方案(实际使用时应该确保registry.py存在) + print("[flux] 警告: 未找到注册系统,使用极简回退方案") + class ComponentRegistry: + _registries = {} + @classmethod + def register(cls, *args, **kwargs): pass + @classmethod + def create(cls, category, name, *args, **kwargs): + # 回退到硬编码逻辑(仅用于过渡) + if category == 'flux' and name == 'rusanov': + return RusanovFluxCalculator(*args, **kwargs) + elif category == 'flux' and name == 'engquist-osher': + return EngquistOsherFluxCalculator(*args, **kwargs) + raise ValueError(f"不支持的 {category}.{name}") + def register_component(*args, **kwargs): + def dummy_decorator(cls): + return cls + return dummy_decorator + +# ---------------------- 1. 抽象通量计算基类(统一接口) ---------------------- +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass + +# ---------------------- 2. 具体通量计算子类(使用装饰器注册) ---------------------- + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + +# ---------------------- 3. 通量计算器工厂(使用注册系统) ---------------------- +class FluxCalculatorFactory: + """通量计算器工厂:根据配置创建对应通量计算器实例""" + @staticmethod + def create(cfd): + """根据配置创建通量计算器实例""" + flux_type = cfd.config.flux_type.lower() + + try: + # 使用注册系统创建实例 + flux_instance = ComponentRegistry.create('flux', flux_type, cfd) + return flux_instance + except (ValueError, RuntimeError) as e: + # 增强错误信息 + available = [] + try: + all_comps = ComponentRegistry.list_all() + available = all_comps.get('flux', []) + except: + # 后备列表 + available = ['rusanov', 'engquist-osher'] + + raise ValueError( + f"不支持的flux类型:'{flux_type}'\n" + f"可用类型:{available}\n" + f"原始错误:{e}" + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05b/initial_condition.py b/example/1d-linear-convection/weno3/python/05b/initial_condition.py new file mode 100644 index 000000000..166b7dbde --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05b/initial_condition.py @@ -0,0 +1,81 @@ +# initial_condition.py +import numpy as np +from abc import ABC, abstractmethod + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现 ---------------------- +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + + +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = self.config.get("domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + + +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = self.config.get("pulse_center", 0.5) + width = self.config.get("pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + + +# ---------------------- 3. 初始条件工厂 ---------------------- +class InitialConditionFactory: + _registry = { + 'step': StepFunctionIC, + 'sin': SineWaveIC, + 'gaussian': GaussianPulseIC, + } + + @classmethod + def create(cls, ic_type, config): + if ic_type not in cls._registry: + raise ValueError(f"未知的初始条件类型: {ic_type}(支持: {list(cls._registry.keys())})") + return cls._registry[ic_type](config) + + @classmethod + def register(cls, name, ic_class): + cls._registry[name] = ic_class \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05b/mesh.py b/example/1d-linear-convection/weno3/python/05b/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05b/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05b/plotter.py b/example/1d-linear-convection/weno3/python/05b/plotter.py new file mode 100644 index 000000000..9f1a414fd --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05b/plotter.py @@ -0,0 +1,107 @@ +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05b/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python/05b/reconstructor/__init__.py new file mode 100644 index 000000000..fa17547a7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05b/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 可选择性导出具体类(通常不需要) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05b/reconstructor/base.py b/example/1d-linear-convection/weno3/python/05b/reconstructor/base.py new file mode 100644 index 000000000..3cb4763dd --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05b/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def reconstruct(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05b/reconstructor/eno.py b/example/1d-linear-convection/weno3/python/05b/reconstructor/eno.py new file mode 100644 index 000000000..e087fe578 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05b/reconstructor/eno.py @@ -0,0 +1,88 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor # 👈 正确导入基类 + + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 3. ENO 重构器 ---------------------- +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def reconstruct(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05b/reconstructor/factory.py b/example/1d-linear-convection/weno3/python/05b/reconstructor/factory.py new file mode 100644 index 000000000..bf5795bcf --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05b/reconstructor/factory.py @@ -0,0 +1,35 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor + +class ReconstructorFactory: + _schemes = { + "eno": EnoReconstructor, + "weno3": Weno3Reconstructor, # ← 注意:这里用 "weno3" + # "weno5": Weno5Reconstructor, # 未来扩展 + } + + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + # ✅ 关键:将 "weno" 自动映射为 "weno3", "weno5" 等 + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 检查是否支持 + if scheme not in ReconstructorFactory._schemes: + supported = list(ReconstructorFactory._schemes.keys()) + raise ValueError(f"不支持的重建格式:'{scheme}'(支持:{supported})") + + recon_cls = ReconstructorFactory._schemes[scheme] + + # 根据 scheme 类型创建实例 + if scheme.startswith("eno"): + return recon_cls(order, domain.ntcells) + elif scheme.startswith("weno"): + return recon_cls() # WENO 类通常无参 + # 可扩展 elif... diff --git a/example/1d-linear-convection/weno3/python/05b/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python/05b/reconstructor/weno3.py new file mode 100644 index 000000000..cf12db862 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05b/reconstructor/weno3.py @@ -0,0 +1,86 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor + +class Weno3Reconstructor(Reconstructor): + def reconstruct(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + """ + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 # smoothness indicator for stencil [v2, v3] + beta1 = (v2 - v1)**2 # smoothness indicator for stencil [v1, v2] + + d0, d1 = 2/3, 1/3 # optimal linear weights (for right value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 0.5 * v2 + 0.5 * v3 # reconstruction from [v2, v3] + q1 = -0.5 * v1 + 1.5 * v2 # reconstruction from [v1, v2] + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 + beta1 = (v2 - v1)**2 + + d0, d1 = 1/3, 2/3 # optimal linear weights (for left value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 1.5 * v2 - 0.5 * v3 # from [v2, v3] + q1 = 0.5 * v1 + 0.5 * v2 # from [v1, v2] + return w0 * q0 + w1 * q1 + """ diff --git a/example/1d-linear-convection/weno3/python/05b/registry.py b/example/1d-linear-convection/weno3/python/05b/registry.py new file mode 100644 index 000000000..f5bd068a3 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05b/registry.py @@ -0,0 +1,56 @@ +""" +CFD组件注册系统核心 +完全独立,不依赖任何现有代码 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表 - 替代硬编码工厂""" + + # 存储所有注册的组件 {类别: {名称: 类}} + _registries: Dict[str, Dict[str, Type]] = {} + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册一个组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + cls._registries[category][name] = component_class + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取已注册的组件类""" + if category not in cls._registries: + raise ValueError(f"❌ 未知类别: {category}") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower().replace('boundary', '').replace('flux', '') + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05b/residual.py b/example/1d-linear-convection/weno3/python/05b/residual.py new file mode 100644 index 000000000..b4d4d7dcc --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05b/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux import FluxCalculatorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + self.reconstructor = self.cfd.reconstructor + + # 初始化通量计算器(工厂创建) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._reconstruct() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _reconstruct(self): + """私有方法:界面值重建""" + self.reconstructor.reconstruct(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05b/run_eno_weno.py b/example/1d-linear-convection/weno3/python/05b/run_eno_weno.py new file mode 100644 index 000000000..fd7f38744 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05b/run_eno_weno.py @@ -0,0 +1,52 @@ +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + #mesh = Mesh(ncells=100, L=2.0) + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行ENO3求解(使用你的链式调用) + print("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + config_eno3.with_reconstruction("eno", 3) # 显式指定3阶(也可省略,ENO默认3阶) + # 可选:覆盖默认值(如dt) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + cfd_eno3.run() # 求解并生成result字典 + + # 可选:快速验证ENO3结果 + # plotter.plot_quick(cfd_eno3.result, title="ENO3 Quick Check") + + # 3. 配置并运行WENO3求解(注意:WENO默认5阶,这里显式指定3阶) + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) # 显式指定3阶(默认是5阶) + # 可选:覆盖默认值 + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + cfd_weno3.run() + + # 4. 可选:保存结果(供离线绘图) + # cfd_eno3.save_result("eno3_result.npz") + # cfd_weno3.save_result("weno3_result.npz") + + # 5. 绘制ENO/WENO对比图 + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" # 可选:保存图片 + ) + +if __name__ == "__main__": + # 主程序入口 + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05b/solution.py b/example/1d-linear-convection/weno3/python/05b/solution.py new file mode 100644 index 000000000..92a46d3f4 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05b/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config.ic_type, config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05b/solver.py b/example/1d-linear-convection/weno3/python/05b/solver.py new file mode 100644 index 000000000..b2024d2c1 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05b/solver.py @@ -0,0 +1,90 @@ +import numpy as np +import matplotlib.pyplot as plt +import inflect +from abc import ABC, abstractmethod + +# Flux +from flux import InviscidFluxCalculator, RusanovFluxCalculator, EngquistOsherFluxCalculator, FluxCalculatorFactory + +# Boundary +from boundary import BoundaryCondition, PeriodicBoundary, DirichletBoundary, NeumannBoundary, BoundaryConditionFactory + +# Time integration +from time_integration import TimeIntegrator, RK1Integrator, RK2Integrator, RK3Integrator, TimeIntegratorFactory + +# Mesh 👈 新增这一行 +from mesh import Mesh + +#from reconstructor import Reconstructor, EnoReconstructor, WenoReconstructor, ReconstructorFactory +from reconstructor import ReconstructorFactory + +from initial_condition import InitialCondition, StepFunctionIC, SineWaveIC, GaussianPulseIC, InitialConditionFactory + +from domain import Domain +from solution import Solution # 👈 新增 + +from config import CfdConfig +from residual import ResidualCalculator + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, config, mesh): + self.config = config + self.domain = Domain(config, mesh) + self.solution = Solution(config, self.domain) + self.reconstructor = ReconstructorFactory.create(config, self.domain) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + def exact_solution(self): + """通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界""" + x = self.domain.mesh.xcc + T = self.config.final_time + c = self.config.wave_speed + L = self.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = InitialConditionFactory.create(self.config.ic_type, self.config) + return ic.evaluate_at(x_shifted) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + #runge_kutta(self) + self.integrator.step(dt) + t += dt + config.dt = dt_old + + # 整理标准化结果 + u_numerical = self.solution.u[self.domain.ist:self.domain.ied].copy() + self.result = { + "x": domain.mesh.xcc, + "numerical": u_numerical, + "analytical": self.exact_solution(), + "config": { + "scheme": self.config.recon_scheme, + "order": self.config.spatial_order, + "rk_order": self.config.rk_order, + "final_time": self.config.final_time + } + } + + return u_numerical diff --git a/example/1d-linear-convection/weno3/python/05b/time_integration.py b/example/1d-linear-convection/weno3/python/05b/time_integration.py new file mode 100644 index 000000000..54dc42771 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05b/time_integration.py @@ -0,0 +1,111 @@ +# time_integration.py +from abc import ABC, abstractmethod + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = cfd.residual_calculator + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(复用公共逻辑) ---------------------- +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +# ---------------------- 3. 时间推进器工厂(统一创建逻辑) ---------------------- +class TimeIntegratorFactory: + """时间推进器工厂:根据配置创建对应RK实例""" + @staticmethod + def create(cfd): + rk_order = cfd.config.rk_order + integrator_mapping = { + 1: RK1Integrator, + 2: RK2Integrator, + 3: RK3Integrator, + # 新增RK4只需:4: RK4Integrator + } + if rk_order not in integrator_mapping: + raise ValueError(f"不支持的RK阶数:{rk_order}(可选:{list(integrator_mapping.keys())})") + return integrator_mapping[rk_order](cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05c/boundary.py b/example/1d-linear-convection/weno3/python/05c/boundary.py new file mode 100644 index 000000000..ae3fe1d97 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05c/boundary.py @@ -0,0 +1,136 @@ +from abc import ABC, abstractmethod + +# ---------------------- 导入注册系统 ---------------------- +try: + # 首先尝试从新的核心位置导入 + from cfd_core.registry import ComponentRegistry, register_component +except ImportError: + # 如果新的核心模块还不存在,使用我们之前定义的简化版本 + # 这将确保代码立即可用,未来再统一迁移到核心模块 + import sys + import os + # 添加当前目录,以便找到可能在同一文件夹的 registry.py + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + try: + from registry import ComponentRegistry, register_component + print("[boundary] 使用本地 registry.py 中的注册系统") + except ImportError: + # 如果完全找不到,提供一个极简定义防止报错(仅用于过渡) + print("[boundary] 警告: 未找到注册系统,使用极简回退方案") + class ComponentRegistry: + _registries = {} + @classmethod + def register(cls, *args, **kwargs): pass + @classmethod + def create(cls, *args, **kwargs): + # 这是一个非常基本的回退,仅用于演示 + # 在实际替换前,您应确保 registry.py 存在 + raise RuntimeError("注册系统未正确初始化。请确保 registry.py 在 Python 路径中。") + def register_component(*args, **kwargs): + def dummy_decorator(cls): + return cls + return dummy_decorator + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界(进口)固定值(从配置读取) + left_value = self.config.get("left_boundary_value", 1.0) + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # 右边界(出口)固定值(从配置读取) + right_value = self.config.get("right_boundary_value", 2.0) + for ig in range(nghosts): + u[ied + ig] = right_value + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + +# ---------------------- 新的边界条件工厂(使用注册系统) ---------------------- +class BoundaryConditionFactory: + """边界条件工厂:根据配置创建对应边界条件实例(新版本,使用注册系统)""" + + @staticmethod + def create(cfd): + """ + 使用注册系统创建边界条件实例。 + 保持与旧版本完全相同的接口,实现无缝替换。 + """ + # 从配置读取边界类型 + bc_type = cfd.config.boundary_type.lower() + + try: + # 使用注册系统创建实例 + # ComponentRegistry.create(类别, 名称, 传递给构造函数的参数) + bc_instance = ComponentRegistry.create('boundary', bc_type, cfd) + return bc_instance + except (ValueError, RuntimeError) as e: + # 增强错误信息,列出可用的选项 + available = [] + try: + # 尝试从注册表获取可用列表 + all_comps = ComponentRegistry.list_all() + available = all_comps.get('boundary', []) + except: + # 如果注册表不可用,使用硬编码列表作为后备 + available = ['periodic', 'dirichlet', 'neumann'] + + raise ValueError( + f"不支持的边界类型:'{bc_type}'\n" + f"可用类型:{available}\n" + f"原始错误:{e}" + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05c/config.py b/example/1d-linear-convection/weno3/python/05c/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05c/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05c/domain.py b/example/1d-linear-convection/weno3/python/05c/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05c/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05c/flux.py b/example/1d-linear-convection/weno3/python/05c/flux.py new file mode 100644 index 000000000..4ad8a8716 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05c/flux.py @@ -0,0 +1,113 @@ +""" +通量计算器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册,替代硬编码工厂 +""" + +from abc import ABC, abstractmethod + +# ---------------------- 导入注册系统 ---------------------- +try: + # 从核心位置或本地导入注册系统 + from cfd_core.registry import ComponentRegistry, register_component +except ImportError: + import sys + import os + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + try: + from registry import ComponentRegistry, register_component + print("[flux] 使用本地 registry.py 中的注册系统") + except ImportError: + # 极简回退方案(实际使用时应该确保registry.py存在) + print("[flux] 警告: 未找到注册系统,使用极简回退方案") + class ComponentRegistry: + _registries = {} + @classmethod + def register(cls, *args, **kwargs): pass + @classmethod + def create(cls, category, name, *args, **kwargs): + # 回退到硬编码逻辑(仅用于过渡) + if category == 'flux' and name == 'rusanov': + return RusanovFluxCalculator(*args, **kwargs) + elif category == 'flux' and name == 'engquist-osher': + return EngquistOsherFluxCalculator(*args, **kwargs) + raise ValueError(f"不支持的 {category}.{name}") + def register_component(*args, **kwargs): + def dummy_decorator(cls): + return cls + return dummy_decorator + +# ---------------------- 1. 抽象通量计算基类(统一接口) ---------------------- +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass + +# ---------------------- 2. 具体通量计算子类(使用装饰器注册) ---------------------- + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + +# ---------------------- 3. 通量计算器工厂(使用注册系统) ---------------------- +class FluxCalculatorFactory: + """通量计算器工厂:根据配置创建对应通量计算器实例""" + @staticmethod + def create(cfd): + """根据配置创建通量计算器实例""" + flux_type = cfd.config.flux_type.lower() + + try: + # 使用注册系统创建实例 + flux_instance = ComponentRegistry.create('flux', flux_type, cfd) + return flux_instance + except (ValueError, RuntimeError) as e: + # 增强错误信息 + available = [] + try: + all_comps = ComponentRegistry.list_all() + available = all_comps.get('flux', []) + except: + # 后备列表 + available = ['rusanov', 'engquist-osher'] + + raise ValueError( + f"不支持的flux类型:'{flux_type}'\n" + f"可用类型:{available}\n" + f"原始错误:{e}" + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05c/initial_condition.py b/example/1d-linear-convection/weno3/python/05c/initial_condition.py new file mode 100644 index 000000000..fac3be5bf --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05c/initial_condition.py @@ -0,0 +1,146 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod + +# ---------------------- 导入注册系统 ---------------------- +try: + from cfd_core.registry import ComponentRegistry, register_component +except ImportError: + import sys + import os + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + try: + from registry import ComponentRegistry, register_component + print("[initial_condition] 使用本地 registry.py 中的注册系统") + except ImportError: + print("[initial_condition] 警告: 未找到注册系统,使用极简回退方案") + class ComponentRegistry: + _registries = {} + @classmethod + def register(cls, *args, **kwargs): pass + @classmethod + def create(cls, category, name, *args, **kwargs): + # 回退到硬编码逻辑 + if category == 'initial_condition': + if name == 'step': + return StepFunctionIC(*args, **kwargs) + elif name == 'sin': + return SineWaveIC(*args, **kwargs) + elif name == 'gaussian': + return GaussianPulseIC(*args, **kwargs) + raise ValueError(f"不支持的 {category}.{name}") + def register_component(*args, **kwargs): + def dummy_decorator(cls): + return cls + return dummy_decorator + + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = self.config.get("domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = self.config.get("pulse_center", 0.5) + width = self.config.get("pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + + +# ---------------------- 3. 初始条件工厂(使用注册系统) ---------------------- +class InitialConditionFactory: + """初始条件工厂:根据配置创建对应初始条件实例""" + + @classmethod + def create(cls, ic_type, config): + """创建初始条件实例""" + ic_type_lower = ic_type.lower() + + try: + # 使用注册系统创建实例 + ic_instance = ComponentRegistry.create('initial_condition', ic_type_lower, config) + return ic_instance + except (ValueError, RuntimeError) as e: + # 增强错误信息 + available = [] + try: + all_comps = ComponentRegistry.list_all() + available = all_comps.get('initial_condition', []) + except: + # 后备列表 + available = ['step', 'sin', 'gaussian'] + + raise ValueError( + f"未知的初始条件类型: '{ic_type}'\n" + f"支持的类型: {available}\n" + f"原始错误: {e}" + ) + + # 保持原有的注册方法,用于向后兼容或动态注册 + _registry = {} # 旧式注册表,可能被其他代码使用 + + @classmethod + def register(cls, name, ic_class): + """注册初始条件类(兼容旧接口)""" + cls._registry[name] = ic_class + # 同时注册到新系统(如果可能) + try: + ComponentRegistry.register('initial_condition', name, ic_class) + except: + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05c/mesh.py b/example/1d-linear-convection/weno3/python/05c/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05c/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05c/plotter.py b/example/1d-linear-convection/weno3/python/05c/plotter.py new file mode 100644 index 000000000..9f1a414fd --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05c/plotter.py @@ -0,0 +1,107 @@ +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05c/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python/05c/reconstructor/__init__.py new file mode 100644 index 000000000..fa17547a7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05c/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 可选择性导出具体类(通常不需要) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05c/reconstructor/base.py b/example/1d-linear-convection/weno3/python/05c/reconstructor/base.py new file mode 100644 index 000000000..3cb4763dd --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05c/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def reconstruct(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05c/reconstructor/eno.py b/example/1d-linear-convection/weno3/python/05c/reconstructor/eno.py new file mode 100644 index 000000000..e087fe578 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05c/reconstructor/eno.py @@ -0,0 +1,88 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor # 👈 正确导入基类 + + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 3. ENO 重构器 ---------------------- +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def reconstruct(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05c/reconstructor/factory.py b/example/1d-linear-convection/weno3/python/05c/reconstructor/factory.py new file mode 100644 index 000000000..bf5795bcf --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05c/reconstructor/factory.py @@ -0,0 +1,35 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor + +class ReconstructorFactory: + _schemes = { + "eno": EnoReconstructor, + "weno3": Weno3Reconstructor, # ← 注意:这里用 "weno3" + # "weno5": Weno5Reconstructor, # 未来扩展 + } + + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + # ✅ 关键:将 "weno" 自动映射为 "weno3", "weno5" 等 + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 检查是否支持 + if scheme not in ReconstructorFactory._schemes: + supported = list(ReconstructorFactory._schemes.keys()) + raise ValueError(f"不支持的重建格式:'{scheme}'(支持:{supported})") + + recon_cls = ReconstructorFactory._schemes[scheme] + + # 根据 scheme 类型创建实例 + if scheme.startswith("eno"): + return recon_cls(order, domain.ntcells) + elif scheme.startswith("weno"): + return recon_cls() # WENO 类通常无参 + # 可扩展 elif... diff --git a/example/1d-linear-convection/weno3/python/05c/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python/05c/reconstructor/weno3.py new file mode 100644 index 000000000..cf12db862 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05c/reconstructor/weno3.py @@ -0,0 +1,86 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor + +class Weno3Reconstructor(Reconstructor): + def reconstruct(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + """ + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 # smoothness indicator for stencil [v2, v3] + beta1 = (v2 - v1)**2 # smoothness indicator for stencil [v1, v2] + + d0, d1 = 2/3, 1/3 # optimal linear weights (for right value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 0.5 * v2 + 0.5 * v3 # reconstruction from [v2, v3] + q1 = -0.5 * v1 + 1.5 * v2 # reconstruction from [v1, v2] + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 + beta1 = (v2 - v1)**2 + + d0, d1 = 1/3, 2/3 # optimal linear weights (for left value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 1.5 * v2 - 0.5 * v3 # from [v2, v3] + q1 = 0.5 * v1 + 0.5 * v2 # from [v1, v2] + return w0 * q0 + w1 * q1 + """ diff --git a/example/1d-linear-convection/weno3/python/05c/registry.py b/example/1d-linear-convection/weno3/python/05c/registry.py new file mode 100644 index 000000000..f5bd068a3 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05c/registry.py @@ -0,0 +1,56 @@ +""" +CFD组件注册系统核心 +完全独立,不依赖任何现有代码 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表 - 替代硬编码工厂""" + + # 存储所有注册的组件 {类别: {名称: 类}} + _registries: Dict[str, Dict[str, Type]] = {} + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册一个组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + cls._registries[category][name] = component_class + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取已注册的组件类""" + if category not in cls._registries: + raise ValueError(f"❌ 未知类别: {category}") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower().replace('boundary', '').replace('flux', '') + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05c/residual.py b/example/1d-linear-convection/weno3/python/05c/residual.py new file mode 100644 index 000000000..b4d4d7dcc --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05c/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux import FluxCalculatorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + self.reconstructor = self.cfd.reconstructor + + # 初始化通量计算器(工厂创建) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._reconstruct() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _reconstruct(self): + """私有方法:界面值重建""" + self.reconstructor.reconstruct(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05c/run_eno_weno.py b/example/1d-linear-convection/weno3/python/05c/run_eno_weno.py new file mode 100644 index 000000000..fd7f38744 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05c/run_eno_weno.py @@ -0,0 +1,52 @@ +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + #mesh = Mesh(ncells=100, L=2.0) + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行ENO3求解(使用你的链式调用) + print("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + config_eno3.with_reconstruction("eno", 3) # 显式指定3阶(也可省略,ENO默认3阶) + # 可选:覆盖默认值(如dt) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + cfd_eno3.run() # 求解并生成result字典 + + # 可选:快速验证ENO3结果 + # plotter.plot_quick(cfd_eno3.result, title="ENO3 Quick Check") + + # 3. 配置并运行WENO3求解(注意:WENO默认5阶,这里显式指定3阶) + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) # 显式指定3阶(默认是5阶) + # 可选:覆盖默认值 + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + cfd_weno3.run() + + # 4. 可选:保存结果(供离线绘图) + # cfd_eno3.save_result("eno3_result.npz") + # cfd_weno3.save_result("weno3_result.npz") + + # 5. 绘制ENO/WENO对比图 + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" # 可选:保存图片 + ) + +if __name__ == "__main__": + # 主程序入口 + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05c/solution.py b/example/1d-linear-convection/weno3/python/05c/solution.py new file mode 100644 index 000000000..92a46d3f4 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05c/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config.ic_type, config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05c/solver.py b/example/1d-linear-convection/weno3/python/05c/solver.py new file mode 100644 index 000000000..b2024d2c1 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05c/solver.py @@ -0,0 +1,90 @@ +import numpy as np +import matplotlib.pyplot as plt +import inflect +from abc import ABC, abstractmethod + +# Flux +from flux import InviscidFluxCalculator, RusanovFluxCalculator, EngquistOsherFluxCalculator, FluxCalculatorFactory + +# Boundary +from boundary import BoundaryCondition, PeriodicBoundary, DirichletBoundary, NeumannBoundary, BoundaryConditionFactory + +# Time integration +from time_integration import TimeIntegrator, RK1Integrator, RK2Integrator, RK3Integrator, TimeIntegratorFactory + +# Mesh 👈 新增这一行 +from mesh import Mesh + +#from reconstructor import Reconstructor, EnoReconstructor, WenoReconstructor, ReconstructorFactory +from reconstructor import ReconstructorFactory + +from initial_condition import InitialCondition, StepFunctionIC, SineWaveIC, GaussianPulseIC, InitialConditionFactory + +from domain import Domain +from solution import Solution # 👈 新增 + +from config import CfdConfig +from residual import ResidualCalculator + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, config, mesh): + self.config = config + self.domain = Domain(config, mesh) + self.solution = Solution(config, self.domain) + self.reconstructor = ReconstructorFactory.create(config, self.domain) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + def exact_solution(self): + """通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界""" + x = self.domain.mesh.xcc + T = self.config.final_time + c = self.config.wave_speed + L = self.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = InitialConditionFactory.create(self.config.ic_type, self.config) + return ic.evaluate_at(x_shifted) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + #runge_kutta(self) + self.integrator.step(dt) + t += dt + config.dt = dt_old + + # 整理标准化结果 + u_numerical = self.solution.u[self.domain.ist:self.domain.ied].copy() + self.result = { + "x": domain.mesh.xcc, + "numerical": u_numerical, + "analytical": self.exact_solution(), + "config": { + "scheme": self.config.recon_scheme, + "order": self.config.spatial_order, + "rk_order": self.config.rk_order, + "final_time": self.config.final_time + } + } + + return u_numerical diff --git a/example/1d-linear-convection/weno3/python/05c/time_integration.py b/example/1d-linear-convection/weno3/python/05c/time_integration.py new file mode 100644 index 000000000..54dc42771 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05c/time_integration.py @@ -0,0 +1,111 @@ +# time_integration.py +from abc import ABC, abstractmethod + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = cfd.residual_calculator + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(复用公共逻辑) ---------------------- +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +# ---------------------- 3. 时间推进器工厂(统一创建逻辑) ---------------------- +class TimeIntegratorFactory: + """时间推进器工厂:根据配置创建对应RK实例""" + @staticmethod + def create(cfd): + rk_order = cfd.config.rk_order + integrator_mapping = { + 1: RK1Integrator, + 2: RK2Integrator, + 3: RK3Integrator, + # 新增RK4只需:4: RK4Integrator + } + if rk_order not in integrator_mapping: + raise ValueError(f"不支持的RK阶数:{rk_order}(可选:{list(integrator_mapping.keys())})") + return integrator_mapping[rk_order](cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05d/boundary.py b/example/1d-linear-convection/weno3/python/05d/boundary.py new file mode 100644 index 000000000..ae3fe1d97 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05d/boundary.py @@ -0,0 +1,136 @@ +from abc import ABC, abstractmethod + +# ---------------------- 导入注册系统 ---------------------- +try: + # 首先尝试从新的核心位置导入 + from cfd_core.registry import ComponentRegistry, register_component +except ImportError: + # 如果新的核心模块还不存在,使用我们之前定义的简化版本 + # 这将确保代码立即可用,未来再统一迁移到核心模块 + import sys + import os + # 添加当前目录,以便找到可能在同一文件夹的 registry.py + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + try: + from registry import ComponentRegistry, register_component + print("[boundary] 使用本地 registry.py 中的注册系统") + except ImportError: + # 如果完全找不到,提供一个极简定义防止报错(仅用于过渡) + print("[boundary] 警告: 未找到注册系统,使用极简回退方案") + class ComponentRegistry: + _registries = {} + @classmethod + def register(cls, *args, **kwargs): pass + @classmethod + def create(cls, *args, **kwargs): + # 这是一个非常基本的回退,仅用于演示 + # 在实际替换前,您应确保 registry.py 存在 + raise RuntimeError("注册系统未正确初始化。请确保 registry.py 在 Python 路径中。") + def register_component(*args, **kwargs): + def dummy_decorator(cls): + return cls + return dummy_decorator + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界(进口)固定值(从配置读取) + left_value = self.config.get("left_boundary_value", 1.0) + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # 右边界(出口)固定值(从配置读取) + right_value = self.config.get("right_boundary_value", 2.0) + for ig in range(nghosts): + u[ied + ig] = right_value + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + +# ---------------------- 新的边界条件工厂(使用注册系统) ---------------------- +class BoundaryConditionFactory: + """边界条件工厂:根据配置创建对应边界条件实例(新版本,使用注册系统)""" + + @staticmethod + def create(cfd): + """ + 使用注册系统创建边界条件实例。 + 保持与旧版本完全相同的接口,实现无缝替换。 + """ + # 从配置读取边界类型 + bc_type = cfd.config.boundary_type.lower() + + try: + # 使用注册系统创建实例 + # ComponentRegistry.create(类别, 名称, 传递给构造函数的参数) + bc_instance = ComponentRegistry.create('boundary', bc_type, cfd) + return bc_instance + except (ValueError, RuntimeError) as e: + # 增强错误信息,列出可用的选项 + available = [] + try: + # 尝试从注册表获取可用列表 + all_comps = ComponentRegistry.list_all() + available = all_comps.get('boundary', []) + except: + # 如果注册表不可用,使用硬编码列表作为后备 + available = ['periodic', 'dirichlet', 'neumann'] + + raise ValueError( + f"不支持的边界类型:'{bc_type}'\n" + f"可用类型:{available}\n" + f"原始错误:{e}" + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05d/config.py b/example/1d-linear-convection/weno3/python/05d/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05d/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05d/domain.py b/example/1d-linear-convection/weno3/python/05d/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05d/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05d/flux.py b/example/1d-linear-convection/weno3/python/05d/flux.py new file mode 100644 index 000000000..4ad8a8716 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05d/flux.py @@ -0,0 +1,113 @@ +""" +通量计算器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册,替代硬编码工厂 +""" + +from abc import ABC, abstractmethod + +# ---------------------- 导入注册系统 ---------------------- +try: + # 从核心位置或本地导入注册系统 + from cfd_core.registry import ComponentRegistry, register_component +except ImportError: + import sys + import os + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + try: + from registry import ComponentRegistry, register_component + print("[flux] 使用本地 registry.py 中的注册系统") + except ImportError: + # 极简回退方案(实际使用时应该确保registry.py存在) + print("[flux] 警告: 未找到注册系统,使用极简回退方案") + class ComponentRegistry: + _registries = {} + @classmethod + def register(cls, *args, **kwargs): pass + @classmethod + def create(cls, category, name, *args, **kwargs): + # 回退到硬编码逻辑(仅用于过渡) + if category == 'flux' and name == 'rusanov': + return RusanovFluxCalculator(*args, **kwargs) + elif category == 'flux' and name == 'engquist-osher': + return EngquistOsherFluxCalculator(*args, **kwargs) + raise ValueError(f"不支持的 {category}.{name}") + def register_component(*args, **kwargs): + def dummy_decorator(cls): + return cls + return dummy_decorator + +# ---------------------- 1. 抽象通量计算基类(统一接口) ---------------------- +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass + +# ---------------------- 2. 具体通量计算子类(使用装饰器注册) ---------------------- + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + +# ---------------------- 3. 通量计算器工厂(使用注册系统) ---------------------- +class FluxCalculatorFactory: + """通量计算器工厂:根据配置创建对应通量计算器实例""" + @staticmethod + def create(cfd): + """根据配置创建通量计算器实例""" + flux_type = cfd.config.flux_type.lower() + + try: + # 使用注册系统创建实例 + flux_instance = ComponentRegistry.create('flux', flux_type, cfd) + return flux_instance + except (ValueError, RuntimeError) as e: + # 增强错误信息 + available = [] + try: + all_comps = ComponentRegistry.list_all() + available = all_comps.get('flux', []) + except: + # 后备列表 + available = ['rusanov', 'engquist-osher'] + + raise ValueError( + f"不支持的flux类型:'{flux_type}'\n" + f"可用类型:{available}\n" + f"原始错误:{e}" + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05d/initial_condition.py b/example/1d-linear-convection/weno3/python/05d/initial_condition.py new file mode 100644 index 000000000..fac3be5bf --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05d/initial_condition.py @@ -0,0 +1,146 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod + +# ---------------------- 导入注册系统 ---------------------- +try: + from cfd_core.registry import ComponentRegistry, register_component +except ImportError: + import sys + import os + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + try: + from registry import ComponentRegistry, register_component + print("[initial_condition] 使用本地 registry.py 中的注册系统") + except ImportError: + print("[initial_condition] 警告: 未找到注册系统,使用极简回退方案") + class ComponentRegistry: + _registries = {} + @classmethod + def register(cls, *args, **kwargs): pass + @classmethod + def create(cls, category, name, *args, **kwargs): + # 回退到硬编码逻辑 + if category == 'initial_condition': + if name == 'step': + return StepFunctionIC(*args, **kwargs) + elif name == 'sin': + return SineWaveIC(*args, **kwargs) + elif name == 'gaussian': + return GaussianPulseIC(*args, **kwargs) + raise ValueError(f"不支持的 {category}.{name}") + def register_component(*args, **kwargs): + def dummy_decorator(cls): + return cls + return dummy_decorator + + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = self.config.get("domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = self.config.get("pulse_center", 0.5) + width = self.config.get("pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + + +# ---------------------- 3. 初始条件工厂(使用注册系统) ---------------------- +class InitialConditionFactory: + """初始条件工厂:根据配置创建对应初始条件实例""" + + @classmethod + def create(cls, ic_type, config): + """创建初始条件实例""" + ic_type_lower = ic_type.lower() + + try: + # 使用注册系统创建实例 + ic_instance = ComponentRegistry.create('initial_condition', ic_type_lower, config) + return ic_instance + except (ValueError, RuntimeError) as e: + # 增强错误信息 + available = [] + try: + all_comps = ComponentRegistry.list_all() + available = all_comps.get('initial_condition', []) + except: + # 后备列表 + available = ['step', 'sin', 'gaussian'] + + raise ValueError( + f"未知的初始条件类型: '{ic_type}'\n" + f"支持的类型: {available}\n" + f"原始错误: {e}" + ) + + # 保持原有的注册方法,用于向后兼容或动态注册 + _registry = {} # 旧式注册表,可能被其他代码使用 + + @classmethod + def register(cls, name, ic_class): + """注册初始条件类(兼容旧接口)""" + cls._registry[name] = ic_class + # 同时注册到新系统(如果可能) + try: + ComponentRegistry.register('initial_condition', name, ic_class) + except: + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05d/mesh.py b/example/1d-linear-convection/weno3/python/05d/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05d/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05d/plotter.py b/example/1d-linear-convection/weno3/python/05d/plotter.py new file mode 100644 index 000000000..9f1a414fd --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05d/plotter.py @@ -0,0 +1,107 @@ +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05d/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python/05d/reconstructor/__init__.py new file mode 100644 index 000000000..fa17547a7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05d/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 可选择性导出具体类(通常不需要) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05d/reconstructor/base.py b/example/1d-linear-convection/weno3/python/05d/reconstructor/base.py new file mode 100644 index 000000000..3cb4763dd --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05d/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def reconstruct(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05d/reconstructor/eno.py b/example/1d-linear-convection/weno3/python/05d/reconstructor/eno.py new file mode 100644 index 000000000..e087fe578 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05d/reconstructor/eno.py @@ -0,0 +1,88 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor # 👈 正确导入基类 + + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 3. ENO 重构器 ---------------------- +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def reconstruct(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05d/reconstructor/factory.py b/example/1d-linear-convection/weno3/python/05d/reconstructor/factory.py new file mode 100644 index 000000000..bf5795bcf --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05d/reconstructor/factory.py @@ -0,0 +1,35 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor + +class ReconstructorFactory: + _schemes = { + "eno": EnoReconstructor, + "weno3": Weno3Reconstructor, # ← 注意:这里用 "weno3" + # "weno5": Weno5Reconstructor, # 未来扩展 + } + + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + # ✅ 关键:将 "weno" 自动映射为 "weno3", "weno5" 等 + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 检查是否支持 + if scheme not in ReconstructorFactory._schemes: + supported = list(ReconstructorFactory._schemes.keys()) + raise ValueError(f"不支持的重建格式:'{scheme}'(支持:{supported})") + + recon_cls = ReconstructorFactory._schemes[scheme] + + # 根据 scheme 类型创建实例 + if scheme.startswith("eno"): + return recon_cls(order, domain.ntcells) + elif scheme.startswith("weno"): + return recon_cls() # WENO 类通常无参 + # 可扩展 elif... diff --git a/example/1d-linear-convection/weno3/python/05d/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python/05d/reconstructor/weno3.py new file mode 100644 index 000000000..cf12db862 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05d/reconstructor/weno3.py @@ -0,0 +1,86 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor + +class Weno3Reconstructor(Reconstructor): + def reconstruct(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + """ + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 # smoothness indicator for stencil [v2, v3] + beta1 = (v2 - v1)**2 # smoothness indicator for stencil [v1, v2] + + d0, d1 = 2/3, 1/3 # optimal linear weights (for right value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 0.5 * v2 + 0.5 * v3 # reconstruction from [v2, v3] + q1 = -0.5 * v1 + 1.5 * v2 # reconstruction from [v1, v2] + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 + beta1 = (v2 - v1)**2 + + d0, d1 = 1/3, 2/3 # optimal linear weights (for left value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 1.5 * v2 - 0.5 * v3 # from [v2, v3] + q1 = 0.5 * v1 + 0.5 * v2 # from [v1, v2] + return w0 * q0 + w1 * q1 + """ diff --git a/example/1d-linear-convection/weno3/python/05d/registry.py b/example/1d-linear-convection/weno3/python/05d/registry.py new file mode 100644 index 000000000..f5bd068a3 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05d/registry.py @@ -0,0 +1,56 @@ +""" +CFD组件注册系统核心 +完全独立,不依赖任何现有代码 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表 - 替代硬编码工厂""" + + # 存储所有注册的组件 {类别: {名称: 类}} + _registries: Dict[str, Dict[str, Type]] = {} + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册一个组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + cls._registries[category][name] = component_class + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取已注册的组件类""" + if category not in cls._registries: + raise ValueError(f"❌ 未知类别: {category}") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower().replace('boundary', '').replace('flux', '') + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05d/residual.py b/example/1d-linear-convection/weno3/python/05d/residual.py new file mode 100644 index 000000000..b4d4d7dcc --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05d/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux import FluxCalculatorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + self.reconstructor = self.cfd.reconstructor + + # 初始化通量计算器(工厂创建) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._reconstruct() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _reconstruct(self): + """私有方法:界面值重建""" + self.reconstructor.reconstruct(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05d/run_eno_weno.py b/example/1d-linear-convection/weno3/python/05d/run_eno_weno.py new file mode 100644 index 000000000..fd7f38744 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05d/run_eno_weno.py @@ -0,0 +1,52 @@ +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + #mesh = Mesh(ncells=100, L=2.0) + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行ENO3求解(使用你的链式调用) + print("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + config_eno3.with_reconstruction("eno", 3) # 显式指定3阶(也可省略,ENO默认3阶) + # 可选:覆盖默认值(如dt) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + cfd_eno3.run() # 求解并生成result字典 + + # 可选:快速验证ENO3结果 + # plotter.plot_quick(cfd_eno3.result, title="ENO3 Quick Check") + + # 3. 配置并运行WENO3求解(注意:WENO默认5阶,这里显式指定3阶) + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) # 显式指定3阶(默认是5阶) + # 可选:覆盖默认值 + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + cfd_weno3.run() + + # 4. 可选:保存结果(供离线绘图) + # cfd_eno3.save_result("eno3_result.npz") + # cfd_weno3.save_result("weno3_result.npz") + + # 5. 绘制ENO/WENO对比图 + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" # 可选:保存图片 + ) + +if __name__ == "__main__": + # 主程序入口 + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05d/solution.py b/example/1d-linear-convection/weno3/python/05d/solution.py new file mode 100644 index 000000000..92a46d3f4 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05d/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config.ic_type, config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05d/solver.py b/example/1d-linear-convection/weno3/python/05d/solver.py new file mode 100644 index 000000000..b2024d2c1 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05d/solver.py @@ -0,0 +1,90 @@ +import numpy as np +import matplotlib.pyplot as plt +import inflect +from abc import ABC, abstractmethod + +# Flux +from flux import InviscidFluxCalculator, RusanovFluxCalculator, EngquistOsherFluxCalculator, FluxCalculatorFactory + +# Boundary +from boundary import BoundaryCondition, PeriodicBoundary, DirichletBoundary, NeumannBoundary, BoundaryConditionFactory + +# Time integration +from time_integration import TimeIntegrator, RK1Integrator, RK2Integrator, RK3Integrator, TimeIntegratorFactory + +# Mesh 👈 新增这一行 +from mesh import Mesh + +#from reconstructor import Reconstructor, EnoReconstructor, WenoReconstructor, ReconstructorFactory +from reconstructor import ReconstructorFactory + +from initial_condition import InitialCondition, StepFunctionIC, SineWaveIC, GaussianPulseIC, InitialConditionFactory + +from domain import Domain +from solution import Solution # 👈 新增 + +from config import CfdConfig +from residual import ResidualCalculator + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, config, mesh): + self.config = config + self.domain = Domain(config, mesh) + self.solution = Solution(config, self.domain) + self.reconstructor = ReconstructorFactory.create(config, self.domain) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + def exact_solution(self): + """通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界""" + x = self.domain.mesh.xcc + T = self.config.final_time + c = self.config.wave_speed + L = self.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = InitialConditionFactory.create(self.config.ic_type, self.config) + return ic.evaluate_at(x_shifted) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + #runge_kutta(self) + self.integrator.step(dt) + t += dt + config.dt = dt_old + + # 整理标准化结果 + u_numerical = self.solution.u[self.domain.ist:self.domain.ied].copy() + self.result = { + "x": domain.mesh.xcc, + "numerical": u_numerical, + "analytical": self.exact_solution(), + "config": { + "scheme": self.config.recon_scheme, + "order": self.config.spatial_order, + "rk_order": self.config.rk_order, + "final_time": self.config.final_time + } + } + + return u_numerical diff --git a/example/1d-linear-convection/weno3/python/05d/time_integration.py b/example/1d-linear-convection/weno3/python/05d/time_integration.py new file mode 100644 index 000000000..af2320cc2 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05d/time_integration.py @@ -0,0 +1,173 @@ +# time_integration.py + +""" +时间推进器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +from abc import ABC, abstractmethod + +# ---------------------- 导入注册系统 ---------------------- +try: + from cfd_core.registry import ComponentRegistry, register_component +except ImportError: + import sys + import os + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + try: + from registry import ComponentRegistry, register_component + print("[time_integration] 使用本地 registry.py 中的注册系统") + except ImportError: + print("[time_integration] 警告: 未找到注册系统,使用极简回退方案") + class ComponentRegistry: + _registries = {} + @classmethod + def register(cls, *args, **kwargs): pass + @classmethod + def create(cls, category, name, *args, **kwargs): + # 回退到硬编码逻辑 + if category == 'integrator': + if name == 'rk1': + return RK1Integrator(*args, **kwargs) + elif name == 'rk2': + return RK2Integrator(*args, **kwargs) + elif name == 'rk3': + return RK3Integrator(*args, **kwargs) + raise ValueError(f"不支持的 {category}.{name}") + def register_component(*args, **kwargs): + def dummy_decorator(cls): + return cls + return dummy_decorator + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = cfd.residual_calculator + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(使用装饰器注册) ---------------------- + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +# ---------------------- 3. 时间推进器工厂(使用注册系统) ---------------------- +class TimeIntegratorFactory: + """时间推进器工厂:根据配置创建对应RK实例""" + + @staticmethod + def create(cfd): + """根据配置创建时间推进器实例""" + rk_order = cfd.config.rk_order + + # 将数字顺序映射为注册系统使用的名字 + # 注意:注册时使用了 'rk1', 'rk2', 'rk3' 这样的名字 + integrator_name = f'rk{rk_order}' + + try: + # 使用注册系统创建实例 + integrator_instance = ComponentRegistry.create('integrator', integrator_name, cfd) + return integrator_instance + except (ValueError, RuntimeError) as e: + # 增强错误信息 + available = [] + try: + all_comps = ComponentRegistry.list_all() + available = all_comps.get('integrator', []) + except: + # 后备列表 + available = ['rk1', 'rk2', 'rk3'] + + # 尝试提供更友好的错误信息 + available_orders = [int(name[2:]) for name in available if name.startswith('rk')] + + raise ValueError( + f"不支持的RK阶数:{rk_order}\n" + f"支持的RK阶数:{available_orders}\n" + f"原始错误:{e}" + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05e/boundary.py b/example/1d-linear-convection/weno3/python/05e/boundary.py new file mode 100644 index 000000000..ae3fe1d97 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05e/boundary.py @@ -0,0 +1,136 @@ +from abc import ABC, abstractmethod + +# ---------------------- 导入注册系统 ---------------------- +try: + # 首先尝试从新的核心位置导入 + from cfd_core.registry import ComponentRegistry, register_component +except ImportError: + # 如果新的核心模块还不存在,使用我们之前定义的简化版本 + # 这将确保代码立即可用,未来再统一迁移到核心模块 + import sys + import os + # 添加当前目录,以便找到可能在同一文件夹的 registry.py + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + try: + from registry import ComponentRegistry, register_component + print("[boundary] 使用本地 registry.py 中的注册系统") + except ImportError: + # 如果完全找不到,提供一个极简定义防止报错(仅用于过渡) + print("[boundary] 警告: 未找到注册系统,使用极简回退方案") + class ComponentRegistry: + _registries = {} + @classmethod + def register(cls, *args, **kwargs): pass + @classmethod + def create(cls, *args, **kwargs): + # 这是一个非常基本的回退,仅用于演示 + # 在实际替换前,您应确保 registry.py 存在 + raise RuntimeError("注册系统未正确初始化。请确保 registry.py 在 Python 路径中。") + def register_component(*args, **kwargs): + def dummy_decorator(cls): + return cls + return dummy_decorator + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界(进口)固定值(从配置读取) + left_value = self.config.get("left_boundary_value", 1.0) + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # 右边界(出口)固定值(从配置读取) + right_value = self.config.get("right_boundary_value", 2.0) + for ig in range(nghosts): + u[ied + ig] = right_value + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + +# ---------------------- 新的边界条件工厂(使用注册系统) ---------------------- +class BoundaryConditionFactory: + """边界条件工厂:根据配置创建对应边界条件实例(新版本,使用注册系统)""" + + @staticmethod + def create(cfd): + """ + 使用注册系统创建边界条件实例。 + 保持与旧版本完全相同的接口,实现无缝替换。 + """ + # 从配置读取边界类型 + bc_type = cfd.config.boundary_type.lower() + + try: + # 使用注册系统创建实例 + # ComponentRegistry.create(类别, 名称, 传递给构造函数的参数) + bc_instance = ComponentRegistry.create('boundary', bc_type, cfd) + return bc_instance + except (ValueError, RuntimeError) as e: + # 增强错误信息,列出可用的选项 + available = [] + try: + # 尝试从注册表获取可用列表 + all_comps = ComponentRegistry.list_all() + available = all_comps.get('boundary', []) + except: + # 如果注册表不可用,使用硬编码列表作为后备 + available = ['periodic', 'dirichlet', 'neumann'] + + raise ValueError( + f"不支持的边界类型:'{bc_type}'\n" + f"可用类型:{available}\n" + f"原始错误:{e}" + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05e/config.py b/example/1d-linear-convection/weno3/python/05e/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05e/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05e/domain.py b/example/1d-linear-convection/weno3/python/05e/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05e/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05e/flux.py b/example/1d-linear-convection/weno3/python/05e/flux.py new file mode 100644 index 000000000..4ad8a8716 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05e/flux.py @@ -0,0 +1,113 @@ +""" +通量计算器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册,替代硬编码工厂 +""" + +from abc import ABC, abstractmethod + +# ---------------------- 导入注册系统 ---------------------- +try: + # 从核心位置或本地导入注册系统 + from cfd_core.registry import ComponentRegistry, register_component +except ImportError: + import sys + import os + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + try: + from registry import ComponentRegistry, register_component + print("[flux] 使用本地 registry.py 中的注册系统") + except ImportError: + # 极简回退方案(实际使用时应该确保registry.py存在) + print("[flux] 警告: 未找到注册系统,使用极简回退方案") + class ComponentRegistry: + _registries = {} + @classmethod + def register(cls, *args, **kwargs): pass + @classmethod + def create(cls, category, name, *args, **kwargs): + # 回退到硬编码逻辑(仅用于过渡) + if category == 'flux' and name == 'rusanov': + return RusanovFluxCalculator(*args, **kwargs) + elif category == 'flux' and name == 'engquist-osher': + return EngquistOsherFluxCalculator(*args, **kwargs) + raise ValueError(f"不支持的 {category}.{name}") + def register_component(*args, **kwargs): + def dummy_decorator(cls): + return cls + return dummy_decorator + +# ---------------------- 1. 抽象通量计算基类(统一接口) ---------------------- +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass + +# ---------------------- 2. 具体通量计算子类(使用装饰器注册) ---------------------- + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + +# ---------------------- 3. 通量计算器工厂(使用注册系统) ---------------------- +class FluxCalculatorFactory: + """通量计算器工厂:根据配置创建对应通量计算器实例""" + @staticmethod + def create(cfd): + """根据配置创建通量计算器实例""" + flux_type = cfd.config.flux_type.lower() + + try: + # 使用注册系统创建实例 + flux_instance = ComponentRegistry.create('flux', flux_type, cfd) + return flux_instance + except (ValueError, RuntimeError) as e: + # 增强错误信息 + available = [] + try: + all_comps = ComponentRegistry.list_all() + available = all_comps.get('flux', []) + except: + # 后备列表 + available = ['rusanov', 'engquist-osher'] + + raise ValueError( + f"不支持的flux类型:'{flux_type}'\n" + f"可用类型:{available}\n" + f"原始错误:{e}" + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05e/initial_condition.py b/example/1d-linear-convection/weno3/python/05e/initial_condition.py new file mode 100644 index 000000000..fac3be5bf --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05e/initial_condition.py @@ -0,0 +1,146 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod + +# ---------------------- 导入注册系统 ---------------------- +try: + from cfd_core.registry import ComponentRegistry, register_component +except ImportError: + import sys + import os + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + try: + from registry import ComponentRegistry, register_component + print("[initial_condition] 使用本地 registry.py 中的注册系统") + except ImportError: + print("[initial_condition] 警告: 未找到注册系统,使用极简回退方案") + class ComponentRegistry: + _registries = {} + @classmethod + def register(cls, *args, **kwargs): pass + @classmethod + def create(cls, category, name, *args, **kwargs): + # 回退到硬编码逻辑 + if category == 'initial_condition': + if name == 'step': + return StepFunctionIC(*args, **kwargs) + elif name == 'sin': + return SineWaveIC(*args, **kwargs) + elif name == 'gaussian': + return GaussianPulseIC(*args, **kwargs) + raise ValueError(f"不支持的 {category}.{name}") + def register_component(*args, **kwargs): + def dummy_decorator(cls): + return cls + return dummy_decorator + + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = self.config.get("domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = self.config.get("pulse_center", 0.5) + width = self.config.get("pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + + +# ---------------------- 3. 初始条件工厂(使用注册系统) ---------------------- +class InitialConditionFactory: + """初始条件工厂:根据配置创建对应初始条件实例""" + + @classmethod + def create(cls, ic_type, config): + """创建初始条件实例""" + ic_type_lower = ic_type.lower() + + try: + # 使用注册系统创建实例 + ic_instance = ComponentRegistry.create('initial_condition', ic_type_lower, config) + return ic_instance + except (ValueError, RuntimeError) as e: + # 增强错误信息 + available = [] + try: + all_comps = ComponentRegistry.list_all() + available = all_comps.get('initial_condition', []) + except: + # 后备列表 + available = ['step', 'sin', 'gaussian'] + + raise ValueError( + f"未知的初始条件类型: '{ic_type}'\n" + f"支持的类型: {available}\n" + f"原始错误: {e}" + ) + + # 保持原有的注册方法,用于向后兼容或动态注册 + _registry = {} # 旧式注册表,可能被其他代码使用 + + @classmethod + def register(cls, name, ic_class): + """注册初始条件类(兼容旧接口)""" + cls._registry[name] = ic_class + # 同时注册到新系统(如果可能) + try: + ComponentRegistry.register('initial_condition', name, ic_class) + except: + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05e/mesh.py b/example/1d-linear-convection/weno3/python/05e/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05e/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05e/plotter.py b/example/1d-linear-convection/weno3/python/05e/plotter.py new file mode 100644 index 000000000..9f1a414fd --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05e/plotter.py @@ -0,0 +1,107 @@ +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05e/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python/05e/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05e/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05e/reconstructor/base.py b/example/1d-linear-convection/weno3/python/05e/reconstructor/base.py new file mode 100644 index 000000000..3cb4763dd --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05e/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def reconstruct(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05e/reconstructor/eno.py b/example/1d-linear-convection/weno3/python/05e/reconstructor/eno.py new file mode 100644 index 000000000..74e0feae0 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05e/reconstructor/eno.py @@ -0,0 +1,108 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor + +# 导入注册系统 +try: + from cfd_core.registry import ComponentRegistry, register_component +except ImportError: + import sys + import os + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + try: + from registry import ComponentRegistry, register_component + print("[reconstructor.eno] 使用本地 registry.py 中的注册系统") + except ImportError: + print("[reconstructor.eno] 警告: 未找到注册系统") + def register_component(*args, **kwargs): + def dummy_decorator(cls): + return cls + return dummy_decorator + + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def reconstruct(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05e/reconstructor/factory.py b/example/1d-linear-convection/weno3/python/05e/reconstructor/factory.py new file mode 100644 index 000000000..3c39ec3fa --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05e/reconstructor/factory.py @@ -0,0 +1,66 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor + +# 导入注册系统 +try: + from cfd_core.registry import ComponentRegistry +except ImportError: + import sys + import os + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + try: + from registry import ComponentRegistry + print("[reconstructor.factory] 使用本地 registry.py 中的注册系统") + except ImportError: + print("[reconstructor.factory] 警告: 未找到注册系统") + # 提供一个回退方案 + class ComponentRegistry: + @staticmethod + def create(category, name, *args, **kwargs): + if category == 'reconstructor': + if name == 'eno': + return EnoReconstructor(*args, **kwargs) + elif name == 'weno3': + return Weno3Reconstructor(*args, **kwargs) + raise ValueError(f"不支持的 {category}.{name}") + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + print(f"[ReconstructorFactory] 请求创建重建器: {scheme}") + + try: + # 根据具体类型传递参数 + if scheme == "eno": + if order is None: + order = 3 + # ENO 需要 order 和 ntcells + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # ✅ 关键修复:WENO3 不需要参数! + return ComponentRegistry.create('reconstructor', scheme) # 没有参数! + else: + # 其他情况默认传递 config + return ComponentRegistry.create('reconstructor', scheme, config) + + except Exception as e: + print(f"注册系统失败,使用硬编码: {e}") + # 硬编码回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + return Weno3Reconstructor() # ✅ 无参数! + else: + raise ValueError(f"不支持的重建格式:{scheme}") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05e/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python/05e/reconstructor/weno3.py new file mode 100644 index 000000000..8aa67ee17 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05e/reconstructor/weno3.py @@ -0,0 +1,105 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor + +# 导入注册系统 +try: + from cfd_core.registry import ComponentRegistry, register_component +except ImportError: + import sys + import os + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + try: + from registry import ComponentRegistry, register_component + print("[reconstructor.weno3] 使用本地 registry.py 中的注册系统") + except ImportError: + print("[reconstructor.weno3] 警告: 未找到注册系统") + def register_component(*args, **kwargs): + def dummy_decorator(cls): + return cls + return dummy_decorator + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def reconstruct(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + """ + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 # smoothness indicator for stencil [v2, v3] + beta1 = (v2 - v1)**2 # smoothness indicator for stencil [v1, v2] + + d0, d1 = 2/3, 1/3 # optimal linear weights (for right value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 0.5 * v2 + 0.5 * v3 # reconstruction from [v2, v3] + q1 = -0.5 * v1 + 1.5 * v2 # reconstruction from [v1, v2] + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 + beta1 = (v2 - v1)**2 + + d0, d1 = 1/3, 2/3 # optimal linear weights (for left value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 1.5 * v2 - 0.5 * v3 # from [v2, v3] + q1 = 0.5 * v1 + 0.5 * v2 # from [v1, v2] + return w0 * q0 + w1 * q1 + """ diff --git a/example/1d-linear-convection/weno3/python/05e/registry.py b/example/1d-linear-convection/weno3/python/05e/registry.py new file mode 100644 index 000000000..f5bd068a3 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05e/registry.py @@ -0,0 +1,56 @@ +""" +CFD组件注册系统核心 +完全独立,不依赖任何现有代码 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表 - 替代硬编码工厂""" + + # 存储所有注册的组件 {类别: {名称: 类}} + _registries: Dict[str, Dict[str, Type]] = {} + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册一个组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + cls._registries[category][name] = component_class + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取已注册的组件类""" + if category not in cls._registries: + raise ValueError(f"❌ 未知类别: {category}") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower().replace('boundary', '').replace('flux', '') + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05e/residual.py b/example/1d-linear-convection/weno3/python/05e/residual.py new file mode 100644 index 000000000..b4d4d7dcc --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05e/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux import FluxCalculatorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + self.reconstructor = self.cfd.reconstructor + + # 初始化通量计算器(工厂创建) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._reconstruct() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _reconstruct(self): + """私有方法:界面值重建""" + self.reconstructor.reconstruct(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05e/run_eno_weno.py b/example/1d-linear-convection/weno3/python/05e/run_eno_weno.py new file mode 100644 index 000000000..fd7f38744 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05e/run_eno_weno.py @@ -0,0 +1,52 @@ +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + #mesh = Mesh(ncells=100, L=2.0) + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行ENO3求解(使用你的链式调用) + print("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + config_eno3.with_reconstruction("eno", 3) # 显式指定3阶(也可省略,ENO默认3阶) + # 可选:覆盖默认值(如dt) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + cfd_eno3.run() # 求解并生成result字典 + + # 可选:快速验证ENO3结果 + # plotter.plot_quick(cfd_eno3.result, title="ENO3 Quick Check") + + # 3. 配置并运行WENO3求解(注意:WENO默认5阶,这里显式指定3阶) + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) # 显式指定3阶(默认是5阶) + # 可选:覆盖默认值 + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + cfd_weno3.run() + + # 4. 可选:保存结果(供离线绘图) + # cfd_eno3.save_result("eno3_result.npz") + # cfd_weno3.save_result("weno3_result.npz") + + # 5. 绘制ENO/WENO对比图 + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" # 可选:保存图片 + ) + +if __name__ == "__main__": + # 主程序入口 + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05e/solution.py b/example/1d-linear-convection/weno3/python/05e/solution.py new file mode 100644 index 000000000..92a46d3f4 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05e/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config.ic_type, config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05e/solver.py b/example/1d-linear-convection/weno3/python/05e/solver.py new file mode 100644 index 000000000..b2024d2c1 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05e/solver.py @@ -0,0 +1,90 @@ +import numpy as np +import matplotlib.pyplot as plt +import inflect +from abc import ABC, abstractmethod + +# Flux +from flux import InviscidFluxCalculator, RusanovFluxCalculator, EngquistOsherFluxCalculator, FluxCalculatorFactory + +# Boundary +from boundary import BoundaryCondition, PeriodicBoundary, DirichletBoundary, NeumannBoundary, BoundaryConditionFactory + +# Time integration +from time_integration import TimeIntegrator, RK1Integrator, RK2Integrator, RK3Integrator, TimeIntegratorFactory + +# Mesh 👈 新增这一行 +from mesh import Mesh + +#from reconstructor import Reconstructor, EnoReconstructor, WenoReconstructor, ReconstructorFactory +from reconstructor import ReconstructorFactory + +from initial_condition import InitialCondition, StepFunctionIC, SineWaveIC, GaussianPulseIC, InitialConditionFactory + +from domain import Domain +from solution import Solution # 👈 新增 + +from config import CfdConfig +from residual import ResidualCalculator + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, config, mesh): + self.config = config + self.domain = Domain(config, mesh) + self.solution = Solution(config, self.domain) + self.reconstructor = ReconstructorFactory.create(config, self.domain) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + def exact_solution(self): + """通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界""" + x = self.domain.mesh.xcc + T = self.config.final_time + c = self.config.wave_speed + L = self.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = InitialConditionFactory.create(self.config.ic_type, self.config) + return ic.evaluate_at(x_shifted) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + #runge_kutta(self) + self.integrator.step(dt) + t += dt + config.dt = dt_old + + # 整理标准化结果 + u_numerical = self.solution.u[self.domain.ist:self.domain.ied].copy() + self.result = { + "x": domain.mesh.xcc, + "numerical": u_numerical, + "analytical": self.exact_solution(), + "config": { + "scheme": self.config.recon_scheme, + "order": self.config.spatial_order, + "rk_order": self.config.rk_order, + "final_time": self.config.final_time + } + } + + return u_numerical diff --git a/example/1d-linear-convection/weno3/python/05e/time_integration.py b/example/1d-linear-convection/weno3/python/05e/time_integration.py new file mode 100644 index 000000000..af2320cc2 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05e/time_integration.py @@ -0,0 +1,173 @@ +# time_integration.py + +""" +时间推进器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +from abc import ABC, abstractmethod + +# ---------------------- 导入注册系统 ---------------------- +try: + from cfd_core.registry import ComponentRegistry, register_component +except ImportError: + import sys + import os + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + try: + from registry import ComponentRegistry, register_component + print("[time_integration] 使用本地 registry.py 中的注册系统") + except ImportError: + print("[time_integration] 警告: 未找到注册系统,使用极简回退方案") + class ComponentRegistry: + _registries = {} + @classmethod + def register(cls, *args, **kwargs): pass + @classmethod + def create(cls, category, name, *args, **kwargs): + # 回退到硬编码逻辑 + if category == 'integrator': + if name == 'rk1': + return RK1Integrator(*args, **kwargs) + elif name == 'rk2': + return RK2Integrator(*args, **kwargs) + elif name == 'rk3': + return RK3Integrator(*args, **kwargs) + raise ValueError(f"不支持的 {category}.{name}") + def register_component(*args, **kwargs): + def dummy_decorator(cls): + return cls + return dummy_decorator + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = cfd.residual_calculator + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(使用装饰器注册) ---------------------- + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +# ---------------------- 3. 时间推进器工厂(使用注册系统) ---------------------- +class TimeIntegratorFactory: + """时间推进器工厂:根据配置创建对应RK实例""" + + @staticmethod + def create(cfd): + """根据配置创建时间推进器实例""" + rk_order = cfd.config.rk_order + + # 将数字顺序映射为注册系统使用的名字 + # 注意:注册时使用了 'rk1', 'rk2', 'rk3' 这样的名字 + integrator_name = f'rk{rk_order}' + + try: + # 使用注册系统创建实例 + integrator_instance = ComponentRegistry.create('integrator', integrator_name, cfd) + return integrator_instance + except (ValueError, RuntimeError) as e: + # 增强错误信息 + available = [] + try: + all_comps = ComponentRegistry.list_all() + available = all_comps.get('integrator', []) + except: + # 后备列表 + available = ['rk1', 'rk2', 'rk3'] + + # 尝试提供更友好的错误信息 + available_orders = [int(name[2:]) for name in available if name.startswith('rk')] + + raise ValueError( + f"不支持的RK阶数:{rk_order}\n" + f"支持的RK阶数:{available_orders}\n" + f"原始错误:{e}" + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05f/boundary.py b/example/1d-linear-convection/weno3/python/05f/boundary.py new file mode 100644 index 000000000..7bfacf2a8 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05f/boundary.py @@ -0,0 +1,151 @@ +from abc import ABC, abstractmethod + +# ---------------------- 导入注册系统 ---------------------- +try: + # 首先尝试从新的核心位置导入 + from cfd_core.registry import ComponentRegistry, register_component +except ImportError: + # 如果新的核心模块还不存在,使用我们之前定义的简化版本 + # 这将确保代码立即可用,未来再统一迁移到核心模块 + import sys + import os + # 添加当前目录,以便找到可能在同一文件夹的 registry.py + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + try: + from registry import ComponentRegistry, register_component + print("[boundary] 使用本地 registry.py 中的注册系统") + except ImportError: + # 如果完全找不到,提供一个极简定义防止报错(仅用于过渡) + print("[boundary] 警告: 未找到注册系统,使用极简回退方案") + class ComponentRegistry: + _registries = {} + @classmethod + def register(cls, *args, **kwargs): pass + @classmethod + def create(cls, *args, **kwargs): + # 这是一个非常基本的回退,仅用于演示 + # 在实际替换前,您应确保 registry.py 存在 + raise RuntimeError("注册系统未正确初始化。请确保 registry.py 在 Python 路径中。") + def register_component(*args, **kwargs): + def dummy_decorator(cls): + return cls + return dummy_decorator + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +# ---------------------- 新的边界条件工厂(使用注册系统) ---------------------- +class BoundaryConditionFactory: + """边界条件工厂:根据配置创建对应边界条件实例(新版本,使用注册系统)""" + + @staticmethod + def create(cfd): + """ + 使用注册系统创建边界条件实例。 + 保持与旧版本完全相同的接口,实现无缝替换。 + """ + # 从配置读取边界类型 + bc_type = cfd.config.boundary_type.lower() + + try: + # 使用注册系统创建实例 + # ComponentRegistry.create(类别, 名称, 传递给构造函数的参数) + bc_instance = ComponentRegistry.create('boundary', bc_type, cfd) + return bc_instance + except (ValueError, RuntimeError) as e: + # 增强错误信息,列出可用的选项 + available = [] + try: + # 尝试从注册表获取可用列表 + all_comps = ComponentRegistry.list_all() + available = all_comps.get('boundary', []) + except: + # 如果注册表不可用,使用硬编码列表作为后备 + available = ['periodic', 'dirichlet', 'neumann'] + + raise ValueError( + f"不支持的边界类型:'{bc_type}'\n" + f"可用类型:{available}\n" + f"原始错误:{e}" + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05f/config.py b/example/1d-linear-convection/weno3/python/05f/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05f/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05f/domain.py b/example/1d-linear-convection/weno3/python/05f/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05f/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05f/flux.py b/example/1d-linear-convection/weno3/python/05f/flux.py new file mode 100644 index 000000000..4ad8a8716 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05f/flux.py @@ -0,0 +1,113 @@ +""" +通量计算器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册,替代硬编码工厂 +""" + +from abc import ABC, abstractmethod + +# ---------------------- 导入注册系统 ---------------------- +try: + # 从核心位置或本地导入注册系统 + from cfd_core.registry import ComponentRegistry, register_component +except ImportError: + import sys + import os + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + try: + from registry import ComponentRegistry, register_component + print("[flux] 使用本地 registry.py 中的注册系统") + except ImportError: + # 极简回退方案(实际使用时应该确保registry.py存在) + print("[flux] 警告: 未找到注册系统,使用极简回退方案") + class ComponentRegistry: + _registries = {} + @classmethod + def register(cls, *args, **kwargs): pass + @classmethod + def create(cls, category, name, *args, **kwargs): + # 回退到硬编码逻辑(仅用于过渡) + if category == 'flux' and name == 'rusanov': + return RusanovFluxCalculator(*args, **kwargs) + elif category == 'flux' and name == 'engquist-osher': + return EngquistOsherFluxCalculator(*args, **kwargs) + raise ValueError(f"不支持的 {category}.{name}") + def register_component(*args, **kwargs): + def dummy_decorator(cls): + return cls + return dummy_decorator + +# ---------------------- 1. 抽象通量计算基类(统一接口) ---------------------- +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass + +# ---------------------- 2. 具体通量计算子类(使用装饰器注册) ---------------------- + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + +# ---------------------- 3. 通量计算器工厂(使用注册系统) ---------------------- +class FluxCalculatorFactory: + """通量计算器工厂:根据配置创建对应通量计算器实例""" + @staticmethod + def create(cfd): + """根据配置创建通量计算器实例""" + flux_type = cfd.config.flux_type.lower() + + try: + # 使用注册系统创建实例 + flux_instance = ComponentRegistry.create('flux', flux_type, cfd) + return flux_instance + except (ValueError, RuntimeError) as e: + # 增强错误信息 + available = [] + try: + all_comps = ComponentRegistry.list_all() + available = all_comps.get('flux', []) + except: + # 后备列表 + available = ['rusanov', 'engquist-osher'] + + raise ValueError( + f"不支持的flux类型:'{flux_type}'\n" + f"可用类型:{available}\n" + f"原始错误:{e}" + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05f/initial_condition.py b/example/1d-linear-convection/weno3/python/05f/initial_condition.py new file mode 100644 index 000000000..fac3be5bf --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05f/initial_condition.py @@ -0,0 +1,146 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod + +# ---------------------- 导入注册系统 ---------------------- +try: + from cfd_core.registry import ComponentRegistry, register_component +except ImportError: + import sys + import os + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + try: + from registry import ComponentRegistry, register_component + print("[initial_condition] 使用本地 registry.py 中的注册系统") + except ImportError: + print("[initial_condition] 警告: 未找到注册系统,使用极简回退方案") + class ComponentRegistry: + _registries = {} + @classmethod + def register(cls, *args, **kwargs): pass + @classmethod + def create(cls, category, name, *args, **kwargs): + # 回退到硬编码逻辑 + if category == 'initial_condition': + if name == 'step': + return StepFunctionIC(*args, **kwargs) + elif name == 'sin': + return SineWaveIC(*args, **kwargs) + elif name == 'gaussian': + return GaussianPulseIC(*args, **kwargs) + raise ValueError(f"不支持的 {category}.{name}") + def register_component(*args, **kwargs): + def dummy_decorator(cls): + return cls + return dummy_decorator + + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = self.config.get("domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = self.config.get("pulse_center", 0.5) + width = self.config.get("pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + + +# ---------------------- 3. 初始条件工厂(使用注册系统) ---------------------- +class InitialConditionFactory: + """初始条件工厂:根据配置创建对应初始条件实例""" + + @classmethod + def create(cls, ic_type, config): + """创建初始条件实例""" + ic_type_lower = ic_type.lower() + + try: + # 使用注册系统创建实例 + ic_instance = ComponentRegistry.create('initial_condition', ic_type_lower, config) + return ic_instance + except (ValueError, RuntimeError) as e: + # 增强错误信息 + available = [] + try: + all_comps = ComponentRegistry.list_all() + available = all_comps.get('initial_condition', []) + except: + # 后备列表 + available = ['step', 'sin', 'gaussian'] + + raise ValueError( + f"未知的初始条件类型: '{ic_type}'\n" + f"支持的类型: {available}\n" + f"原始错误: {e}" + ) + + # 保持原有的注册方法,用于向后兼容或动态注册 + _registry = {} # 旧式注册表,可能被其他代码使用 + + @classmethod + def register(cls, name, ic_class): + """注册初始条件类(兼容旧接口)""" + cls._registry[name] = ic_class + # 同时注册到新系统(如果可能) + try: + ComponentRegistry.register('initial_condition', name, ic_class) + except: + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05f/mesh.py b/example/1d-linear-convection/weno3/python/05f/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05f/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05f/plotter.py b/example/1d-linear-convection/weno3/python/05f/plotter.py new file mode 100644 index 000000000..9f1a414fd --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05f/plotter.py @@ -0,0 +1,107 @@ +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05f/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python/05f/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05f/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05f/reconstructor/base.py b/example/1d-linear-convection/weno3/python/05f/reconstructor/base.py new file mode 100644 index 000000000..3cb4763dd --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05f/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def reconstruct(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05f/reconstructor/eno.py b/example/1d-linear-convection/weno3/python/05f/reconstructor/eno.py new file mode 100644 index 000000000..74e0feae0 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05f/reconstructor/eno.py @@ -0,0 +1,108 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor + +# 导入注册系统 +try: + from cfd_core.registry import ComponentRegistry, register_component +except ImportError: + import sys + import os + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + try: + from registry import ComponentRegistry, register_component + print("[reconstructor.eno] 使用本地 registry.py 中的注册系统") + except ImportError: + print("[reconstructor.eno] 警告: 未找到注册系统") + def register_component(*args, **kwargs): + def dummy_decorator(cls): + return cls + return dummy_decorator + + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def reconstruct(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05f/reconstructor/factory.py b/example/1d-linear-convection/weno3/python/05f/reconstructor/factory.py new file mode 100644 index 000000000..3c39ec3fa --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05f/reconstructor/factory.py @@ -0,0 +1,66 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor + +# 导入注册系统 +try: + from cfd_core.registry import ComponentRegistry +except ImportError: + import sys + import os + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + try: + from registry import ComponentRegistry + print("[reconstructor.factory] 使用本地 registry.py 中的注册系统") + except ImportError: + print("[reconstructor.factory] 警告: 未找到注册系统") + # 提供一个回退方案 + class ComponentRegistry: + @staticmethod + def create(category, name, *args, **kwargs): + if category == 'reconstructor': + if name == 'eno': + return EnoReconstructor(*args, **kwargs) + elif name == 'weno3': + return Weno3Reconstructor(*args, **kwargs) + raise ValueError(f"不支持的 {category}.{name}") + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + print(f"[ReconstructorFactory] 请求创建重建器: {scheme}") + + try: + # 根据具体类型传递参数 + if scheme == "eno": + if order is None: + order = 3 + # ENO 需要 order 和 ntcells + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # ✅ 关键修复:WENO3 不需要参数! + return ComponentRegistry.create('reconstructor', scheme) # 没有参数! + else: + # 其他情况默认传递 config + return ComponentRegistry.create('reconstructor', scheme, config) + + except Exception as e: + print(f"注册系统失败,使用硬编码: {e}") + # 硬编码回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + return Weno3Reconstructor() # ✅ 无参数! + else: + raise ValueError(f"不支持的重建格式:{scheme}") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05f/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python/05f/reconstructor/weno3.py new file mode 100644 index 000000000..8aa67ee17 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05f/reconstructor/weno3.py @@ -0,0 +1,105 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor + +# 导入注册系统 +try: + from cfd_core.registry import ComponentRegistry, register_component +except ImportError: + import sys + import os + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + try: + from registry import ComponentRegistry, register_component + print("[reconstructor.weno3] 使用本地 registry.py 中的注册系统") + except ImportError: + print("[reconstructor.weno3] 警告: 未找到注册系统") + def register_component(*args, **kwargs): + def dummy_decorator(cls): + return cls + return dummy_decorator + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def reconstruct(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + """ + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 # smoothness indicator for stencil [v2, v3] + beta1 = (v2 - v1)**2 # smoothness indicator for stencil [v1, v2] + + d0, d1 = 2/3, 1/3 # optimal linear weights (for right value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 0.5 * v2 + 0.5 * v3 # reconstruction from [v2, v3] + q1 = -0.5 * v1 + 1.5 * v2 # reconstruction from [v1, v2] + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 + beta1 = (v2 - v1)**2 + + d0, d1 = 1/3, 2/3 # optimal linear weights (for left value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 1.5 * v2 - 0.5 * v3 # from [v2, v3] + q1 = 0.5 * v1 + 0.5 * v2 # from [v1, v2] + return w0 * q0 + w1 * q1 + """ diff --git a/example/1d-linear-convection/weno3/python/05f/registry.py b/example/1d-linear-convection/weno3/python/05f/registry.py new file mode 100644 index 000000000..f5bd068a3 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05f/registry.py @@ -0,0 +1,56 @@ +""" +CFD组件注册系统核心 +完全独立,不依赖任何现有代码 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表 - 替代硬编码工厂""" + + # 存储所有注册的组件 {类别: {名称: 类}} + _registries: Dict[str, Dict[str, Type]] = {} + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册一个组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + cls._registries[category][name] = component_class + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取已注册的组件类""" + if category not in cls._registries: + raise ValueError(f"❌ 未知类别: {category}") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower().replace('boundary', '').replace('flux', '') + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05f/residual.py b/example/1d-linear-convection/weno3/python/05f/residual.py new file mode 100644 index 000000000..b4d4d7dcc --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05f/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux import FluxCalculatorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + self.reconstructor = self.cfd.reconstructor + + # 初始化通量计算器(工厂创建) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._reconstruct() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _reconstruct(self): + """私有方法:界面值重建""" + self.reconstructor.reconstruct(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05f/run_eno_weno.py b/example/1d-linear-convection/weno3/python/05f/run_eno_weno.py new file mode 100644 index 000000000..fd7f38744 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05f/run_eno_weno.py @@ -0,0 +1,52 @@ +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + #mesh = Mesh(ncells=100, L=2.0) + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行ENO3求解(使用你的链式调用) + print("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + config_eno3.with_reconstruction("eno", 3) # 显式指定3阶(也可省略,ENO默认3阶) + # 可选:覆盖默认值(如dt) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + cfd_eno3.run() # 求解并生成result字典 + + # 可选:快速验证ENO3结果 + # plotter.plot_quick(cfd_eno3.result, title="ENO3 Quick Check") + + # 3. 配置并运行WENO3求解(注意:WENO默认5阶,这里显式指定3阶) + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) # 显式指定3阶(默认是5阶) + # 可选:覆盖默认值 + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + cfd_weno3.run() + + # 4. 可选:保存结果(供离线绘图) + # cfd_eno3.save_result("eno3_result.npz") + # cfd_weno3.save_result("weno3_result.npz") + + # 5. 绘制ENO/WENO对比图 + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" # 可选:保存图片 + ) + +if __name__ == "__main__": + # 主程序入口 + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05f/solution.py b/example/1d-linear-convection/weno3/python/05f/solution.py new file mode 100644 index 000000000..92a46d3f4 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05f/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config.ic_type, config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05f/solver.py b/example/1d-linear-convection/weno3/python/05f/solver.py new file mode 100644 index 000000000..b2024d2c1 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05f/solver.py @@ -0,0 +1,90 @@ +import numpy as np +import matplotlib.pyplot as plt +import inflect +from abc import ABC, abstractmethod + +# Flux +from flux import InviscidFluxCalculator, RusanovFluxCalculator, EngquistOsherFluxCalculator, FluxCalculatorFactory + +# Boundary +from boundary import BoundaryCondition, PeriodicBoundary, DirichletBoundary, NeumannBoundary, BoundaryConditionFactory + +# Time integration +from time_integration import TimeIntegrator, RK1Integrator, RK2Integrator, RK3Integrator, TimeIntegratorFactory + +# Mesh 👈 新增这一行 +from mesh import Mesh + +#from reconstructor import Reconstructor, EnoReconstructor, WenoReconstructor, ReconstructorFactory +from reconstructor import ReconstructorFactory + +from initial_condition import InitialCondition, StepFunctionIC, SineWaveIC, GaussianPulseIC, InitialConditionFactory + +from domain import Domain +from solution import Solution # 👈 新增 + +from config import CfdConfig +from residual import ResidualCalculator + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, config, mesh): + self.config = config + self.domain = Domain(config, mesh) + self.solution = Solution(config, self.domain) + self.reconstructor = ReconstructorFactory.create(config, self.domain) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + def exact_solution(self): + """通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界""" + x = self.domain.mesh.xcc + T = self.config.final_time + c = self.config.wave_speed + L = self.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = InitialConditionFactory.create(self.config.ic_type, self.config) + return ic.evaluate_at(x_shifted) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + #runge_kutta(self) + self.integrator.step(dt) + t += dt + config.dt = dt_old + + # 整理标准化结果 + u_numerical = self.solution.u[self.domain.ist:self.domain.ied].copy() + self.result = { + "x": domain.mesh.xcc, + "numerical": u_numerical, + "analytical": self.exact_solution(), + "config": { + "scheme": self.config.recon_scheme, + "order": self.config.spatial_order, + "rk_order": self.config.rk_order, + "final_time": self.config.final_time + } + } + + return u_numerical diff --git a/example/1d-linear-convection/weno3/python/05f/test_all_boundaries.py b/example/1d-linear-convection/weno3/python/05f/test_all_boundaries.py new file mode 100644 index 000000000..68b0f2fda --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05f/test_all_boundaries.py @@ -0,0 +1,68 @@ +# test_all_boundaries_fixed.py +import numpy as np +from config import CfdConfig +from mesh import Mesh +from domain import Domain +from solution import Solution +from boundary import BoundaryConditionFactory + +print("=== 测试所有边界条件 (修复版) ===") + +config = CfdConfig() +mesh = Mesh() +domain = Domain(config, mesh) +solution = Solution(config, domain) + +# 测试每种边界条件 +boundary_types = ['periodic', 'dirichlet', 'neumann'] + +for bc_type in boundary_types: + print(f"\n测试 {bc_type.upper()} 边界:") + + # 创建新的配置对象,避免属性污染 + config = CfdConfig() + config.boundary_type = bc_type + + # ✅ 确保设置正确的属性(不是字典键) + if bc_type == 'dirichlet': + config.left_boundary_value = 1.0 # 直接设置属性 + config.right_boundary_value = 2.0 # 直接设置属性 + + mesh = Mesh() + domain = Domain(config, mesh) + + # 创建模拟CFD对象 + class MockCFD: + def __init__(self, config, domain): + self.config = config + self.domain = domain + + cfd = MockCFD(config, domain) + + # 创建边界条件 + try: + bc = BoundaryConditionFactory.create(cfd) + print(f" ✅ 创建: {type(bc).__name__}") + + # 应用边界条件 + u = np.ones(domain.ntcells) * 5.0 + bc.apply(u) + print(f" ✅ 应用成功") + + # 简单验证 + if bc_type == 'periodic': + print(f" 左ghost层: {u[:domain.nghosts]}") + print(f" 右ghost层: {u[-domain.nghosts:]}") + elif bc_type == 'dirichlet': + print(f" 左边界值: {u[:domain.nghosts]} (应接近1.0)") + print(f" 右边界值: {u[-domain.nghosts:]} (应接近2.0)") + elif bc_type == 'neumann': + print(f" 左边界梯度: {u[domain.nghosts-1]} = {u[domain.nghosts]}") + print(f" 右边界梯度: {u[-domain.nghosts]} = {u[-domain.nghosts-1]}") + + except Exception as e: + print(f" ❌ 失败: {type(e).__name__}: {e}") + import traceback + traceback.print_exc() + +print("\n=== 测试完成 ===") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05f/test_registry_complete.py b/example/1d-linear-convection/weno3/python/05f/test_registry_complete.py new file mode 100644 index 000000000..16bdd211e --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05f/test_registry_complete.py @@ -0,0 +1,75 @@ +# test_registry_complete.py +import sys +import os +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +print("=== 完整的注册系统测试 ===") + +# 导入所有模块以触发注册 +print("\n1. 导入所有CFD模块(触发注册):") +modules = [ + 'boundary', + 'flux', + 'initial_condition', + 'time_integration', + # reconstructor 已经在导入时注册 +] + +for module in modules: + try: + __import__(module) + print(f" ✅ {module}") + except ImportError as e: + print(f" ❌ {module}: {e}") + +# 检查注册表 +print("\n2. 检查注册表内容:") + +from registry import ComponentRegistry + +all_components = ComponentRegistry.list_all() +total_components = 0 + +for category, components in all_components.items(): + print(f"\n {category.upper()} ({len(components)}种):") + for name in sorted(components): + try: + cls = ComponentRegistry.get(category, name) + print(f" - {name}: {cls.__name__}") + total_components += 1 + except: + print(f" - {name}: <无法获取类>") + +print(f"\n总计: {total_components} 个组件已注册") +print("\n3. 测试创建每个类别的组件:") + +# 测试数据 +class MockConfig: + boundary_type = 'periodic' + flux_type = 'rusanov' + recon_scheme = 'eno' + rk_order = 2 + ic_type = 'step' + spatial_order = 3 + +class MockCFD: + def __init__(self): + self.config = MockConfig() + self.domain = type('Domain', (), {'ntcells': 50, 'mesh': type('Mesh', (), {'nnodes': 10})()})() + +# 测试每个类别 +test_cases = [ + ('boundary', 'periodic', MockCFD()), + ('flux', 'rusanov', MockCFD()), + ('initial_condition', 'step', MockConfig()), + ('integrator', 'rk2', MockCFD()), +] + +for category, name, arg in test_cases: + try: + instance = ComponentRegistry.create(category, name, arg) + print(f" ✅ {category}.{name}: 创建成功 -> {type(instance).__name__}") + except Exception as e: + print(f" ❌ {category}.{name}: 创建失败 -> {type(e).__name__}: {e}") + +print("\n✅ 注册系统测试完成!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/05f/time_integration.py b/example/1d-linear-convection/weno3/python/05f/time_integration.py new file mode 100644 index 000000000..af2320cc2 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/05f/time_integration.py @@ -0,0 +1,173 @@ +# time_integration.py + +""" +时间推进器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +from abc import ABC, abstractmethod + +# ---------------------- 导入注册系统 ---------------------- +try: + from cfd_core.registry import ComponentRegistry, register_component +except ImportError: + import sys + import os + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + try: + from registry import ComponentRegistry, register_component + print("[time_integration] 使用本地 registry.py 中的注册系统") + except ImportError: + print("[time_integration] 警告: 未找到注册系统,使用极简回退方案") + class ComponentRegistry: + _registries = {} + @classmethod + def register(cls, *args, **kwargs): pass + @classmethod + def create(cls, category, name, *args, **kwargs): + # 回退到硬编码逻辑 + if category == 'integrator': + if name == 'rk1': + return RK1Integrator(*args, **kwargs) + elif name == 'rk2': + return RK2Integrator(*args, **kwargs) + elif name == 'rk3': + return RK3Integrator(*args, **kwargs) + raise ValueError(f"不支持的 {category}.{name}") + def register_component(*args, **kwargs): + def dummy_decorator(cls): + return cls + return dummy_decorator + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = cfd.residual_calculator + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(使用装饰器注册) ---------------------- + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +# ---------------------- 3. 时间推进器工厂(使用注册系统) ---------------------- +class TimeIntegratorFactory: + """时间推进器工厂:根据配置创建对应RK实例""" + + @staticmethod + def create(cfd): + """根据配置创建时间推进器实例""" + rk_order = cfd.config.rk_order + + # 将数字顺序映射为注册系统使用的名字 + # 注意:注册时使用了 'rk1', 'rk2', 'rk3' 这样的名字 + integrator_name = f'rk{rk_order}' + + try: + # 使用注册系统创建实例 + integrator_instance = ComponentRegistry.create('integrator', integrator_name, cfd) + return integrator_instance + except (ValueError, RuntimeError) as e: + # 增强错误信息 + available = [] + try: + all_comps = ComponentRegistry.list_all() + available = all_comps.get('integrator', []) + except: + # 后备列表 + available = ['rk1', 'rk2', 'rk3'] + + # 尝试提供更友好的错误信息 + available_orders = [int(name[2:]) for name in available if name.startswith('rk')] + + raise ValueError( + f"不支持的RK阶数:{rk_order}\n" + f"支持的RK阶数:{available_orders}\n" + f"原始错误:{e}" + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06/boundary.py b/example/1d-linear-convection/weno3/python/06/boundary.py new file mode 100644 index 000000000..6054f92de --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06/boundary.py @@ -0,0 +1,103 @@ +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from factories.base_factory import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06/cfd_registry.py b/example/1d-linear-convection/weno3/python/06/cfd_registry.py new file mode 100644 index 000000000..c12eb106c --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06/cfd_registry.py @@ -0,0 +1,62 @@ +# cfd_registry.py +""" +CFD注册系统统一导入模块 +所有其他模块从这里导入注册系统 +""" + +import sys +import os + +# 添加当前目录到路径,确保能找到registry.py +current_dir = os.path.dirname(os.path.abspath(__file__)) +if current_dir not in sys.path: + sys.path.insert(0, current_dir) + +try: + # 尝试导入注册系统 + from registry import ComponentRegistry, register_component + REGISTRY_AVAILABLE = True +except ImportError: + # 如果找不到,提供简单的空实现 + print("⚠️ 警告: 未找到 registry.py,使用最小化回退实现") + + class ComponentRegistry: + """最小化的注册系统回退实现""" + _registries = {} + + @classmethod + def register(cls, category, name, component_class): + if category not in cls._registries: + cls._registries[category] = {} + cls._registries[category][name] = component_class + + @classmethod + def get(cls, category, name): + if category not in cls._registries: + raise ValueError(f"未知类别: {category}") + if name not in cls._registries[category]: + raise ValueError(f"未找到: {category}.{name}") + return cls._registries[category][name] + + @classmethod + def create(cls, category, name, *args, **kwargs): + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) + for cat, comps in cls._registries.items()} + + def register_component(category, name=None): + """简化的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + REGISTRY_AVAILABLE = False + +# 导出统一的接口 +__all__ = ['ComponentRegistry', 'register_component', 'REGISTRY_AVAILABLE'] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06/config.py b/example/1d-linear-convection/weno3/python/06/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06/domain.py b/example/1d-linear-convection/weno3/python/06/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06/factories/base_factory.py b/example/1d-linear-convection/weno3/python/06/factories/base_factory.py new file mode 100644 index 000000000..7b8b6105e --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06/factories/base_factory.py @@ -0,0 +1,49 @@ +# factories/base_factory.py +""" +工厂基类 - 提供统一的组件创建接口 +""" + +from cfd_registry import ComponentRegistry +from typing import Any, Optional + +class BaseFactory: + """工厂基类,提供统一的组件创建方法""" + + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + """ + 统一创建组件的方法 + + Args: + category: 组件类别(如'boundary', 'flux'等) + name: 组件名称(如'periodic', 'rusanov'等) + *args, **kwargs: 传递给构造函数的参数 + + Returns: + 创建的组件实例 + + Raises: + ValueError: 如果组件不存在 + """ + name_lower = name.lower() + + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + # 获取可用组件列表 + available = ComponentRegistry.list_all().get(category, []) + + # 构建友好的错误信息 + if available: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"可用类型:{available}") + else: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"({category}类别下没有注册任何组件)") + + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + """获取指定类别的可用组件列表""" + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06/flux.py b/example/1d-linear-convection/weno3/python/06/flux.py new file mode 100644 index 000000000..beb9ed488 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06/flux.py @@ -0,0 +1,73 @@ +""" +通量计算器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册,替代硬编码工厂 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象通量计算基类(统一接口) ---------------------- +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass + +# ---------------------- 2. 具体通量计算子类(使用装饰器注册) ---------------------- + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + +class FluxCalculatorFactory: + """通量计算器工厂""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD对象 + + Returns: + 通量计算器实例 + """ + from factories.base_factory import BaseFactory + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06/initial_condition.py b/example/1d-linear-convection/weno3/python/06/initial_condition.py new file mode 100644 index 000000000..7c568c702 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06/initial_condition.py @@ -0,0 +1,90 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = self.config.get("domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = self.config.get("pulse_center", 0.5) + width = self.config.get("pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, ic_type: str, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from factories.base_factory import BaseFactory + return BaseFactory.create_component('initial_condition', ic_type, config) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06/mesh.py b/example/1d-linear-convection/weno3/python/06/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06/plotter.py b/example/1d-linear-convection/weno3/python/06/plotter.py new file mode 100644 index 000000000..9f1a414fd --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06/plotter.py @@ -0,0 +1,107 @@ +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python/06/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06/reconstructor/base.py b/example/1d-linear-convection/weno3/python/06/reconstructor/base.py new file mode 100644 index 000000000..3cb4763dd --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def reconstruct(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06/reconstructor/eno.py b/example/1d-linear-convection/weno3/python/06/reconstructor/eno.py new file mode 100644 index 000000000..c2fb385dd --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def reconstruct(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06/reconstructor/factory.py b/example/1d-linear-convection/weno3/python/06/reconstructor/factory.py new file mode 100644 index 000000000..efa4a1446 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from cfd_registry import ComponentRegistry + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from factories.base_factory import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python/06/reconstructor/weno3.py new file mode 100644 index 000000000..6e8c3f230 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06/reconstructor/weno3.py @@ -0,0 +1,88 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def reconstruct(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + """ + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 # smoothness indicator for stencil [v2, v3] + beta1 = (v2 - v1)**2 # smoothness indicator for stencil [v1, v2] + + d0, d1 = 2/3, 1/3 # optimal linear weights (for right value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 0.5 * v2 + 0.5 * v3 # reconstruction from [v2, v3] + q1 = -0.5 * v1 + 1.5 * v2 # reconstruction from [v1, v2] + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 + beta1 = (v2 - v1)**2 + + d0, d1 = 1/3, 2/3 # optimal linear weights (for left value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 1.5 * v2 - 0.5 * v3 # from [v2, v3] + q1 = 0.5 * v1 + 0.5 * v2 # from [v1, v2] + return w0 * q0 + w1 * q1 + """ diff --git a/example/1d-linear-convection/weno3/python/06/registry.py b/example/1d-linear-convection/weno3/python/06/registry.py new file mode 100644 index 000000000..61be90500 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06/registry.py @@ -0,0 +1,75 @@ +# registry.py (改进版) +""" +CFD组件注册系统 - 简洁高效版 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表""" + + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True # 控制是否打印注册信息 + + @classmethod + def set_verbose(cls, verbose: bool): + """设置是否打印注册信息""" + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + # 检查是否已注册 + if name in cls._registries[category]: + if cls._registries[category][name] == component_class: + # 相同类重复注册,静默跳过 + return + elif cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取组件类""" + if category not in cls._registries: + available = list(cls._registries.keys()) + raise ValueError(f"❌ 未知类别: {category} (可用类别: {available})") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + @classmethod + def get_count(cls) -> int: + """获取注册组件总数""" + return sum(len(components) for components in cls._registries.values()) + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06/residual.py b/example/1d-linear-convection/weno3/python/06/residual.py new file mode 100644 index 000000000..b4d4d7dcc --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux import FluxCalculatorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + self.reconstructor = self.cfd.reconstructor + + # 初始化通量计算器(工厂创建) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._reconstruct() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _reconstruct(self): + """私有方法:界面值重建""" + self.reconstructor.reconstruct(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06/run_eno_weno.py b/example/1d-linear-convection/weno3/python/06/run_eno_weno.py new file mode 100644 index 000000000..fd7f38744 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06/run_eno_weno.py @@ -0,0 +1,52 @@ +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + #mesh = Mesh(ncells=100, L=2.0) + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行ENO3求解(使用你的链式调用) + print("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + config_eno3.with_reconstruction("eno", 3) # 显式指定3阶(也可省略,ENO默认3阶) + # 可选:覆盖默认值(如dt) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + cfd_eno3.run() # 求解并生成result字典 + + # 可选:快速验证ENO3结果 + # plotter.plot_quick(cfd_eno3.result, title="ENO3 Quick Check") + + # 3. 配置并运行WENO3求解(注意:WENO默认5阶,这里显式指定3阶) + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) # 显式指定3阶(默认是5阶) + # 可选:覆盖默认值 + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + cfd_weno3.run() + + # 4. 可选:保存结果(供离线绘图) + # cfd_eno3.save_result("eno3_result.npz") + # cfd_weno3.save_result("weno3_result.npz") + + # 5. 绘制ENO/WENO对比图 + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" # 可选:保存图片 + ) + +if __name__ == "__main__": + # 主程序入口 + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06/solution.py b/example/1d-linear-convection/weno3/python/06/solution.py new file mode 100644 index 000000000..92a46d3f4 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config.ic_type, config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06/solver.py b/example/1d-linear-convection/weno3/python/06/solver.py new file mode 100644 index 000000000..0d0b442da --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06/solver.py @@ -0,0 +1,86 @@ +import numpy as np +import matplotlib.pyplot as plt +import inflect +from abc import ABC, abstractmethod + + +from flux import InviscidFluxCalculator, RusanovFluxCalculator, EngquistOsherFluxCalculator, FluxCalculatorFactory + +from boundary import BoundaryCondition, PeriodicBoundary, DirichletBoundary, NeumannBoundary, BoundaryConditionFactory + +from time_integration import TimeIntegrator, RK1Integrator, RK2Integrator, RK3Integrator, TimeIntegratorFactory + +from mesh import Mesh + +from reconstructor import ReconstructorFactory + +from initial_condition import InitialCondition, StepFunctionIC, SineWaveIC, GaussianPulseIC, InitialConditionFactory + +from domain import Domain +from solution import Solution + +from config import CfdConfig +from residual import ResidualCalculator + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, config, mesh): + self.config = config + self.domain = Domain(config, mesh) + self.solution = Solution(config, self.domain) + self.reconstructor = ReconstructorFactory.create(config, self.domain) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + def exact_solution(self): + """通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界""" + x = self.domain.mesh.xcc + T = self.config.final_time + c = self.config.wave_speed + L = self.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = InitialConditionFactory.create(self.config.ic_type, self.config) + return ic.evaluate_at(x_shifted) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + #runge_kutta(self) + self.integrator.step(dt) + t += dt + config.dt = dt_old + + # 整理标准化结果 + u_numerical = self.solution.u[self.domain.ist:self.domain.ied].copy() + self.result = { + "x": domain.mesh.xcc, + "numerical": u_numerical, + "analytical": self.exact_solution(), + "config": { + "scheme": self.config.recon_scheme, + "order": self.config.spatial_order, + "rk_order": self.config.rk_order, + "final_time": self.config.final_time + } + } + + return u_numerical diff --git a/example/1d-linear-convection/weno3/python/06/test_simplified_imports.py b/example/1d-linear-convection/weno3/python/06/test_simplified_imports.py new file mode 100644 index 000000000..fccce5f2c --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06/test_simplified_imports.py @@ -0,0 +1,45 @@ +# test_simplified_imports.py +""" +测试简化后的导入系统 +""" + +print("=== 测试简化后的导入系统 ===") + +# 测试导入所有模块 +modules = [ + 'boundary', + 'flux', + 'initial_condition', + 'time_integration', +] + +print("\n1. 导入所有模块:") +for module in modules: + try: + __import__(module) + print(f" ✅ {module}") + except ImportError as e: + print(f" ❌ {module}: {e}") + +# 测试注册系统 +print("\n2. 测试注册系统:") +from cfd_registry import ComponentRegistry + +print("所有注册的组件:") +all_components = ComponentRegistry.list_all() +for category, names in all_components.items(): + print(f" {category}: {names}") + +print(f"\n总计: {ComponentRegistry.get_count()} 个组件") + +# 测试关闭verbose模式 +print("\n3. 测试关闭verbose模式:") +ComponentRegistry.set_verbose(False) + +# 尝试重新导入一个模块(应该不会打印注册信息) +print("重新导入boundary模块(应该静默):") +import importlib +import boundary +importlib.reload(boundary) + +print("\n✅ 简化导入系统测试完成!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06/time_integration.py b/example/1d-linear-convection/weno3/python/06/time_integration.py new file mode 100644 index 000000000..25ac0b4ca --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06/time_integration.py @@ -0,0 +1,125 @@ +# time_integration.py + +""" +时间推进器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = cfd.residual_calculator + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(使用装饰器注册) ---------------------- + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class TimeIntegratorFactory: + """时间推进器工厂""" + + @staticmethod + def create(cfd) -> 'TimeIntegrator': + """ + 创建时间推进器实例 + + Args: + cfd: CFD对象 + + Returns: + 时间推进器实例 + """ + from factories.base_factory import BaseFactory + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06a/Project.toml b/example/1d-linear-convection/weno3/python/06a/Project.toml new file mode 100644 index 000000000..48e08504d --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06a/Project.toml @@ -0,0 +1,2 @@ +[deps] +PythonCall = "6099a3de-0909-46bc-b1f4-468b9a2dfc0d" diff --git a/example/1d-linear-convection/weno3/python/06a/boundary.py b/example/1d-linear-convection/weno3/python/06a/boundary.py new file mode 100644 index 000000000..6054f92de --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06a/boundary.py @@ -0,0 +1,103 @@ +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from factories.base_factory import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06a/cfd_registry.py b/example/1d-linear-convection/weno3/python/06a/cfd_registry.py new file mode 100644 index 000000000..c12eb106c --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06a/cfd_registry.py @@ -0,0 +1,62 @@ +# cfd_registry.py +""" +CFD注册系统统一导入模块 +所有其他模块从这里导入注册系统 +""" + +import sys +import os + +# 添加当前目录到路径,确保能找到registry.py +current_dir = os.path.dirname(os.path.abspath(__file__)) +if current_dir not in sys.path: + sys.path.insert(0, current_dir) + +try: + # 尝试导入注册系统 + from registry import ComponentRegistry, register_component + REGISTRY_AVAILABLE = True +except ImportError: + # 如果找不到,提供简单的空实现 + print("⚠️ 警告: 未找到 registry.py,使用最小化回退实现") + + class ComponentRegistry: + """最小化的注册系统回退实现""" + _registries = {} + + @classmethod + def register(cls, category, name, component_class): + if category not in cls._registries: + cls._registries[category] = {} + cls._registries[category][name] = component_class + + @classmethod + def get(cls, category, name): + if category not in cls._registries: + raise ValueError(f"未知类别: {category}") + if name not in cls._registries[category]: + raise ValueError(f"未找到: {category}.{name}") + return cls._registries[category][name] + + @classmethod + def create(cls, category, name, *args, **kwargs): + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) + for cat, comps in cls._registries.items()} + + def register_component(category, name=None): + """简化的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + REGISTRY_AVAILABLE = False + +# 导出统一的接口 +__all__ = ['ComponentRegistry', 'register_component', 'REGISTRY_AVAILABLE'] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06a/config.py b/example/1d-linear-convection/weno3/python/06a/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06a/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06a/domain.py b/example/1d-linear-convection/weno3/python/06a/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06a/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06a/factories/base_factory.py b/example/1d-linear-convection/weno3/python/06a/factories/base_factory.py new file mode 100644 index 000000000..7b8b6105e --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06a/factories/base_factory.py @@ -0,0 +1,49 @@ +# factories/base_factory.py +""" +工厂基类 - 提供统一的组件创建接口 +""" + +from cfd_registry import ComponentRegistry +from typing import Any, Optional + +class BaseFactory: + """工厂基类,提供统一的组件创建方法""" + + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + """ + 统一创建组件的方法 + + Args: + category: 组件类别(如'boundary', 'flux'等) + name: 组件名称(如'periodic', 'rusanov'等) + *args, **kwargs: 传递给构造函数的参数 + + Returns: + 创建的组件实例 + + Raises: + ValueError: 如果组件不存在 + """ + name_lower = name.lower() + + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + # 获取可用组件列表 + available = ComponentRegistry.list_all().get(category, []) + + # 构建友好的错误信息 + if available: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"可用类型:{available}") + else: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"({category}类别下没有注册任何组件)") + + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + """获取指定类别的可用组件列表""" + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06a/flux.py b/example/1d-linear-convection/weno3/python/06a/flux.py new file mode 100644 index 000000000..beb9ed488 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06a/flux.py @@ -0,0 +1,73 @@ +""" +通量计算器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册,替代硬编码工厂 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象通量计算基类(统一接口) ---------------------- +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass + +# ---------------------- 2. 具体通量计算子类(使用装饰器注册) ---------------------- + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R + +class FluxCalculatorFactory: + """通量计算器工厂""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD对象 + + Returns: + 通量计算器实例 + """ + from factories.base_factory import BaseFactory + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06a/initial_condition.py b/example/1d-linear-convection/weno3/python/06a/initial_condition.py new file mode 100644 index 000000000..dabe7e8c1 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06a/initial_condition.py @@ -0,0 +1,90 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = self.config.get("pulse_center", 0.5) + width = self.config.get("pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, ic_type: str, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from factories.base_factory import BaseFactory + return BaseFactory.create_component('initial_condition', ic_type, config) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06a/mesh.py b/example/1d-linear-convection/weno3/python/06a/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06a/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06a/plotter.py b/example/1d-linear-convection/weno3/python/06a/plotter.py new file mode 100644 index 000000000..9f1a414fd --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06a/plotter.py @@ -0,0 +1,107 @@ +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06a/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python/06a/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06a/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06a/reconstructor/base.py b/example/1d-linear-convection/weno3/python/06a/reconstructor/base.py new file mode 100644 index 000000000..bbd638503 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06a/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def reconstruct(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06a/reconstructor/eno.py b/example/1d-linear-convection/weno3/python/06a/reconstructor/eno.py new file mode 100644 index 000000000..c2fb385dd --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06a/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def reconstruct(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06a/reconstructor/factory.py b/example/1d-linear-convection/weno3/python/06a/reconstructor/factory.py new file mode 100644 index 000000000..efa4a1446 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06a/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from cfd_registry import ComponentRegistry + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from factories.base_factory import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06a/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python/06a/reconstructor/weno3.py new file mode 100644 index 000000000..6e8c3f230 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06a/reconstructor/weno3.py @@ -0,0 +1,88 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def reconstruct(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + """ + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 # smoothness indicator for stencil [v2, v3] + beta1 = (v2 - v1)**2 # smoothness indicator for stencil [v1, v2] + + d0, d1 = 2/3, 1/3 # optimal linear weights (for right value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 0.5 * v2 + 0.5 * v3 # reconstruction from [v2, v3] + q1 = -0.5 * v1 + 1.5 * v2 # reconstruction from [v1, v2] + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v3 - v2)**2 + beta1 = (v2 - v1)**2 + + d0, d1 = 1/3, 2/3 # optimal linear weights (for left value) + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + w0 = alpha0 / (alpha0 + alpha1) + w1 = alpha1 / (alpha0 + alpha1) + + q0 = 1.5 * v2 - 0.5 * v3 # from [v2, v3] + q1 = 0.5 * v1 + 0.5 * v2 # from [v1, v2] + return w0 * q0 + w1 * q1 + """ diff --git a/example/1d-linear-convection/weno3/python/06a/registry.py b/example/1d-linear-convection/weno3/python/06a/registry.py new file mode 100644 index 000000000..61be90500 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06a/registry.py @@ -0,0 +1,75 @@ +# registry.py (改进版) +""" +CFD组件注册系统 - 简洁高效版 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表""" + + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True # 控制是否打印注册信息 + + @classmethod + def set_verbose(cls, verbose: bool): + """设置是否打印注册信息""" + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + # 检查是否已注册 + if name in cls._registries[category]: + if cls._registries[category][name] == component_class: + # 相同类重复注册,静默跳过 + return + elif cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取组件类""" + if category not in cls._registries: + available = list(cls._registries.keys()) + raise ValueError(f"❌ 未知类别: {category} (可用类别: {available})") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + @classmethod + def get_count(cls) -> int: + """获取注册组件总数""" + return sum(len(components) for components in cls._registries.values()) + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06a/residual.py b/example/1d-linear-convection/weno3/python/06a/residual.py new file mode 100644 index 000000000..b4d4d7dcc --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06a/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux import FluxCalculatorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + self.reconstructor = self.cfd.reconstructor + + # 初始化通量计算器(工厂创建) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._reconstruct() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _reconstruct(self): + """私有方法:界面值重建""" + self.reconstructor.reconstruct(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06a/run_eno_weno.py b/example/1d-linear-convection/weno3/python/06a/run_eno_weno.py new file mode 100644 index 000000000..fd7f38744 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06a/run_eno_weno.py @@ -0,0 +1,52 @@ +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + #mesh = Mesh(ncells=100, L=2.0) + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行ENO3求解(使用你的链式调用) + print("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + config_eno3.with_reconstruction("eno", 3) # 显式指定3阶(也可省略,ENO默认3阶) + # 可选:覆盖默认值(如dt) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + cfd_eno3.run() # 求解并生成result字典 + + # 可选:快速验证ENO3结果 + # plotter.plot_quick(cfd_eno3.result, title="ENO3 Quick Check") + + # 3. 配置并运行WENO3求解(注意:WENO默认5阶,这里显式指定3阶) + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) # 显式指定3阶(默认是5阶) + # 可选:覆盖默认值 + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + cfd_weno3.run() + + # 4. 可选:保存结果(供离线绘图) + # cfd_eno3.save_result("eno3_result.npz") + # cfd_weno3.save_result("weno3_result.npz") + + # 5. 绘制ENO/WENO对比图 + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" # 可选:保存图片 + ) + +if __name__ == "__main__": + # 主程序入口 + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06a/solution.py b/example/1d-linear-convection/weno3/python/06a/solution.py new file mode 100644 index 000000000..92a46d3f4 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06a/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config.ic_type, config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06a/solver.py b/example/1d-linear-convection/weno3/python/06a/solver.py new file mode 100644 index 000000000..0d0b442da --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06a/solver.py @@ -0,0 +1,86 @@ +import numpy as np +import matplotlib.pyplot as plt +import inflect +from abc import ABC, abstractmethod + + +from flux import InviscidFluxCalculator, RusanovFluxCalculator, EngquistOsherFluxCalculator, FluxCalculatorFactory + +from boundary import BoundaryCondition, PeriodicBoundary, DirichletBoundary, NeumannBoundary, BoundaryConditionFactory + +from time_integration import TimeIntegrator, RK1Integrator, RK2Integrator, RK3Integrator, TimeIntegratorFactory + +from mesh import Mesh + +from reconstructor import ReconstructorFactory + +from initial_condition import InitialCondition, StepFunctionIC, SineWaveIC, GaussianPulseIC, InitialConditionFactory + +from domain import Domain +from solution import Solution + +from config import CfdConfig +from residual import ResidualCalculator + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, config, mesh): + self.config = config + self.domain = Domain(config, mesh) + self.solution = Solution(config, self.domain) + self.reconstructor = ReconstructorFactory.create(config, self.domain) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + def exact_solution(self): + """通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界""" + x = self.domain.mesh.xcc + T = self.config.final_time + c = self.config.wave_speed + L = self.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = InitialConditionFactory.create(self.config.ic_type, self.config) + return ic.evaluate_at(x_shifted) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + #runge_kutta(self) + self.integrator.step(dt) + t += dt + config.dt = dt_old + + # 整理标准化结果 + u_numerical = self.solution.u[self.domain.ist:self.domain.ied].copy() + self.result = { + "x": domain.mesh.xcc, + "numerical": u_numerical, + "analytical": self.exact_solution(), + "config": { + "scheme": self.config.recon_scheme, + "order": self.config.spatial_order, + "rk_order": self.config.rk_order, + "final_time": self.config.final_time + } + } + + return u_numerical diff --git a/example/1d-linear-convection/weno3/python/06a/time_integration.py b/example/1d-linear-convection/weno3/python/06a/time_integration.py new file mode 100644 index 000000000..25ac0b4ca --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06a/time_integration.py @@ -0,0 +1,125 @@ +# time_integration.py + +""" +时间推进器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = cfd.residual_calculator + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(使用装饰器注册) ---------------------- + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class TimeIntegratorFactory: + """时间推进器工厂""" + + @staticmethod + def create(cfd) -> 'TimeIntegrator': + """ + 创建时间推进器实例 + + Args: + cfd: CFD对象 + + Returns: + 时间推进器实例 + """ + from factories.base_factory import BaseFactory + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06b/boundary.py b/example/1d-linear-convection/weno3/python/06b/boundary.py new file mode 100644 index 000000000..3a271aa29 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06b/boundary.py @@ -0,0 +1,104 @@ +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from factories.base_factory import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) + diff --git a/example/1d-linear-convection/weno3/python/06b/cfd_registry.py b/example/1d-linear-convection/weno3/python/06b/cfd_registry.py new file mode 100644 index 000000000..c12eb106c --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06b/cfd_registry.py @@ -0,0 +1,62 @@ +# cfd_registry.py +""" +CFD注册系统统一导入模块 +所有其他模块从这里导入注册系统 +""" + +import sys +import os + +# 添加当前目录到路径,确保能找到registry.py +current_dir = os.path.dirname(os.path.abspath(__file__)) +if current_dir not in sys.path: + sys.path.insert(0, current_dir) + +try: + # 尝试导入注册系统 + from registry import ComponentRegistry, register_component + REGISTRY_AVAILABLE = True +except ImportError: + # 如果找不到,提供简单的空实现 + print("⚠️ 警告: 未找到 registry.py,使用最小化回退实现") + + class ComponentRegistry: + """最小化的注册系统回退实现""" + _registries = {} + + @classmethod + def register(cls, category, name, component_class): + if category not in cls._registries: + cls._registries[category] = {} + cls._registries[category][name] = component_class + + @classmethod + def get(cls, category, name): + if category not in cls._registries: + raise ValueError(f"未知类别: {category}") + if name not in cls._registries[category]: + raise ValueError(f"未找到: {category}.{name}") + return cls._registries[category][name] + + @classmethod + def create(cls, category, name, *args, **kwargs): + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) + for cat, comps in cls._registries.items()} + + def register_component(category, name=None): + """简化的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + REGISTRY_AVAILABLE = False + +# 导出统一的接口 +__all__ = ['ComponentRegistry', 'register_component', 'REGISTRY_AVAILABLE'] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06b/config.py b/example/1d-linear-convection/weno3/python/06b/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06b/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06b/domain.py b/example/1d-linear-convection/weno3/python/06b/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06b/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06b/factories/base_factory.py b/example/1d-linear-convection/weno3/python/06b/factories/base_factory.py new file mode 100644 index 000000000..7b8b6105e --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06b/factories/base_factory.py @@ -0,0 +1,49 @@ +# factories/base_factory.py +""" +工厂基类 - 提供统一的组件创建接口 +""" + +from cfd_registry import ComponentRegistry +from typing import Any, Optional + +class BaseFactory: + """工厂基类,提供统一的组件创建方法""" + + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + """ + 统一创建组件的方法 + + Args: + category: 组件类别(如'boundary', 'flux'等) + name: 组件名称(如'periodic', 'rusanov'等) + *args, **kwargs: 传递给构造函数的参数 + + Returns: + 创建的组件实例 + + Raises: + ValueError: 如果组件不存在 + """ + name_lower = name.lower() + + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + # 获取可用组件列表 + available = ComponentRegistry.list_all().get(category, []) + + # 构建友好的错误信息 + if available: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"可用类型:{available}") + else: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"({category}类别下没有注册任何组件)") + + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + """获取指定类别的可用组件列表""" + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06b/flux/__init__.py b/example/1d-linear-convection/weno3/python/06b/flux/__init__.py new file mode 100644 index 000000000..432a36cb0 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06b/flux/__init__.py @@ -0,0 +1,7 @@ +# flux/__init__.py +from .base import InviscidFluxCalculator +from .factory import FluxCalculatorFactory + +# 确保子模块被导入以触发注册 +from . import rusanov +from . import engquist_osher \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06b/flux/base.py b/example/1d-linear-convection/weno3/python/06b/flux/base.py new file mode 100644 index 000000000..7a5a134fc --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06b/flux/base.py @@ -0,0 +1,25 @@ +# flux/base.py +""" +抽象通量计算基类 +""" + +from abc import ABC, abstractmethod + +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06b/flux/engquist_osher.py b/example/1d-linear-convection/weno3/python/06b/flux/engquist_osher.py new file mode 100644 index 000000000..34ad5f9c2 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06b/flux/engquist_osher.py @@ -0,0 +1,19 @@ +# flux/engquist_osher.py +""" +Engquist-Osher 通量计算器(线性对流专用) +""" + +from cfd_registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06b/flux/factory.py b/example/1d-linear-convection/weno3/python/06b/flux/factory.py new file mode 100644 index 000000000..c2f6f0756 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06b/flux/factory.py @@ -0,0 +1,25 @@ +# flux/factory.py +""" +通量计算器专用工厂(封装注册细节,提供清晰接口) +符合你希望“将创建逻辑封装在工厂中”的设计原则 +""" + +from factories.base_factory import BaseFactory + +class FluxCalculatorFactory: + """通量计算器工厂:隐藏注册类别和组件名称等细节""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD 主对象,包含 config.flux_type 等配置 + + Returns: + 实现 InviscidFluxCalculator 接口的通量计算器实例 + """ + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06b/flux/rusanov.py b/example/1d-linear-convection/weno3/python/06b/flux/rusanov.py new file mode 100644 index 000000000..62dd207ca --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06b/flux/rusanov.py @@ -0,0 +1,21 @@ +# flux/rusanov.py +""" +Rusanov(Lax-Friedrichs)通量计算器 +""" + +from cfd_registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06b/initial_condition.py b/example/1d-linear-convection/weno3/python/06b/initial_condition.py new file mode 100644 index 000000000..03b803b02 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06b/initial_condition.py @@ -0,0 +1,91 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, ic_type: str, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from factories.base_factory import BaseFactory + return BaseFactory.create_component('initial_condition', ic_type, config) + diff --git a/example/1d-linear-convection/weno3/python/06b/mesh.py b/example/1d-linear-convection/weno3/python/06b/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06b/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06b/plotter.py b/example/1d-linear-convection/weno3/python/06b/plotter.py new file mode 100644 index 000000000..e1f99ae25 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06b/plotter.py @@ -0,0 +1,109 @@ +# plotter.py + +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06b/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python/06b/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06b/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06b/reconstructor/base.py b/example/1d-linear-convection/weno3/python/06b/reconstructor/base.py new file mode 100644 index 000000000..bbd638503 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06b/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def reconstruct(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06b/reconstructor/eno.py b/example/1d-linear-convection/weno3/python/06b/reconstructor/eno.py new file mode 100644 index 000000000..c2fb385dd --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06b/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def reconstruct(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06b/reconstructor/factory.py b/example/1d-linear-convection/weno3/python/06b/reconstructor/factory.py new file mode 100644 index 000000000..efa4a1446 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06b/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from cfd_registry import ComponentRegistry + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from factories.base_factory import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06b/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python/06b/reconstructor/weno3.py new file mode 100644 index 000000000..bf68be509 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06b/reconstructor/weno3.py @@ -0,0 +1,59 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def reconstruct(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/python/06b/registry.py b/example/1d-linear-convection/weno3/python/06b/registry.py new file mode 100644 index 000000000..61be90500 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06b/registry.py @@ -0,0 +1,75 @@ +# registry.py (改进版) +""" +CFD组件注册系统 - 简洁高效版 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表""" + + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True # 控制是否打印注册信息 + + @classmethod + def set_verbose(cls, verbose: bool): + """设置是否打印注册信息""" + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + # 检查是否已注册 + if name in cls._registries[category]: + if cls._registries[category][name] == component_class: + # 相同类重复注册,静默跳过 + return + elif cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取组件类""" + if category not in cls._registries: + available = list(cls._registries.keys()) + raise ValueError(f"❌ 未知类别: {category} (可用类别: {available})") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + @classmethod + def get_count(cls) -> int: + """获取注册组件总数""" + return sum(len(components) for components in cls._registries.values()) + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06b/residual.py b/example/1d-linear-convection/weno3/python/06b/residual.py new file mode 100644 index 000000000..afd752227 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06b/residual.py @@ -0,0 +1,39 @@ +# residual.py + +from flux.factory import FluxCalculatorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + self.reconstructor = self.cfd.reconstructor + + # 初始化通量计算器(工厂创建) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + def compute(self): + """计算完整残差(对外唯一接口)""" + self._reconstruct() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _reconstruct(self): + """私有方法:界面值重建""" + self.reconstructor.reconstruct(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06b/run_eno_weno.py b/example/1d-linear-convection/weno3/python/06b/run_eno_weno.py new file mode 100644 index 000000000..ff46f2267 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06b/run_eno_weno.py @@ -0,0 +1,54 @@ +# run_eno_weno.py + +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + #mesh = Mesh(ncells=100, L=2.0) + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行ENO3求解(使用你的链式调用) + print("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + config_eno3.with_reconstruction("eno", 3) # 显式指定3阶(也可省略,ENO默认3阶) + # 可选:覆盖默认值(如dt) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + cfd_eno3.run() # 求解并生成result字典 + + # 可选:快速验证ENO3结果 + # plotter.plot_quick(cfd_eno3.result, title="ENO3 Quick Check") + + # 3. 配置并运行WENO3求解(注意:WENO默认5阶,这里显式指定3阶) + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) # 显式指定3阶(默认是5阶) + # 可选:覆盖默认值 + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + cfd_weno3.run() + + # 4. 可选:保存结果(供离线绘图) + # cfd_eno3.save_result("eno3_result.npz") + # cfd_weno3.save_result("weno3_result.npz") + + # 5. 绘制ENO/WENO对比图 + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" # 可选:保存图片 + ) + +if __name__ == "__main__": + # 主程序入口 + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06b/solution.py b/example/1d-linear-convection/weno3/python/06b/solution.py new file mode 100644 index 000000000..92a46d3f4 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06b/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config.ic_type, config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06b/solver.py b/example/1d-linear-convection/weno3/python/06b/solver.py new file mode 100644 index 000000000..d15bab2aa --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06b/solver.py @@ -0,0 +1,79 @@ +import numpy as np +import matplotlib.pyplot as plt +import inflect +from abc import ABC, abstractmethod + +from boundary import BoundaryConditionFactory +from initial_condition import InitialConditionFactory +from time_integration import TimeIntegrator,TimeIntegratorFactory +from flux import InviscidFluxCalculator # 仅用于类型提示(可选) +from mesh import Mesh +from domain import Domain +from solution import Solution +from config import CfdConfig +from residual import ResidualCalculator +from reconstructor import ReconstructorFactory +from factories.base_factory import BaseFactory + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, config, mesh): + self.config = config + self.domain = Domain(config, mesh) + self.solution = Solution(config, self.domain) + self.reconstructor = ReconstructorFactory.create(config, self.domain) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + + def exact_solution(self): + """通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界""" + x = self.domain.mesh.xcc + T = self.config.final_time + c = self.config.wave_speed + L = self.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = InitialConditionFactory.create(self.config.ic_type, self.config) + return ic.evaluate_at(x_shifted) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + self.integrator.step(dt) + t += dt + config.dt = dt_old + + # 整理标准化结果 + u_numerical = self.solution.u[self.domain.ist:self.domain.ied].copy() + self.result = { + "x": domain.mesh.xcc, + "numerical": u_numerical, + "analytical": self.exact_solution(), + "config": { + "scheme": self.config.recon_scheme, + "order": self.config.spatial_order, + "rk_order": self.config.rk_order, + "final_time": self.config.final_time + } + } + + return u_numerical diff --git a/example/1d-linear-convection/weno3/python/06b/time_integration.py b/example/1d-linear-convection/weno3/python/06b/time_integration.py new file mode 100644 index 000000000..25ac0b4ca --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06b/time_integration.py @@ -0,0 +1,125 @@ +# time_integration.py + +""" +时间推进器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = cfd.residual_calculator + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(使用装饰器注册) ---------------------- + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class TimeIntegratorFactory: + """时间推进器工厂""" + + @staticmethod + def create(cfd) -> 'TimeIntegrator': + """ + 创建时间推进器实例 + + Args: + cfd: CFD对象 + + Returns: + 时间推进器实例 + """ + from factories.base_factory import BaseFactory + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06c/boundary.py b/example/1d-linear-convection/weno3/python/06c/boundary.py new file mode 100644 index 000000000..3a271aa29 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06c/boundary.py @@ -0,0 +1,104 @@ +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from factories.base_factory import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) + diff --git a/example/1d-linear-convection/weno3/python/06c/cfd_registry.py b/example/1d-linear-convection/weno3/python/06c/cfd_registry.py new file mode 100644 index 000000000..c12eb106c --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06c/cfd_registry.py @@ -0,0 +1,62 @@ +# cfd_registry.py +""" +CFD注册系统统一导入模块 +所有其他模块从这里导入注册系统 +""" + +import sys +import os + +# 添加当前目录到路径,确保能找到registry.py +current_dir = os.path.dirname(os.path.abspath(__file__)) +if current_dir not in sys.path: + sys.path.insert(0, current_dir) + +try: + # 尝试导入注册系统 + from registry import ComponentRegistry, register_component + REGISTRY_AVAILABLE = True +except ImportError: + # 如果找不到,提供简单的空实现 + print("⚠️ 警告: 未找到 registry.py,使用最小化回退实现") + + class ComponentRegistry: + """最小化的注册系统回退实现""" + _registries = {} + + @classmethod + def register(cls, category, name, component_class): + if category not in cls._registries: + cls._registries[category] = {} + cls._registries[category][name] = component_class + + @classmethod + def get(cls, category, name): + if category not in cls._registries: + raise ValueError(f"未知类别: {category}") + if name not in cls._registries[category]: + raise ValueError(f"未找到: {category}.{name}") + return cls._registries[category][name] + + @classmethod + def create(cls, category, name, *args, **kwargs): + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) + for cat, comps in cls._registries.items()} + + def register_component(category, name=None): + """简化的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + REGISTRY_AVAILABLE = False + +# 导出统一的接口 +__all__ = ['ComponentRegistry', 'register_component', 'REGISTRY_AVAILABLE'] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06c/config.py b/example/1d-linear-convection/weno3/python/06c/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06c/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06c/domain.py b/example/1d-linear-convection/weno3/python/06c/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06c/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06c/factories/base_factory.py b/example/1d-linear-convection/weno3/python/06c/factories/base_factory.py new file mode 100644 index 000000000..7b8b6105e --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06c/factories/base_factory.py @@ -0,0 +1,49 @@ +# factories/base_factory.py +""" +工厂基类 - 提供统一的组件创建接口 +""" + +from cfd_registry import ComponentRegistry +from typing import Any, Optional + +class BaseFactory: + """工厂基类,提供统一的组件创建方法""" + + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + """ + 统一创建组件的方法 + + Args: + category: 组件类别(如'boundary', 'flux'等) + name: 组件名称(如'periodic', 'rusanov'等) + *args, **kwargs: 传递给构造函数的参数 + + Returns: + 创建的组件实例 + + Raises: + ValueError: 如果组件不存在 + """ + name_lower = name.lower() + + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + # 获取可用组件列表 + available = ComponentRegistry.list_all().get(category, []) + + # 构建友好的错误信息 + if available: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"可用类型:{available}") + else: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"({category}类别下没有注册任何组件)") + + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + """获取指定类别的可用组件列表""" + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06c/flux/__init__.py b/example/1d-linear-convection/weno3/python/06c/flux/__init__.py new file mode 100644 index 000000000..432a36cb0 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06c/flux/__init__.py @@ -0,0 +1,7 @@ +# flux/__init__.py +from .base import InviscidFluxCalculator +from .factory import FluxCalculatorFactory + +# 确保子模块被导入以触发注册 +from . import rusanov +from . import engquist_osher \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06c/flux/base.py b/example/1d-linear-convection/weno3/python/06c/flux/base.py new file mode 100644 index 000000000..7a5a134fc --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06c/flux/base.py @@ -0,0 +1,25 @@ +# flux/base.py +""" +抽象通量计算基类 +""" + +from abc import ABC, abstractmethod + +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06c/flux/engquist_osher.py b/example/1d-linear-convection/weno3/python/06c/flux/engquist_osher.py new file mode 100644 index 000000000..34ad5f9c2 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06c/flux/engquist_osher.py @@ -0,0 +1,19 @@ +# flux/engquist_osher.py +""" +Engquist-Osher 通量计算器(线性对流专用) +""" + +from cfd_registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06c/flux/factory.py b/example/1d-linear-convection/weno3/python/06c/flux/factory.py new file mode 100644 index 000000000..c2f6f0756 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06c/flux/factory.py @@ -0,0 +1,25 @@ +# flux/factory.py +""" +通量计算器专用工厂(封装注册细节,提供清晰接口) +符合你希望“将创建逻辑封装在工厂中”的设计原则 +""" + +from factories.base_factory import BaseFactory + +class FluxCalculatorFactory: + """通量计算器工厂:隐藏注册类别和组件名称等细节""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD 主对象,包含 config.flux_type 等配置 + + Returns: + 实现 InviscidFluxCalculator 接口的通量计算器实例 + """ + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06c/flux/rusanov.py b/example/1d-linear-convection/weno3/python/06c/flux/rusanov.py new file mode 100644 index 000000000..62dd207ca --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06c/flux/rusanov.py @@ -0,0 +1,21 @@ +# flux/rusanov.py +""" +Rusanov(Lax-Friedrichs)通量计算器 +""" + +from cfd_registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06c/initial_condition.py b/example/1d-linear-convection/weno3/python/06c/initial_condition.py new file mode 100644 index 000000000..03b803b02 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06c/initial_condition.py @@ -0,0 +1,91 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, ic_type: str, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from factories.base_factory import BaseFactory + return BaseFactory.create_component('initial_condition', ic_type, config) + diff --git a/example/1d-linear-convection/weno3/python/06c/mesh.py b/example/1d-linear-convection/weno3/python/06c/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06c/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06c/plotter.py b/example/1d-linear-convection/weno3/python/06c/plotter.py new file mode 100644 index 000000000..e1f99ae25 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06c/plotter.py @@ -0,0 +1,109 @@ +# plotter.py + +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06c/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python/06c/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06c/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06c/reconstructor/base.py b/example/1d-linear-convection/weno3/python/06c/reconstructor/base.py new file mode 100644 index 000000000..094b47120 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06c/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def compute_face_values(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06c/reconstructor/eno.py b/example/1d-linear-convection/weno3/python/06c/reconstructor/eno.py new file mode 100644 index 000000000..8fde43334 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06c/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def compute_face_values(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06c/reconstructor/factory.py b/example/1d-linear-convection/weno3/python/06c/reconstructor/factory.py new file mode 100644 index 000000000..efa4a1446 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06c/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from cfd_registry import ComponentRegistry + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from factories.base_factory import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06c/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python/06c/reconstructor/weno3.py new file mode 100644 index 000000000..929061c13 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06c/reconstructor/weno3.py @@ -0,0 +1,59 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/python/06c/registry.py b/example/1d-linear-convection/weno3/python/06c/registry.py new file mode 100644 index 000000000..61be90500 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06c/registry.py @@ -0,0 +1,75 @@ +# registry.py (改进版) +""" +CFD组件注册系统 - 简洁高效版 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表""" + + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True # 控制是否打印注册信息 + + @classmethod + def set_verbose(cls, verbose: bool): + """设置是否打印注册信息""" + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + # 检查是否已注册 + if name in cls._registries[category]: + if cls._registries[category][name] == component_class: + # 相同类重复注册,静默跳过 + return + elif cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取组件类""" + if category not in cls._registries: + available = list(cls._registries.keys()) + raise ValueError(f"❌ 未知类别: {category} (可用类别: {available})") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + @classmethod + def get_count(cls) -> int: + """获取注册组件总数""" + return sum(len(components) for components in cls._registries.values()) + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06c/residual.py b/example/1d-linear-convection/weno3/python/06c/residual.py new file mode 100644 index 000000000..b1d035924 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06c/residual.py @@ -0,0 +1,39 @@ +# residual.py + +from flux.factory import FluxCalculatorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + self.reconstructor = self.cfd.reconstructor + + # 初始化通量计算器(工厂创建) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + def compute(self): + """计算完整残差(对外唯一接口)""" + self._compute_face_values() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _compute_face_values(self): + """私有方法:界面值重建""" + self.reconstructor.compute_face_values(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06c/run_eno_weno.py b/example/1d-linear-convection/weno3/python/06c/run_eno_weno.py new file mode 100644 index 000000000..ff46f2267 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06c/run_eno_weno.py @@ -0,0 +1,54 @@ +# run_eno_weno.py + +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + #mesh = Mesh(ncells=100, L=2.0) + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行ENO3求解(使用你的链式调用) + print("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + config_eno3.with_reconstruction("eno", 3) # 显式指定3阶(也可省略,ENO默认3阶) + # 可选:覆盖默认值(如dt) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + cfd_eno3.run() # 求解并生成result字典 + + # 可选:快速验证ENO3结果 + # plotter.plot_quick(cfd_eno3.result, title="ENO3 Quick Check") + + # 3. 配置并运行WENO3求解(注意:WENO默认5阶,这里显式指定3阶) + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) # 显式指定3阶(默认是5阶) + # 可选:覆盖默认值 + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + cfd_weno3.run() + + # 4. 可选:保存结果(供离线绘图) + # cfd_eno3.save_result("eno3_result.npz") + # cfd_weno3.save_result("weno3_result.npz") + + # 5. 绘制ENO/WENO对比图 + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" # 可选:保存图片 + ) + +if __name__ == "__main__": + # 主程序入口 + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06c/solution.py b/example/1d-linear-convection/weno3/python/06c/solution.py new file mode 100644 index 000000000..92a46d3f4 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06c/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config.ic_type, config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06c/solver.py b/example/1d-linear-convection/weno3/python/06c/solver.py new file mode 100644 index 000000000..d15bab2aa --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06c/solver.py @@ -0,0 +1,79 @@ +import numpy as np +import matplotlib.pyplot as plt +import inflect +from abc import ABC, abstractmethod + +from boundary import BoundaryConditionFactory +from initial_condition import InitialConditionFactory +from time_integration import TimeIntegrator,TimeIntegratorFactory +from flux import InviscidFluxCalculator # 仅用于类型提示(可选) +from mesh import Mesh +from domain import Domain +from solution import Solution +from config import CfdConfig +from residual import ResidualCalculator +from reconstructor import ReconstructorFactory +from factories.base_factory import BaseFactory + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, config, mesh): + self.config = config + self.domain = Domain(config, mesh) + self.solution = Solution(config, self.domain) + self.reconstructor = ReconstructorFactory.create(config, self.domain) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + + def exact_solution(self): + """通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界""" + x = self.domain.mesh.xcc + T = self.config.final_time + c = self.config.wave_speed + L = self.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = InitialConditionFactory.create(self.config.ic_type, self.config) + return ic.evaluate_at(x_shifted) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + self.integrator.step(dt) + t += dt + config.dt = dt_old + + # 整理标准化结果 + u_numerical = self.solution.u[self.domain.ist:self.domain.ied].copy() + self.result = { + "x": domain.mesh.xcc, + "numerical": u_numerical, + "analytical": self.exact_solution(), + "config": { + "scheme": self.config.recon_scheme, + "order": self.config.spatial_order, + "rk_order": self.config.rk_order, + "final_time": self.config.final_time + } + } + + return u_numerical diff --git a/example/1d-linear-convection/weno3/python/06c/time_integration.py b/example/1d-linear-convection/weno3/python/06c/time_integration.py new file mode 100644 index 000000000..25ac0b4ca --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06c/time_integration.py @@ -0,0 +1,125 @@ +# time_integration.py + +""" +时间推进器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = cfd.residual_calculator + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(使用装饰器注册) ---------------------- + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class TimeIntegratorFactory: + """时间推进器工厂""" + + @staticmethod + def create(cfd) -> 'TimeIntegrator': + """ + 创建时间推进器实例 + + Args: + cfd: CFD对象 + + Returns: + 时间推进器实例 + """ + from factories.base_factory import BaseFactory + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06d/boundary.py b/example/1d-linear-convection/weno3/python/06d/boundary.py new file mode 100644 index 000000000..3a271aa29 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06d/boundary.py @@ -0,0 +1,104 @@ +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from factories.base_factory import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) + diff --git a/example/1d-linear-convection/weno3/python/06d/cfd_registry.py b/example/1d-linear-convection/weno3/python/06d/cfd_registry.py new file mode 100644 index 000000000..c12eb106c --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06d/cfd_registry.py @@ -0,0 +1,62 @@ +# cfd_registry.py +""" +CFD注册系统统一导入模块 +所有其他模块从这里导入注册系统 +""" + +import sys +import os + +# 添加当前目录到路径,确保能找到registry.py +current_dir = os.path.dirname(os.path.abspath(__file__)) +if current_dir not in sys.path: + sys.path.insert(0, current_dir) + +try: + # 尝试导入注册系统 + from registry import ComponentRegistry, register_component + REGISTRY_AVAILABLE = True +except ImportError: + # 如果找不到,提供简单的空实现 + print("⚠️ 警告: 未找到 registry.py,使用最小化回退实现") + + class ComponentRegistry: + """最小化的注册系统回退实现""" + _registries = {} + + @classmethod + def register(cls, category, name, component_class): + if category not in cls._registries: + cls._registries[category] = {} + cls._registries[category][name] = component_class + + @classmethod + def get(cls, category, name): + if category not in cls._registries: + raise ValueError(f"未知类别: {category}") + if name not in cls._registries[category]: + raise ValueError(f"未找到: {category}.{name}") + return cls._registries[category][name] + + @classmethod + def create(cls, category, name, *args, **kwargs): + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) + for cat, comps in cls._registries.items()} + + def register_component(category, name=None): + """简化的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + REGISTRY_AVAILABLE = False + +# 导出统一的接口 +__all__ = ['ComponentRegistry', 'register_component', 'REGISTRY_AVAILABLE'] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06d/config.py b/example/1d-linear-convection/weno3/python/06d/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06d/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06d/domain.py b/example/1d-linear-convection/weno3/python/06d/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06d/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06d/factories/base_factory.py b/example/1d-linear-convection/weno3/python/06d/factories/base_factory.py new file mode 100644 index 000000000..7b8b6105e --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06d/factories/base_factory.py @@ -0,0 +1,49 @@ +# factories/base_factory.py +""" +工厂基类 - 提供统一的组件创建接口 +""" + +from cfd_registry import ComponentRegistry +from typing import Any, Optional + +class BaseFactory: + """工厂基类,提供统一的组件创建方法""" + + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + """ + 统一创建组件的方法 + + Args: + category: 组件类别(如'boundary', 'flux'等) + name: 组件名称(如'periodic', 'rusanov'等) + *args, **kwargs: 传递给构造函数的参数 + + Returns: + 创建的组件实例 + + Raises: + ValueError: 如果组件不存在 + """ + name_lower = name.lower() + + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + # 获取可用组件列表 + available = ComponentRegistry.list_all().get(category, []) + + # 构建友好的错误信息 + if available: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"可用类型:{available}") + else: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"({category}类别下没有注册任何组件)") + + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + """获取指定类别的可用组件列表""" + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06d/flux/__init__.py b/example/1d-linear-convection/weno3/python/06d/flux/__init__.py new file mode 100644 index 000000000..432a36cb0 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06d/flux/__init__.py @@ -0,0 +1,7 @@ +# flux/__init__.py +from .base import InviscidFluxCalculator +from .factory import FluxCalculatorFactory + +# 确保子模块被导入以触发注册 +from . import rusanov +from . import engquist_osher \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06d/flux/base.py b/example/1d-linear-convection/weno3/python/06d/flux/base.py new file mode 100644 index 000000000..7a5a134fc --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06d/flux/base.py @@ -0,0 +1,25 @@ +# flux/base.py +""" +抽象通量计算基类 +""" + +from abc import ABC, abstractmethod + +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06d/flux/engquist_osher.py b/example/1d-linear-convection/weno3/python/06d/flux/engquist_osher.py new file mode 100644 index 000000000..34ad5f9c2 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06d/flux/engquist_osher.py @@ -0,0 +1,19 @@ +# flux/engquist_osher.py +""" +Engquist-Osher 通量计算器(线性对流专用) +""" + +from cfd_registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06d/flux/factory.py b/example/1d-linear-convection/weno3/python/06d/flux/factory.py new file mode 100644 index 000000000..c2f6f0756 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06d/flux/factory.py @@ -0,0 +1,25 @@ +# flux/factory.py +""" +通量计算器专用工厂(封装注册细节,提供清晰接口) +符合你希望“将创建逻辑封装在工厂中”的设计原则 +""" + +from factories.base_factory import BaseFactory + +class FluxCalculatorFactory: + """通量计算器工厂:隐藏注册类别和组件名称等细节""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD 主对象,包含 config.flux_type 等配置 + + Returns: + 实现 InviscidFluxCalculator 接口的通量计算器实例 + """ + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06d/flux/rusanov.py b/example/1d-linear-convection/weno3/python/06d/flux/rusanov.py new file mode 100644 index 000000000..62dd207ca --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06d/flux/rusanov.py @@ -0,0 +1,21 @@ +# flux/rusanov.py +""" +Rusanov(Lax-Friedrichs)通量计算器 +""" + +from cfd_registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06d/initial_condition.py b/example/1d-linear-convection/weno3/python/06d/initial_condition.py new file mode 100644 index 000000000..500cdfb81 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06d/initial_condition.py @@ -0,0 +1,91 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from factories.base_factory import BaseFactory + return BaseFactory.create_component('initial_condition', config.ic_type, config) + diff --git a/example/1d-linear-convection/weno3/python/06d/mesh.py b/example/1d-linear-convection/weno3/python/06d/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06d/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06d/plotter.py b/example/1d-linear-convection/weno3/python/06d/plotter.py new file mode 100644 index 000000000..e1f99ae25 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06d/plotter.py @@ -0,0 +1,109 @@ +# plotter.py + +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06d/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python/06d/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06d/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06d/reconstructor/base.py b/example/1d-linear-convection/weno3/python/06d/reconstructor/base.py new file mode 100644 index 000000000..094b47120 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06d/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def compute_face_values(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06d/reconstructor/eno.py b/example/1d-linear-convection/weno3/python/06d/reconstructor/eno.py new file mode 100644 index 000000000..8fde43334 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06d/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def compute_face_values(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06d/reconstructor/factory.py b/example/1d-linear-convection/weno3/python/06d/reconstructor/factory.py new file mode 100644 index 000000000..efa4a1446 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06d/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from cfd_registry import ComponentRegistry + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from factories.base_factory import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06d/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python/06d/reconstructor/weno3.py new file mode 100644 index 000000000..929061c13 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06d/reconstructor/weno3.py @@ -0,0 +1,59 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/python/06d/registry.py b/example/1d-linear-convection/weno3/python/06d/registry.py new file mode 100644 index 000000000..61be90500 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06d/registry.py @@ -0,0 +1,75 @@ +# registry.py (改进版) +""" +CFD组件注册系统 - 简洁高效版 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表""" + + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True # 控制是否打印注册信息 + + @classmethod + def set_verbose(cls, verbose: bool): + """设置是否打印注册信息""" + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + # 检查是否已注册 + if name in cls._registries[category]: + if cls._registries[category][name] == component_class: + # 相同类重复注册,静默跳过 + return + elif cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取组件类""" + if category not in cls._registries: + available = list(cls._registries.keys()) + raise ValueError(f"❌ 未知类别: {category} (可用类别: {available})") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + @classmethod + def get_count(cls) -> int: + """获取注册组件总数""" + return sum(len(components) for components in cls._registries.values()) + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06d/residual.py b/example/1d-linear-convection/weno3/python/06d/residual.py new file mode 100644 index 000000000..b1d035924 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06d/residual.py @@ -0,0 +1,39 @@ +# residual.py + +from flux.factory import FluxCalculatorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + self.reconstructor = self.cfd.reconstructor + + # 初始化通量计算器(工厂创建) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + def compute(self): + """计算完整残差(对外唯一接口)""" + self._compute_face_values() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _compute_face_values(self): + """私有方法:界面值重建""" + self.reconstructor.compute_face_values(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06d/run_eno_weno.py b/example/1d-linear-convection/weno3/python/06d/run_eno_weno.py new file mode 100644 index 000000000..ff46f2267 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06d/run_eno_weno.py @@ -0,0 +1,54 @@ +# run_eno_weno.py + +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + #mesh = Mesh(ncells=100, L=2.0) + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行ENO3求解(使用你的链式调用) + print("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + config_eno3.with_reconstruction("eno", 3) # 显式指定3阶(也可省略,ENO默认3阶) + # 可选:覆盖默认值(如dt) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + cfd_eno3.run() # 求解并生成result字典 + + # 可选:快速验证ENO3结果 + # plotter.plot_quick(cfd_eno3.result, title="ENO3 Quick Check") + + # 3. 配置并运行WENO3求解(注意:WENO默认5阶,这里显式指定3阶) + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) # 显式指定3阶(默认是5阶) + # 可选:覆盖默认值 + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + cfd_weno3.run() + + # 4. 可选:保存结果(供离线绘图) + # cfd_eno3.save_result("eno3_result.npz") + # cfd_weno3.save_result("weno3_result.npz") + + # 5. 绘制ENO/WENO对比图 + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" # 可选:保存图片 + ) + +if __name__ == "__main__": + # 主程序入口 + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06d/solution.py b/example/1d-linear-convection/weno3/python/06d/solution.py new file mode 100644 index 000000000..3d4cba569 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06d/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06d/solver.py b/example/1d-linear-convection/weno3/python/06d/solver.py new file mode 100644 index 000000000..12f0ce1e1 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06d/solver.py @@ -0,0 +1,79 @@ +import numpy as np +import matplotlib.pyplot as plt +import inflect +from abc import ABC, abstractmethod + +from boundary import BoundaryConditionFactory +from initial_condition import InitialConditionFactory +from time_integration import TimeIntegrator,TimeIntegratorFactory +from flux import InviscidFluxCalculator # 仅用于类型提示(可选) +from mesh import Mesh +from domain import Domain +from solution import Solution +from config import CfdConfig +from residual import ResidualCalculator +from reconstructor import ReconstructorFactory +from factories.base_factory import BaseFactory + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, config, mesh): + self.config = config + self.domain = Domain(config, mesh) + self.solution = Solution(config, self.domain) + self.reconstructor = ReconstructorFactory.create(config, self.domain) + self.residual_calculator = ResidualCalculator(self) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + + def exact_solution(self): + """通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界""" + x = self.domain.mesh.xcc + T = self.config.final_time + c = self.config.wave_speed + L = self.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = InitialConditionFactory.create(self.config) + return ic.evaluate_at(x_shifted) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + self.integrator.step(dt) + t += dt + config.dt = dt_old + + # 整理标准化结果 + u_numerical = self.solution.u[self.domain.ist:self.domain.ied].copy() + self.result = { + "x": domain.mesh.xcc, + "numerical": u_numerical, + "analytical": self.exact_solution(), + "config": { + "scheme": self.config.recon_scheme, + "order": self.config.spatial_order, + "rk_order": self.config.rk_order, + "final_time": self.config.final_time + } + } + + return u_numerical diff --git a/example/1d-linear-convection/weno3/python/06d/time_integration.py b/example/1d-linear-convection/weno3/python/06d/time_integration.py new file mode 100644 index 000000000..25ac0b4ca --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06d/time_integration.py @@ -0,0 +1,125 @@ +# time_integration.py + +""" +时间推进器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = cfd.residual_calculator + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(使用装饰器注册) ---------------------- + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class TimeIntegratorFactory: + """时间推进器工厂""" + + @staticmethod + def create(cfd) -> 'TimeIntegrator': + """ + 创建时间推进器实例 + + Args: + cfd: CFD对象 + + Returns: + 时间推进器实例 + """ + from factories.base_factory import BaseFactory + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06e/boundary.py b/example/1d-linear-convection/weno3/python/06e/boundary.py new file mode 100644 index 000000000..3a271aa29 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06e/boundary.py @@ -0,0 +1,104 @@ +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from factories.base_factory import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) + diff --git a/example/1d-linear-convection/weno3/python/06e/cfd_registry.py b/example/1d-linear-convection/weno3/python/06e/cfd_registry.py new file mode 100644 index 000000000..c12eb106c --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06e/cfd_registry.py @@ -0,0 +1,62 @@ +# cfd_registry.py +""" +CFD注册系统统一导入模块 +所有其他模块从这里导入注册系统 +""" + +import sys +import os + +# 添加当前目录到路径,确保能找到registry.py +current_dir = os.path.dirname(os.path.abspath(__file__)) +if current_dir not in sys.path: + sys.path.insert(0, current_dir) + +try: + # 尝试导入注册系统 + from registry import ComponentRegistry, register_component + REGISTRY_AVAILABLE = True +except ImportError: + # 如果找不到,提供简单的空实现 + print("⚠️ 警告: 未找到 registry.py,使用最小化回退实现") + + class ComponentRegistry: + """最小化的注册系统回退实现""" + _registries = {} + + @classmethod + def register(cls, category, name, component_class): + if category not in cls._registries: + cls._registries[category] = {} + cls._registries[category][name] = component_class + + @classmethod + def get(cls, category, name): + if category not in cls._registries: + raise ValueError(f"未知类别: {category}") + if name not in cls._registries[category]: + raise ValueError(f"未找到: {category}.{name}") + return cls._registries[category][name] + + @classmethod + def create(cls, category, name, *args, **kwargs): + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) + for cat, comps in cls._registries.items()} + + def register_component(category, name=None): + """简化的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + REGISTRY_AVAILABLE = False + +# 导出统一的接口 +__all__ = ['ComponentRegistry', 'register_component', 'REGISTRY_AVAILABLE'] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06e/config.py b/example/1d-linear-convection/weno3/python/06e/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06e/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06e/domain.py b/example/1d-linear-convection/weno3/python/06e/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06e/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06e/factories/base_factory.py b/example/1d-linear-convection/weno3/python/06e/factories/base_factory.py new file mode 100644 index 000000000..7b8b6105e --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06e/factories/base_factory.py @@ -0,0 +1,49 @@ +# factories/base_factory.py +""" +工厂基类 - 提供统一的组件创建接口 +""" + +from cfd_registry import ComponentRegistry +from typing import Any, Optional + +class BaseFactory: + """工厂基类,提供统一的组件创建方法""" + + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + """ + 统一创建组件的方法 + + Args: + category: 组件类别(如'boundary', 'flux'等) + name: 组件名称(如'periodic', 'rusanov'等) + *args, **kwargs: 传递给构造函数的参数 + + Returns: + 创建的组件实例 + + Raises: + ValueError: 如果组件不存在 + """ + name_lower = name.lower() + + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + # 获取可用组件列表 + available = ComponentRegistry.list_all().get(category, []) + + # 构建友好的错误信息 + if available: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"可用类型:{available}") + else: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"({category}类别下没有注册任何组件)") + + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + """获取指定类别的可用组件列表""" + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06e/flux/__init__.py b/example/1d-linear-convection/weno3/python/06e/flux/__init__.py new file mode 100644 index 000000000..432a36cb0 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06e/flux/__init__.py @@ -0,0 +1,7 @@ +# flux/__init__.py +from .base import InviscidFluxCalculator +from .factory import FluxCalculatorFactory + +# 确保子模块被导入以触发注册 +from . import rusanov +from . import engquist_osher \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06e/flux/base.py b/example/1d-linear-convection/weno3/python/06e/flux/base.py new file mode 100644 index 000000000..7a5a134fc --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06e/flux/base.py @@ -0,0 +1,25 @@ +# flux/base.py +""" +抽象通量计算基类 +""" + +from abc import ABC, abstractmethod + +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06e/flux/engquist_osher.py b/example/1d-linear-convection/weno3/python/06e/flux/engquist_osher.py new file mode 100644 index 000000000..34ad5f9c2 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06e/flux/engquist_osher.py @@ -0,0 +1,19 @@ +# flux/engquist_osher.py +""" +Engquist-Osher 通量计算器(线性对流专用) +""" + +from cfd_registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06e/flux/factory.py b/example/1d-linear-convection/weno3/python/06e/flux/factory.py new file mode 100644 index 000000000..c2f6f0756 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06e/flux/factory.py @@ -0,0 +1,25 @@ +# flux/factory.py +""" +通量计算器专用工厂(封装注册细节,提供清晰接口) +符合你希望“将创建逻辑封装在工厂中”的设计原则 +""" + +from factories.base_factory import BaseFactory + +class FluxCalculatorFactory: + """通量计算器工厂:隐藏注册类别和组件名称等细节""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD 主对象,包含 config.flux_type 等配置 + + Returns: + 实现 InviscidFluxCalculator 接口的通量计算器实例 + """ + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06e/flux/rusanov.py b/example/1d-linear-convection/weno3/python/06e/flux/rusanov.py new file mode 100644 index 000000000..62dd207ca --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06e/flux/rusanov.py @@ -0,0 +1,21 @@ +# flux/rusanov.py +""" +Rusanov(Lax-Friedrichs)通量计算器 +""" + +from cfd_registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06e/initial_condition.py b/example/1d-linear-convection/weno3/python/06e/initial_condition.py new file mode 100644 index 000000000..500cdfb81 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06e/initial_condition.py @@ -0,0 +1,91 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from factories.base_factory import BaseFactory + return BaseFactory.create_component('initial_condition', config.ic_type, config) + diff --git a/example/1d-linear-convection/weno3/python/06e/mesh.py b/example/1d-linear-convection/weno3/python/06e/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06e/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06e/plotter.py b/example/1d-linear-convection/weno3/python/06e/plotter.py new file mode 100644 index 000000000..e1f99ae25 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06e/plotter.py @@ -0,0 +1,109 @@ +# plotter.py + +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06e/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python/06e/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06e/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06e/reconstructor/base.py b/example/1d-linear-convection/weno3/python/06e/reconstructor/base.py new file mode 100644 index 000000000..094b47120 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06e/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def compute_face_values(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06e/reconstructor/eno.py b/example/1d-linear-convection/weno3/python/06e/reconstructor/eno.py new file mode 100644 index 000000000..8fde43334 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06e/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def compute_face_values(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06e/reconstructor/factory.py b/example/1d-linear-convection/weno3/python/06e/reconstructor/factory.py new file mode 100644 index 000000000..efa4a1446 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06e/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from cfd_registry import ComponentRegistry + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from factories.base_factory import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06e/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python/06e/reconstructor/weno3.py new file mode 100644 index 000000000..929061c13 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06e/reconstructor/weno3.py @@ -0,0 +1,59 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/python/06e/registry.py b/example/1d-linear-convection/weno3/python/06e/registry.py new file mode 100644 index 000000000..61be90500 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06e/registry.py @@ -0,0 +1,75 @@ +# registry.py (改进版) +""" +CFD组件注册系统 - 简洁高效版 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表""" + + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True # 控制是否打印注册信息 + + @classmethod + def set_verbose(cls, verbose: bool): + """设置是否打印注册信息""" + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + # 检查是否已注册 + if name in cls._registries[category]: + if cls._registries[category][name] == component_class: + # 相同类重复注册,静默跳过 + return + elif cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取组件类""" + if category not in cls._registries: + available = list(cls._registries.keys()) + raise ValueError(f"❌ 未知类别: {category} (可用类别: {available})") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + @classmethod + def get_count(cls) -> int: + """获取注册组件总数""" + return sum(len(components) for components in cls._registries.values()) + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06e/residual.py b/example/1d-linear-convection/weno3/python/06e/residual.py new file mode 100644 index 000000000..e0f96cd3c --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06e/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux.factory import FluxCalculatorFactory +from reconstructor import ReconstructorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + + self.reconstructor = ReconstructorFactory.create(self.config, self.domain) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._compute_face_values() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _compute_face_values(self): + """私有方法:界面值重建""" + self.reconstructor.compute_face_values(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06e/run_eno_weno.py b/example/1d-linear-convection/weno3/python/06e/run_eno_weno.py new file mode 100644 index 000000000..ff46f2267 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06e/run_eno_weno.py @@ -0,0 +1,54 @@ +# run_eno_weno.py + +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + #mesh = Mesh(ncells=100, L=2.0) + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行ENO3求解(使用你的链式调用) + print("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + config_eno3.with_reconstruction("eno", 3) # 显式指定3阶(也可省略,ENO默认3阶) + # 可选:覆盖默认值(如dt) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + cfd_eno3.run() # 求解并生成result字典 + + # 可选:快速验证ENO3结果 + # plotter.plot_quick(cfd_eno3.result, title="ENO3 Quick Check") + + # 3. 配置并运行WENO3求解(注意:WENO默认5阶,这里显式指定3阶) + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) # 显式指定3阶(默认是5阶) + # 可选:覆盖默认值 + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + cfd_weno3.run() + + # 4. 可选:保存结果(供离线绘图) + # cfd_eno3.save_result("eno3_result.npz") + # cfd_weno3.save_result("weno3_result.npz") + + # 5. 绘制ENO/WENO对比图 + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" # 可选:保存图片 + ) + +if __name__ == "__main__": + # 主程序入口 + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06e/solution.py b/example/1d-linear-convection/weno3/python/06e/solution.py new file mode 100644 index 000000000..3d4cba569 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06e/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06e/solver.py b/example/1d-linear-convection/weno3/python/06e/solver.py new file mode 100644 index 000000000..3de95b4bf --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06e/solver.py @@ -0,0 +1,67 @@ +from boundary import BoundaryConditionFactory +from initial_condition import InitialConditionFactory +from time_integration import TimeIntegrator,TimeIntegratorFactory +from mesh import Mesh +from domain import Domain +from solution import Solution +from config import CfdConfig + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, config, mesh): + self.config = config + self.domain = Domain(config, mesh) + self.solution = Solution(config, self.domain) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + def exact_solution(self): + """通用对流问题的解析解:u(x, T) = u0(x - c*T),周期边界""" + x = self.domain.mesh.xcc + T = self.config.final_time + c = self.config.wave_speed + L = self.domain.mesh.L + + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * T + L) % L + + # 获取 IC 实例并评估 + ic = InitialConditionFactory.create(self.config) + return ic.evaluate_at(x_shifted) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + self.integrator.step(dt) + t += dt + config.dt = dt_old + + # 整理标准化结果 + u_numerical = self.solution.u[self.domain.ist:self.domain.ied].copy() + self.result = { + "x": domain.mesh.xcc, + "numerical": u_numerical, + "analytical": self.exact_solution(), + "config": { + "scheme": self.config.recon_scheme, + "order": self.config.spatial_order, + "rk_order": self.config.rk_order, + "final_time": self.config.final_time + } + } + + return u_numerical diff --git a/example/1d-linear-convection/weno3/python/06e/time_integration.py b/example/1d-linear-convection/weno3/python/06e/time_integration.py new file mode 100644 index 000000000..51b2b20c1 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06e/time_integration.py @@ -0,0 +1,126 @@ +# time_integration.py + +""" +时间推进器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component +from residual import ResidualCalculator + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = ResidualCalculator(cfd) + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(使用装饰器注册) ---------------------- + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class TimeIntegratorFactory: + """时间推进器工厂""" + + @staticmethod + def create(cfd) -> 'TimeIntegrator': + """ + 创建时间推进器实例 + + Args: + cfd: CFD对象 + + Returns: + 时间推进器实例 + """ + from factories.base_factory import BaseFactory + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06f/boundary.py b/example/1d-linear-convection/weno3/python/06f/boundary.py new file mode 100644 index 000000000..3a271aa29 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06f/boundary.py @@ -0,0 +1,104 @@ +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from factories.base_factory import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) + diff --git a/example/1d-linear-convection/weno3/python/06f/cfd_registry.py b/example/1d-linear-convection/weno3/python/06f/cfd_registry.py new file mode 100644 index 000000000..c12eb106c --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06f/cfd_registry.py @@ -0,0 +1,62 @@ +# cfd_registry.py +""" +CFD注册系统统一导入模块 +所有其他模块从这里导入注册系统 +""" + +import sys +import os + +# 添加当前目录到路径,确保能找到registry.py +current_dir = os.path.dirname(os.path.abspath(__file__)) +if current_dir not in sys.path: + sys.path.insert(0, current_dir) + +try: + # 尝试导入注册系统 + from registry import ComponentRegistry, register_component + REGISTRY_AVAILABLE = True +except ImportError: + # 如果找不到,提供简单的空实现 + print("⚠️ 警告: 未找到 registry.py,使用最小化回退实现") + + class ComponentRegistry: + """最小化的注册系统回退实现""" + _registries = {} + + @classmethod + def register(cls, category, name, component_class): + if category not in cls._registries: + cls._registries[category] = {} + cls._registries[category][name] = component_class + + @classmethod + def get(cls, category, name): + if category not in cls._registries: + raise ValueError(f"未知类别: {category}") + if name not in cls._registries[category]: + raise ValueError(f"未找到: {category}.{name}") + return cls._registries[category][name] + + @classmethod + def create(cls, category, name, *args, **kwargs): + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) + for cat, comps in cls._registries.items()} + + def register_component(category, name=None): + """简化的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + REGISTRY_AVAILABLE = False + +# 导出统一的接口 +__all__ = ['ComponentRegistry', 'register_component', 'REGISTRY_AVAILABLE'] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06f/config.py b/example/1d-linear-convection/weno3/python/06f/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06f/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06f/domain.py b/example/1d-linear-convection/weno3/python/06f/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06f/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06f/factories/base_factory.py b/example/1d-linear-convection/weno3/python/06f/factories/base_factory.py new file mode 100644 index 000000000..7b8b6105e --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06f/factories/base_factory.py @@ -0,0 +1,49 @@ +# factories/base_factory.py +""" +工厂基类 - 提供统一的组件创建接口 +""" + +from cfd_registry import ComponentRegistry +from typing import Any, Optional + +class BaseFactory: + """工厂基类,提供统一的组件创建方法""" + + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + """ + 统一创建组件的方法 + + Args: + category: 组件类别(如'boundary', 'flux'等) + name: 组件名称(如'periodic', 'rusanov'等) + *args, **kwargs: 传递给构造函数的参数 + + Returns: + 创建的组件实例 + + Raises: + ValueError: 如果组件不存在 + """ + name_lower = name.lower() + + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + # 获取可用组件列表 + available = ComponentRegistry.list_all().get(category, []) + + # 构建友好的错误信息 + if available: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"可用类型:{available}") + else: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"({category}类别下没有注册任何组件)") + + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + """获取指定类别的可用组件列表""" + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06f/flux/__init__.py b/example/1d-linear-convection/weno3/python/06f/flux/__init__.py new file mode 100644 index 000000000..432a36cb0 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06f/flux/__init__.py @@ -0,0 +1,7 @@ +# flux/__init__.py +from .base import InviscidFluxCalculator +from .factory import FluxCalculatorFactory + +# 确保子模块被导入以触发注册 +from . import rusanov +from . import engquist_osher \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06f/flux/base.py b/example/1d-linear-convection/weno3/python/06f/flux/base.py new file mode 100644 index 000000000..7a5a134fc --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06f/flux/base.py @@ -0,0 +1,25 @@ +# flux/base.py +""" +抽象通量计算基类 +""" + +from abc import ABC, abstractmethod + +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06f/flux/engquist_osher.py b/example/1d-linear-convection/weno3/python/06f/flux/engquist_osher.py new file mode 100644 index 000000000..34ad5f9c2 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06f/flux/engquist_osher.py @@ -0,0 +1,19 @@ +# flux/engquist_osher.py +""" +Engquist-Osher 通量计算器(线性对流专用) +""" + +from cfd_registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06f/flux/factory.py b/example/1d-linear-convection/weno3/python/06f/flux/factory.py new file mode 100644 index 000000000..c2f6f0756 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06f/flux/factory.py @@ -0,0 +1,25 @@ +# flux/factory.py +""" +通量计算器专用工厂(封装注册细节,提供清晰接口) +符合你希望“将创建逻辑封装在工厂中”的设计原则 +""" + +from factories.base_factory import BaseFactory + +class FluxCalculatorFactory: + """通量计算器工厂:隐藏注册类别和组件名称等细节""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD 主对象,包含 config.flux_type 等配置 + + Returns: + 实现 InviscidFluxCalculator 接口的通量计算器实例 + """ + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06f/flux/rusanov.py b/example/1d-linear-convection/weno3/python/06f/flux/rusanov.py new file mode 100644 index 000000000..62dd207ca --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06f/flux/rusanov.py @@ -0,0 +1,21 @@ +# flux/rusanov.py +""" +Rusanov(Lax-Friedrichs)通量计算器 +""" + +from cfd_registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06f/initial_condition.py b/example/1d-linear-convection/weno3/python/06f/initial_condition.py new file mode 100644 index 000000000..893034c71 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06f/initial_condition.py @@ -0,0 +1,103 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def exact_solution(self, cfd): + """ + 默认解析解:线性对流 u(x, t) = u0(x - c * t) + 子类可重写以支持更复杂物理 + """ + x = cfd.domain.mesh.xcc + t = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * t + L) % L + return self.evaluate_at(x_shifted) + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from factories.base_factory import BaseFactory + return BaseFactory.create_component('initial_condition', config.ic_type, config) + diff --git a/example/1d-linear-convection/weno3/python/06f/mesh.py b/example/1d-linear-convection/weno3/python/06f/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06f/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06f/plotter.py b/example/1d-linear-convection/weno3/python/06f/plotter.py new file mode 100644 index 000000000..e1f99ae25 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06f/plotter.py @@ -0,0 +1,109 @@ +# plotter.py + +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06f/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python/06f/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06f/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06f/reconstructor/base.py b/example/1d-linear-convection/weno3/python/06f/reconstructor/base.py new file mode 100644 index 000000000..094b47120 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06f/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def compute_face_values(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06f/reconstructor/eno.py b/example/1d-linear-convection/weno3/python/06f/reconstructor/eno.py new file mode 100644 index 000000000..8fde43334 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06f/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def compute_face_values(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06f/reconstructor/factory.py b/example/1d-linear-convection/weno3/python/06f/reconstructor/factory.py new file mode 100644 index 000000000..efa4a1446 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06f/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from cfd_registry import ComponentRegistry + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from factories.base_factory import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06f/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python/06f/reconstructor/weno3.py new file mode 100644 index 000000000..929061c13 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06f/reconstructor/weno3.py @@ -0,0 +1,59 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/python/06f/registry.py b/example/1d-linear-convection/weno3/python/06f/registry.py new file mode 100644 index 000000000..61be90500 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06f/registry.py @@ -0,0 +1,75 @@ +# registry.py (改进版) +""" +CFD组件注册系统 - 简洁高效版 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表""" + + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True # 控制是否打印注册信息 + + @classmethod + def set_verbose(cls, verbose: bool): + """设置是否打印注册信息""" + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + # 检查是否已注册 + if name in cls._registries[category]: + if cls._registries[category][name] == component_class: + # 相同类重复注册,静默跳过 + return + elif cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取组件类""" + if category not in cls._registries: + available = list(cls._registries.keys()) + raise ValueError(f"❌ 未知类别: {category} (可用类别: {available})") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + @classmethod + def get_count(cls) -> int: + """获取注册组件总数""" + return sum(len(components) for components in cls._registries.values()) + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06f/residual.py b/example/1d-linear-convection/weno3/python/06f/residual.py new file mode 100644 index 000000000..e0f96cd3c --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06f/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux.factory import FluxCalculatorFactory +from reconstructor import ReconstructorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + + self.reconstructor = ReconstructorFactory.create(self.config, self.domain) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._compute_face_values() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _compute_face_values(self): + """私有方法:界面值重建""" + self.reconstructor.compute_face_values(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06f/run_eno_weno.py b/example/1d-linear-convection/weno3/python/06f/run_eno_weno.py new file mode 100644 index 000000000..ff46f2267 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06f/run_eno_weno.py @@ -0,0 +1,54 @@ +# run_eno_weno.py + +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + #mesh = Mesh(ncells=100, L=2.0) + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行ENO3求解(使用你的链式调用) + print("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + config_eno3.with_reconstruction("eno", 3) # 显式指定3阶(也可省略,ENO默认3阶) + # 可选:覆盖默认值(如dt) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + cfd_eno3.run() # 求解并生成result字典 + + # 可选:快速验证ENO3结果 + # plotter.plot_quick(cfd_eno3.result, title="ENO3 Quick Check") + + # 3. 配置并运行WENO3求解(注意:WENO默认5阶,这里显式指定3阶) + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) # 显式指定3阶(默认是5阶) + # 可选:覆盖默认值 + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + cfd_weno3.run() + + # 4. 可选:保存结果(供离线绘图) + # cfd_eno3.save_result("eno3_result.npz") + # cfd_weno3.save_result("weno3_result.npz") + + # 5. 绘制ENO/WENO对比图 + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" # 可选:保存图片 + ) + +if __name__ == "__main__": + # 主程序入口 + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06f/solution.py b/example/1d-linear-convection/weno3/python/06f/solution.py new file mode 100644 index 000000000..3d4cba569 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06f/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06f/solver.py b/example/1d-linear-convection/weno3/python/06f/solver.py new file mode 100644 index 000000000..8b369feed --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06f/solver.py @@ -0,0 +1,57 @@ +from boundary import BoundaryConditionFactory +from initial_condition import InitialConditionFactory +from time_integration import TimeIntegrator,TimeIntegratorFactory +from mesh import Mesh +from domain import Domain +from solution import Solution +from config import CfdConfig + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, config, mesh): + self.config = config + self.domain = Domain(config, mesh) + self.solution = Solution(config, self.domain) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + self.integrator.step(dt) + t += dt + config.dt = dt_old + + self.result = self._assemble_result() + return self.result["numerical"] + + def _assemble_result(self): + """组装标准化求解结果字典""" + u_numerical = self.solution.u[self.domain.ist:self.domain.ied].copy() + ic = InitialConditionFactory.create(self.config) + analytical = ic.exact_solution(self) + return { + "x": self.domain.mesh.xcc, + "numerical": u_numerical, + "analytical": analytical, + "config": { + "scheme": self.config.recon_scheme, + "order": self.config.spatial_order, + "rk_order": self.config.rk_order, + "final_time": self.config.final_time + } + } \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06f/time_integration.py b/example/1d-linear-convection/weno3/python/06f/time_integration.py new file mode 100644 index 000000000..51b2b20c1 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06f/time_integration.py @@ -0,0 +1,126 @@ +# time_integration.py + +""" +时间推进器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component +from residual import ResidualCalculator + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = ResidualCalculator(cfd) + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(使用装饰器注册) ---------------------- + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class TimeIntegratorFactory: + """时间推进器工厂""" + + @staticmethod + def create(cfd) -> 'TimeIntegrator': + """ + 创建时间推进器实例 + + Args: + cfd: CFD对象 + + Returns: + 时间推进器实例 + """ + from factories.base_factory import BaseFactory + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06g/boundary.py b/example/1d-linear-convection/weno3/python/06g/boundary.py new file mode 100644 index 000000000..3a271aa29 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06g/boundary.py @@ -0,0 +1,104 @@ +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 边界条件抽象基类(统一接口) ---------------------- +class BoundaryCondition(ABC): + """边界条件抽象基类:定义所有边界条件必须实现的接口""" + def __init__(self, cfd): + self.cfd = cfd + self.domain = cfd.domain + self.config = cfd.config # 可从配置读取边界参数(如进口速度、固壁温度等) + + @abstractmethod + def apply(self, u): + """ + 应用边界条件到解数组 + :param u: 包含ghost层的解数组(会直接修改该数组) + :return: None + """ + pass + +# ---------------------- 具体边界条件实现(使用装饰器注册)-------------------- + +@register_component('boundary', 'periodic') +class PeriodicBoundary(BoundaryCondition): + """周期边界条件(1D专用)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左ghost层 = 右物理层 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ied - 1 - ig] + + # 右ghost层 = 左物理层 + for ig in range(nghosts): + u[ied + ig] = u[ist + ig] + +@register_component('boundary', 'dirichlet') +class DirichletBoundary(BoundaryCondition): + """Dirichlet(固定值)边界条件(如进口固定速度、固壁零速度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # ✅ 修复:使用 getattr 而不是 .get() 方法 + # 旧代码: left_value = self.config.get("left_boundary_value", 1.0) + # 新代码: 使用 getattr,它对于类和实例都适用 + left_value = getattr(self.config, "left_boundary_value", 1.0) + + # 左边界(进口)固定值 + for ig in range(nghosts): + u[ist - 1 - ig] = left_value + + # ✅ 修复:同样使用 getattr + right_value = getattr(self.config, "right_boundary_value", 2.0) + + # 右边界(出口)固定值 + for ig in range(nghosts): + u[ied + ig] = right_value + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Dirichlet边界: 左值={left_value}, 右值={right_value}") + +@register_component('boundary', 'neumann') +class NeumannBoundary(BoundaryCondition): + """Neumann(零梯度)边界条件(如出口无梯度)""" + def apply(self, u): + nghosts = self.domain.nghosts + ist = self.domain.ist + ied = self.domain.ied + + # 左边界零梯度 + for ig in range(nghosts): + u[ist - 1 - ig] = u[ist + ig] + + # 右边界零梯度 + for ig in range(nghosts): + u[ied + ig] = u[ied - 1 - ig] + + # 调试信息 + if hasattr(self.config, 'debug') and self.config.debug: + print(f" 应用Neumann边界: 零梯度") + +class BoundaryConditionFactory: + """边界条件工厂""" + + @staticmethod + def create(cfd) -> 'BoundaryCondition': + """ + 创建边界条件实例 + + Args: + cfd: CFD对象 + + Returns: + 边界条件实例 + """ + from factories.base_factory import BaseFactory + bc_type = cfd.config.boundary_type + return BaseFactory.create_component('boundary', bc_type, cfd) + diff --git a/example/1d-linear-convection/weno3/python/06g/cfd_registry.py b/example/1d-linear-convection/weno3/python/06g/cfd_registry.py new file mode 100644 index 000000000..c12eb106c --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06g/cfd_registry.py @@ -0,0 +1,62 @@ +# cfd_registry.py +""" +CFD注册系统统一导入模块 +所有其他模块从这里导入注册系统 +""" + +import sys +import os + +# 添加当前目录到路径,确保能找到registry.py +current_dir = os.path.dirname(os.path.abspath(__file__)) +if current_dir not in sys.path: + sys.path.insert(0, current_dir) + +try: + # 尝试导入注册系统 + from registry import ComponentRegistry, register_component + REGISTRY_AVAILABLE = True +except ImportError: + # 如果找不到,提供简单的空实现 + print("⚠️ 警告: 未找到 registry.py,使用最小化回退实现") + + class ComponentRegistry: + """最小化的注册系统回退实现""" + _registries = {} + + @classmethod + def register(cls, category, name, component_class): + if category not in cls._registries: + cls._registries[category] = {} + cls._registries[category][name] = component_class + + @classmethod + def get(cls, category, name): + if category not in cls._registries: + raise ValueError(f"未知类别: {category}") + if name not in cls._registries[category]: + raise ValueError(f"未找到: {category}.{name}") + return cls._registries[category][name] + + @classmethod + def create(cls, category, name, *args, **kwargs): + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls): + return {cat: list(comps.keys()) + for cat, comps in cls._registries.items()} + + def register_component(category, name=None): + """简化的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator + + REGISTRY_AVAILABLE = False + +# 导出统一的接口 +__all__ = ['ComponentRegistry', 'register_component', 'REGISTRY_AVAILABLE'] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06g/config.py b/example/1d-linear-convection/weno3/python/06g/config.py new file mode 100644 index 000000000..b3ad4749d --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06g/config.py @@ -0,0 +1,41 @@ +# config.py +class CfdConfig: + def __init__(self): + self.ic_type = "step" + self.recon_scheme = "eno" # 0=ENO, 1=WENO + self.flux_type = "rusanov" # 0=Rusanov, 1=Engquist-Osher + self.rk_order = 1 + self.wave_speed = 1.0 + self.final_time = 0.625 + self.dt = 0.025 + + self.boundary_type = "periodic" + self.left_boundary_value = 1.0 # Dirichlet左边界值 + self.right_boundary_value = 2.0 # Dirichlet右边界值 + + self.spatial_order = 2 + + def with_reconstruction(self, scheme, order=None): + """专用配置:重建方案(链式调用)""" + self.recon_scheme = scheme.lower() # 统一小写,避免大小写问题 + + # 智能默认阶数 + if order is not None: + self.spatial_order = order + else: + if self.recon_scheme.startswith("weno"): + self.spatial_order = 5 + elif self.recon_scheme == "eno": + self.spatial_order = 3 # ENO默认3阶 + else: + raise ValueError(f"不支持的重建格式:{scheme}(仅支持 eno/weno)") + + return self + + def with_boundary(self, bc_type, left_value=None, right_value=None): + self.boundary_type = bc_type + if left_value is not None: + self.left_boundary_value = left_value + if right_value is not None: + self.right_boundary_value = right_value + return self \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06g/domain.py b/example/1d-linear-convection/weno3/python/06g/domain.py new file mode 100644 index 000000000..ae1cca4e7 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06g/domain.py @@ -0,0 +1,56 @@ +# domain.py +from mesh import Mesh + +class Domain: + """计算域:管理物理区域、ghost层、索引映射等逻辑,依赖 Mesh 提供几何信息""" + def __init__(self, config, mesh): + """ + 初始化计算域 + :param mesh: Mesh实例(静态网格属性) + :param config: CfdConfig实例(包含recon_scheme/spatial_order) + """ + self.config = config + self.mesh = mesh + + # 核心:根据重建格式动态计算nghosts + self.nghosts = self._calc_nghosts() + + # 基于nghosts推导索引 + self.ist = self.nghosts # 物理网格起始索引 + self.ied = self.ist + mesh.ncells # 物理网格结束索引 + self.ntcells = mesh.ncells + 2 * self.nghosts # 总网格数(含ghost) + + # 可选:调试信息(可后续移除) + # print(f"mesh.ncells={mesh.ncells}") + # print(f"self.config.spatial_order={self.config.spatial_order}") + # print(f"self.nghosts={self.nghosts}") + # print(f"self.ist={self.ist}") + # print(f"self.ied={self.ied}") + + def _calc_nghosts(self): + """内部方法:根据重建格式和阶数计算ghost层数量""" + scheme = self.config.recon_scheme.lower() + order = self.config.spatial_order + + if scheme is None: + raise ValueError("必须先通过 with_reconstruction 设置重建格式!") + + # ✅ 统一处理:所有以 "weno" 开头的都按 WENO 规则计算 ghost 层数 + if scheme == "eno": + nghosts = order + elif scheme.startswith("weno"): # ← 关键修改:支持 "weno", "weno3", "weno5", "weno-z" 等 + nghosts = order // 2 + 1 + else: + raise ValueError(f"未知重建格式 {scheme},无法计算ghost层!") + + if nghosts <= 0: + raise ValueError(f"计算得到的ghost层数量无效:{nghosts}(阶数{order},格式{scheme})") + return nghosts + + def is_physical_cell(self, idx): + """判断索引是否在物理网格范围内""" + return self.ist <= idx < self.ied + + def get_physical_indices(self): + """返回物理网格的索引范围(可直接用于循环)""" + return range(self.ist, self.ied) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06g/factories/base_factory.py b/example/1d-linear-convection/weno3/python/06g/factories/base_factory.py new file mode 100644 index 000000000..7b8b6105e --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06g/factories/base_factory.py @@ -0,0 +1,49 @@ +# factories/base_factory.py +""" +工厂基类 - 提供统一的组件创建接口 +""" + +from cfd_registry import ComponentRegistry +from typing import Any, Optional + +class BaseFactory: + """工厂基类,提供统一的组件创建方法""" + + @classmethod + def create_component(cls, category: str, name: str, *args, **kwargs) -> Any: + """ + 统一创建组件的方法 + + Args: + category: 组件类别(如'boundary', 'flux'等) + name: 组件名称(如'periodic', 'rusanov'等) + *args, **kwargs: 传递给构造函数的参数 + + Returns: + 创建的组件实例 + + Raises: + ValueError: 如果组件不存在 + """ + name_lower = name.lower() + + try: + return ComponentRegistry.create(category, name_lower, *args, **kwargs) + except ValueError as e: + # 获取可用组件列表 + available = ComponentRegistry.list_all().get(category, []) + + # 构建友好的错误信息 + if available: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"可用类型:{available}") + else: + error_msg = (f"不支持的{category}类型:'{name}'\n" + f"({category}类别下没有注册任何组件)") + + raise ValueError(error_msg) from e + + @classmethod + def get_available_components(cls, category: str) -> list: + """获取指定类别的可用组件列表""" + return ComponentRegistry.list_all().get(category, []) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06g/flux/__init__.py b/example/1d-linear-convection/weno3/python/06g/flux/__init__.py new file mode 100644 index 000000000..432a36cb0 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06g/flux/__init__.py @@ -0,0 +1,7 @@ +# flux/__init__.py +from .base import InviscidFluxCalculator +from .factory import FluxCalculatorFactory + +# 确保子模块被导入以触发注册 +from . import rusanov +from . import engquist_osher \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06g/flux/base.py b/example/1d-linear-convection/weno3/python/06g/flux/base.py new file mode 100644 index 000000000..7a5a134fc --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06g/flux/base.py @@ -0,0 +1,25 @@ +# flux/base.py +""" +抽象通量计算基类 +""" + +from abc import ABC, abstractmethod + +class InviscidFluxCalculator(ABC): + """无粘通量计算抽象基类:定义一维CFD通量计算接口""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.mesh = cfd.domain.mesh + self.wave_speed = self.config.wave_speed + + @abstractmethod + def compute(self, q_face_left, q_face_right, flux): + """ + 计算无粘通量(核心接口) + :param q_face_left: 左界面值数组 + :param q_face_right: 右界面值数组 + :param flux: 输出通量数组 + :return: None + """ + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06g/flux/engquist_osher.py b/example/1d-linear-convection/weno3/python/06g/flux/engquist_osher.py new file mode 100644 index 000000000..34ad5f9c2 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06g/flux/engquist_osher.py @@ -0,0 +1,19 @@ +# flux/engquist_osher.py +""" +Engquist-Osher 通量计算器(线性对流专用) +""" + +from cfd_registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'engquist-osher') +class EngquistOsherFluxCalculator(InviscidFluxCalculator): + """Engquist-Osher通量(线性对流专用)""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + c = self.wave_speed + cp = 0.5 * (c + abs(c)) + cm = 0.5 * (c - abs(c)) + u_L = q_face_left[i] + u_R = q_face_right[i] + flux[i] = cp * u_L + cm * u_R \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06g/flux/factory.py b/example/1d-linear-convection/weno3/python/06g/flux/factory.py new file mode 100644 index 000000000..c2f6f0756 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06g/flux/factory.py @@ -0,0 +1,25 @@ +# flux/factory.py +""" +通量计算器专用工厂(封装注册细节,提供清晰接口) +符合你希望“将创建逻辑封装在工厂中”的设计原则 +""" + +from factories.base_factory import BaseFactory + +class FluxCalculatorFactory: + """通量计算器工厂:隐藏注册类别和组件名称等细节""" + + @staticmethod + def create(cfd) -> 'InviscidFluxCalculator': + """ + 创建通量计算器实例 + + Args: + cfd: CFD 主对象,包含 config.flux_type 等配置 + + Returns: + 实现 InviscidFluxCalculator 接口的通量计算器实例 + """ + flux_type = cfd.config.flux_type + return BaseFactory.create_component('flux', flux_type, cfd) + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06g/flux/rusanov.py b/example/1d-linear-convection/weno3/python/06g/flux/rusanov.py new file mode 100644 index 000000000..62dd207ca --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06g/flux/rusanov.py @@ -0,0 +1,21 @@ +# flux/rusanov.py +""" +Rusanov(Lax-Friedrichs)通量计算器 +""" + +from cfd_registry import register_component +from .base import InviscidFluxCalculator + +@register_component('flux', 'rusanov') +class RusanovFluxCalculator(InviscidFluxCalculator): + """Rusanov(Lax-Friedrichs)通量""" + def compute(self, q_face_left, q_face_right, flux): + for i in range(self.mesh.nnodes): + u_L = q_face_left[i] + u_R = q_face_right[i] + c_L = self.wave_speed + c_R = self.wave_speed + F_L = c_L * u_L # Flux from left state + F_R = c_R * u_R # Flux from right state + Smax = max(abs(c_L), abs(c_R)) # Maximum wave speed + flux[i] = 0.5 * (F_L + F_R) - 0.5 * Smax * (u_R - u_L) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06g/initial_condition.py b/example/1d-linear-convection/weno3/python/06g/initial_condition.py new file mode 100644 index 000000000..963e9cdde --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06g/initial_condition.py @@ -0,0 +1,107 @@ +# initial_condition.py + +""" +初始条件模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +import numpy as np +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 初始条件抽象基类 ---------------------- +class InitialCondition(ABC): + """初始条件基类""" + def __init__(self, config): + self.config = config + + @abstractmethod + def apply(self, solution): + """将初始条件应用到 solution 的内部区域""" + pass + + @abstractmethod + def evaluate_at(self, x): + """纯数学函数:给定 x,返回 u(x),不涉及网格或边界""" + pass + + def exact_solution(self, cfd): + """ + 默认解析解:线性对流 u(x, t) = u0(x - c * t) + 子类可重写以支持更复杂物理 + """ + x = cfd.domain.mesh.xcc + t = cfd.config.final_time + c = cfd.config.wave_speed + L = cfd.domain.mesh.L + # 周期平移:确保在 [x0, x0 + L) 内 + x_shifted = (x - c * t + L) % L + return self.evaluate_at(x_shifted) + + def _apply_to_interior(self, solution, values): + domain = solution.domain + for i in range(domain.ist, domain.ied): + j = i - domain.ist + solution.u[i] = values[j] + + +# ---------------------- 2. 具体初始条件实现(使用装饰器注册) ---------------------- + +@register_component('initial_condition', 'step') +class StepFunctionIC(InitialCondition): + def evaluate_at(self, x): + u0 = np.ones_like(x) + mask = (x >= 0.5) & (x <= 1.0) + u0[mask] = 2.0 + return u0 + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'sin') +class SineWaveIC(InitialCondition): + def evaluate_at(self, x): + L = getattr(self.config, "domain_length", 2.0) + return np.sin(2 * np.pi * x / L) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +@register_component('initial_condition', 'gaussian') +class GaussianPulseIC(InitialCondition): + def evaluate_at(self, x): + center = getattr(self.config, "pulse_center", 0.5) + width = getattr(self.config, "pulse_width", 0.1) + return np.exp(-((x - center) / width) ** 2) + + def apply(self, solution): + x = solution.domain.mesh.xcc + u0 = self.evaluate_at(x) + self._apply_to_interior(solution, u0) + +class InitialConditionFactory: + """初始条件工厂""" + + @classmethod + def create(cls, config) -> 'InitialCondition': + """ + 创建初始条件实例 + + Args: + ic_type: 初始条件类型(如'step', 'sin'等) + config: 配置对象 + + Returns: + 初始条件实例 + """ + from factories.base_factory import BaseFactory + return BaseFactory.create_component('initial_condition', config.ic_type, config) + + @classmethod + def get_exact_solution(cls, cfd): + return cls.create(cfd.config).exact_solution(cfd) # 一行! + diff --git a/example/1d-linear-convection/weno3/python/06g/mesh.py b/example/1d-linear-convection/weno3/python/06g/mesh.py new file mode 100644 index 000000000..bb8553137 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06g/mesh.py @@ -0,0 +1,26 @@ +# mesh.py +import numpy as np + +# Mesh class: defines computational grid +class Mesh: + def __init__(self): + self.xmin = 0.0 + self.xmax = 2.0 + self.ncells = 40 + self.nnodes = self.ncells + 1 + self.nx = self.ncells + self.x = np.zeros(self.nnodes) + self.xcc = np.zeros(self.ncells) + self.init_mesh() + + def init_mesh(self): + self.L = self.xmax - self.xmin + self.dx = self.L / self.ncells + + # Generate node coordinates + for i in range(self.nnodes): + self.x[i] = self.xmin + i * self.dx + + # Generate cell center coordinates + for i in range(self.ncells): + self.xcc[i] = 0.5 * (self.x[i] + self.x[i+1]) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06g/plotter.py b/example/1d-linear-convection/weno3/python/06g/plotter.py new file mode 100644 index 000000000..e1f99ae25 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06g/plotter.py @@ -0,0 +1,109 @@ +# plotter.py + +import matplotlib.pyplot as plt +import numpy as np +import inflect + +class CFDPlotter: + """CFD可视化工具类:解耦绘图逻辑""" + def __init__(self): + # 预设样式(统一管理) + self.default_styles = { + "numerical": {"color": "blue", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + "analytical": {"color": "red", "linestyle": "--", "marker": "", "linewidth": 1.5}, + "comparison": [ + {"color": "black", "linestyle": "-", "marker": "o", "markerfacecolor": "none"}, + {"color": "blue", "linestyle": "--", "marker": "s", "markerfacecolor": "none"}, + {"color": "green", "linestyle": ":", "marker": "^", "markerfacecolor": "none"}, + ] + } + self.p = inflect.engine() + + def plot_quick(self, cfd_result, title=None, show=True, save_path=None): + """轻量即时绘图(快速验证结果)""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + rk_str = self.p.ordinal(cfd_result["config"]["rk_order"]) + title = (f'1D Convection (t={cfd_result["config"]["final_time"]:.3f})\n' + f'{cfd_result["config"]["order"]}th-order {cfd_result["config"]["scheme"].upper()} + {rk_str}-order RK') + + # 绘制数值解 + plt.plot( + cfd_result["x"], cfd_result["numerical"], + label=f'Numerical ({cfd_result["config"]["scheme"].upper()})', + **self.default_styles["numerical"], + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + cfd_result["x"], cfd_result["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def plot_comparison(self, result_list, title=None, show=True, save_path=None): + """多格式/多精度对比绘图""" + plt.figure("OneFLOW-CFD Solver",figsize=(10, 6)) + + # 自动生成标题 + if title is None: + schemes = [f'{r["config"]["scheme"].upper()}{r["config"]["order"]}' for r in result_list] + rk_str = self.p.ordinal(result_list[0]["config"]["rk_order"]) + title = (f'1D Convection Comparison (t={result_list[0]["config"]["final_time"]:.3f})\n' + f'{", ".join(schemes)} + {rk_str}-order RK') + + # 绘制多个数值解 + for i, res in enumerate(result_list): + style = self.default_styles["comparison"][i % len(self.default_styles["comparison"])] + label = f'Numerical ({res["config"]["scheme"].upper()}{res["config"]["order"]})' + plt.plot( + res["x"], res["numerical"], + label=label, + **style, + markersize=5, linewidth=0.5 + ) + + # 绘制解析解 + plt.plot( + result_list[0]["x"], result_list[0]["analytical"], + label='Analytical', + **self.default_styles["analytical"] + ) + + # 通用样式 + self._set_common_style(title) + + if save_path: + plt.savefig(save_path, dpi=300, bbox_inches='tight') + if show: + plt.show() + plt.close() + + def _set_common_style(self, title): + """统一设置图表样式""" + plt.title(title, fontsize=12) + plt.xlabel('x', fontsize=10) + plt.ylabel('u', fontsize=10) + plt.legend(fontsize=9) + plt.grid(True, color='gray', linestyle='--', linewidth=0.5, alpha=0.7) + plt.tight_layout() + +# 快捷函数:ENO/WENO对比绘图 +def plot_eno_weno_comparison(eno_result, weno_result, save_path=None): + plotter = CFDPlotter() + plotter.plot_comparison( + result_list=[eno_result, weno_result], + save_path=save_path + ) \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06g/reconstructor/__init__.py b/example/1d-linear-convection/weno3/python/06g/reconstructor/__init__.py new file mode 100644 index 000000000..46a023a23 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06g/reconstructor/__init__.py @@ -0,0 +1,4 @@ +# reconstructor/__init__.py +from .factory import ReconstructorFactory +from .base import Reconstructor +# 注意:我们不直接导入具体的重构器类,让工厂动态加载 \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06g/reconstructor/base.py b/example/1d-linear-convection/weno3/python/06g/reconstructor/base.py new file mode 100644 index 000000000..094b47120 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06g/reconstructor/base.py @@ -0,0 +1,7 @@ +# reconstructor/base.py +from abc import ABC, abstractmethod + +class Reconstructor(ABC): + @abstractmethod + def compute_face_values(self, q, cfd): + pass \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06g/reconstructor/eno.py b/example/1d-linear-convection/weno3/python/06g/reconstructor/eno.py new file mode 100644 index 000000000..8fde43334 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06g/reconstructor/eno.py @@ -0,0 +1,90 @@ +# reconstructor/eno.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +# ---------------------- 1. 重构系数初始化函数 ---------------------- +def _init_eno_coef(spatial_order, coef): + """Initialize reconstruction coefficients for different spatial orders.""" + if spatial_order == 1: + coef[0] = [1.0] + coef[1] = [1.0] + elif spatial_order == 2: + coef[0] = [3.0/2.0, -1.0/2.0] + coef[1] = [1.0/2.0, 1.0/2.0] + coef[2] = [-1.0/2.0, 3.0/2.0] + elif spatial_order == 3: + coef[0] = [ 11.0/6.0, -7.0/6.0, 1.0/3.0 ] + coef[1] = [ 1.0/3.0, 5.0/6.0, -1.0/6.0 ] + coef[2] = [ -1.0/6.0, 5.0/6.0, 1.0/3.0 ] + coef[3] = [ 1.0/3.0, -7.0/6.0, 11.0/6.0 ] + elif spatial_order == 4: + coef[0] = [ 25.0/12.0, -23.0/12.0, 13.0/12.0, -1.0/4.0 ] + coef[1] = [ 1.0/4.0, 13.0/12.0, -5.0/12.0, 1.0/12.0 ] + coef[2] = [ -1.0/12.0, 7.0/12.0, 7.0/12.0, -1.0/12.0 ] + coef[3] = [ 1.0/12.0, -5.0/12.0, 13.0/12.0, 1.0/4.0 ] + coef[4] = [ -1.0/4.0, 13.0/12.0, -23.0/12.0, 25.0/12.0 ] + elif spatial_order == 5: + coef[0] = [ 137.0/60.0, -163.0/60.0, 137.0/60.0, -21.0/20.0, 1.0/5.0 ] + coef[1] = [ 1.0/5.0, 77.0/60.0, -43.0/60.0, 17.0/60.0, -1.0/20.0 ] + coef[2] = [ -1.0/20.0, 9.0/20.0, 47.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[3] = [ 1.0/30.0, -13.0/60.0, 47.0/60.0, 9.0/20.0, -1.0/20.0 ] + coef[4] = [ -1.0/20.0, 17.0/60.0, -43.0/60.0, 77.0/60.0, 1.0/5.0 ] + coef[5] = [ 1.0/5.0, -21.0/20.0, 137.0/60.0, -163.0/60.0, 137.0/60.0 ] + elif spatial_order == 6: + coef[0] = [ 49.0/20.0, -71.0/20.0, 79.0/20.0, -163.0/60.0, 31.0/30.0, -1.0/6.0 ] + coef[1] = [ 1.0/6.0, 29.0/20.0, -21.0/20.0, 37.0/60.0, -13.0/60.0, 1.0/30.0 ] + coef[2] = [ -1.0/30.0, 11.0/30.0, 19.0/20.0, -23.0/60.0, 7.0/60.0, -1.0/60.0 ] + coef[3] = [ 1.0/60.0, -2.0/15.0, 37.0/60.0, 37.0/60.0, -2.0/15.0, 1.0/60.0 ] + coef[4] = [ -1.0/60.0, 7.0/60.0, -23.0/60.0, 19.0/20.0, 11.0/30.0, -1.0/30.0 ] + coef[5] = [ 1.0/30.0, -13.0/60.0, 37.0/60.0, -21.0/20.0, 29.0/20.0, 1.0/6.0 ] + coef[6] = [ -1.0/6.0, 31.0/30.0, -163.0/60.0, 79.0/20.0, -71.0/20.0, 49.0/20.0 ] + elif spatial_order == 7: + coef[0] = [ 363.0/140.0, -617.0/140.0, 853.0/140.0, -2341.0/420.0, 667.0/210.0, -43.0/42.0, 1.0/7.0 ] + coef[1] = [ 1.0/7.0, 223.0/140.0, -197.0/140.0, 153.0/140.0, -241.0/420.0, 37.0/210.0, -1.0/42.0 ] + coef[2] = [ -1.0/42.0, 13.0/42.0, 153.0/140.0, -241.0/420.0, 109.0/420.0, -31.0/420.0, 1.0/105.0 ] + coef[3] = [ 1.0/105.0, -19.0/210.0, 107.0/210.0, 319.0/420.0, -101.0/420.0, 5.0/84.0, -1.0/140.0 ] + coef[4] = [ -1.0/140.0, 5.0/84.0, -101.0/420.0, 319.0/420.0, 107.0/210.0, -19.0/210.0, 1.0/105.0 ] + coef[5] = [ 1.0/105.0, -31.0/420.0, 109.0/420.0, -241.0/420.0, 153.0/140.0, 13.0/42.0, -1.0/42.0 ] + coef[6] = [ -1.0/42.0, 37.0/210.0, -241.0/420.0, 153.0/140.0, -197.0/140.0, 223.0/140.0, 1.0/7.0 ] + coef[7] = [ 1.0/7.0, -43.0/42.0, 667.0/210.0, -2341.0/420.0, 853.0/140.0, -617.0/140.0, 363.0/140.0 ] + +# ---------------------- 2. ENO 重构器 ---------------------- + +@register_component('reconstructor', 'eno') +class EnoReconstructor(Reconstructor): + def __init__(self, spatial_order, ntcells): + self.spatial_order = spatial_order + self.ntcells = ntcells + self.lmc = np.zeros(self.ntcells, dtype=int) + self.coef = np.zeros((spatial_order + 1, spatial_order)) + self.dd = np.zeros((spatial_order, self.ntcells)) + _init_eno_coef(self.spatial_order, self.coef) + + def compute_face_values(self, q, cfd): + """ENO reconstruction of interface values""" + self.dd[0, :] = q + for m in range(1, self.spatial_order): + for j in range(self.ntcells - m): + self.dd[m, j] = self.dd[m-1, j+1] - self.dd[m-1, j] + + domain = cfd.domain + solution = cfd.solution + + for i in range(domain.ist - 1, domain.ied + 1): + self.lmc[i] = i + for m in range(1, self.spatial_order): + if abs(self.dd[m, self.lmc[i] - 1]) < abs(self.dd[m, self.lmc[i]]): + self.lmc[i] -= 1 + + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + k1 = self.lmc[i - 1] + k2 = self.lmc[i] + r1 = i - 1 - k1 + r2 = i - k2 + solution.q_face_left[j] = 0.0 + solution.q_face_right[j] = 0.0 + for m in range(self.spatial_order): + solution.q_face_left[j] += q[k1 + m] * self.coef[r1 + 1, m] + solution.q_face_right[j] += q[k2 + m] * self.coef[r2, m] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06g/reconstructor/factory.py b/example/1d-linear-convection/weno3/python/06g/reconstructor/factory.py new file mode 100644 index 000000000..efa4a1446 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06g/reconstructor/factory.py @@ -0,0 +1,42 @@ +# reconstructor/factory.py +from .eno import EnoReconstructor +from .weno3 import Weno3Reconstructor +from cfd_registry import ComponentRegistry + +class ReconstructorFactory: + @staticmethod + def create(config, domain): + scheme = config.recon_scheme.lower() + order = getattr(config, 'spatial_order', None) + + if scheme == "weno": + if order is None: + raise ValueError("使用 'weno' 时必须设置 config.spatial_order") + scheme = f"weno{order}" + + # 使用BaseFactory,但处理特殊参数 + from factories.base_factory import BaseFactory + + try: + if scheme == "eno": + if order is None: + order = 3 + # ENO需要特殊参数 + return ComponentRegistry.create('reconstructor', scheme, order, domain.ntcells) + elif scheme == "weno3": + # WENO3无参数 + return BaseFactory.create_component('reconstructor', scheme) + else: + # 其他情况 + return BaseFactory.create_component('reconstructor', scheme, config) + except ValueError as e: + # 简单的回退逻辑 + if scheme == "eno": + if order is None: + order = 3 + from .eno import EnoReconstructor + return EnoReconstructor(order, domain.ntcells) + elif scheme == "weno3": + from .weno3 import Weno3Reconstructor + return Weno3Reconstructor() + raise \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06g/reconstructor/weno3.py b/example/1d-linear-convection/weno3/python/06g/reconstructor/weno3.py new file mode 100644 index 000000000..929061c13 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06g/reconstructor/weno3.py @@ -0,0 +1,59 @@ +# reconstructor/weno3.py +import numpy as np +from .base import Reconstructor +from cfd_registry import ComponentRegistry, register_component + +@register_component('reconstructor', 'weno3') +class Weno3Reconstructor(Reconstructor): + def compute_face_values(self, q, cfd): + domain = cfd.domain + solution = cfd.solution + self._reconstruct_left_interfaces(domain, q, solution.q_face_left) + self._reconstruct_right_interfaces(domain, q, solution.q_face_right) + + def _reconstruct_left_interfaces(self, domain, u, qL): + """在每个 i+1/2 界面,计算左单元贡献的 qL (即 u_{i+1/2}^-)""" + for i in range(domain.ist - 1, domain.ied): + j = i - (domain.ist - 1) + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qL[j] = self._reconstruct_from_right_biased_stencil(v3, v2, v1) + qL[j] = self._reconstruct_from_left_biased_stencil(v1, v2, v3) + + def _reconstruct_right_interfaces(self, domain, u, qR): + """在每个 i+1/2 界面,计算右单元贡献的 qR (即 u_{i+1/2}^+)""" + for i in range(domain.ist, domain.ied + 1): + j = i - domain.ist + v1, v2, v3 = u[i-1], u[i], u[i+1] + #qR[j] = self._reconstruct_from_left_biased_stencil(v3, v2, v1) + qR[j] = self._reconstruct_from_right_biased_stencil(v1, v2, v3) + + def _reconstruct_from_left_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + + def _reconstruct_from_right_biased_stencil(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + diff --git a/example/1d-linear-convection/weno3/python/06g/registry.py b/example/1d-linear-convection/weno3/python/06g/registry.py new file mode 100644 index 000000000..61be90500 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06g/registry.py @@ -0,0 +1,75 @@ +# registry.py (改进版) +""" +CFD组件注册系统 - 简洁高效版 +""" + +from typing import Dict, Type, Any + +class ComponentRegistry: + """组件注册表""" + + _registries: Dict[str, Dict[str, Type]] = {} + _verbose = True # 控制是否打印注册信息 + + @classmethod + def set_verbose(cls, verbose: bool): + """设置是否打印注册信息""" + cls._verbose = verbose + + @classmethod + def register(cls, category: str, name: str, component_class: Type): + """注册组件类""" + if category not in cls._registries: + cls._registries[category] = {} + + # 检查是否已注册 + if name in cls._registries[category]: + if cls._registries[category][name] == component_class: + # 相同类重复注册,静默跳过 + return + elif cls._verbose: + print(f"⚠️ 覆盖注册: {category}.{name}") + + cls._registries[category][name] = component_class + if cls._verbose: + print(f"✅ 已注册: {category}.{name} -> {component_class.__name__}") + + @classmethod + def get(cls, category: str, name: str) -> Type: + """获取组件类""" + if category not in cls._registries: + available = list(cls._registries.keys()) + raise ValueError(f"❌ 未知类别: {category} (可用类别: {available})") + + if name not in cls._registries[category]: + available = list(cls._registries[category].keys()) + raise ValueError(f"❌ 未找到: {category}.{name} (可用: {available})") + + return cls._registries[category][name] + + @classmethod + def create(cls, category: str, name: str, *args, **kwargs) -> Any: + """创建组件实例""" + component_class = cls.get(category, name) + return component_class(*args, **kwargs) + + @classmethod + def list_all(cls) -> Dict[str, list]: + """列出所有已注册的组件""" + return { + category: list(components.keys()) + for category, components in cls._registries.items() + } + + @classmethod + def get_count(cls) -> int: + """获取注册组件总数""" + return sum(len(components) for components in cls._registries.values()) + +def register_component(category: str, name: str = None): + """简化注册的装饰器""" + def decorator(cls): + component_name = name or cls.__name__.lower() + ComponentRegistry.register(category, component_name, cls) + return cls + return decorator \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06g/residual.py b/example/1d-linear-convection/weno3/python/06g/residual.py new file mode 100644 index 000000000..e0f96cd3c --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06g/residual.py @@ -0,0 +1,40 @@ +# residual.py + +from flux.factory import FluxCalculatorFactory +from reconstructor import ReconstructorFactory + +class ResidualCalculator: + """残差计算器:封装「重建→通量→散度」完整流程""" + def __init__(self, cfd): + self.cfd = cfd + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.mesh = self.domain.mesh + + self.reconstructor = ReconstructorFactory.create(self.config, self.domain) + self.flux_calculator = FluxCalculatorFactory.create(cfd) + + def compute(self): + """计算完整残差(对外唯一接口)""" + self._compute_face_values() + self._compute_inviscid_flux() + self._compute_flux_divergence() + + def _compute_face_values(self): + """私有方法:界面值重建""" + self.reconstructor.compute_face_values(self.solution.u, self.cfd) + + def _compute_inviscid_flux(self): + """私有方法:计算无粘通量""" + self.flux_calculator.compute( + self.solution.q_face_left, + self.solution.q_face_right, + self.solution.flux + ) + + def _compute_flux_divergence(self): + """私有方法:计算通量散度(残差 = -dF/dx)""" + solution = self.solution + for i in range(self.mesh.ncells): + solution.res[i] = -(solution.flux[i+1] - solution.flux[i]) / self.mesh.dx \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06g/result.py b/example/1d-linear-convection/weno3/python/06g/result.py new file mode 100644 index 000000000..acd257b0d --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06g/result.py @@ -0,0 +1,21 @@ +# result.py + +from initial_condition import InitialConditionFactory + +class ResultAssembler: + @staticmethod + def assemble(cfd: 'Cfd') -> dict: + u_numerical = cfd.solution.u[cfd.domain.ist:cfd.domain.ied].copy() + analytical = InitialConditionFactory.get_exact_solution(cfd) + + return { + "x": cfd.domain.mesh.xcc, + "numerical": u_numerical, + "analytical": analytical, + "config": { + "scheme": cfd.config.recon_scheme, + "order": cfd.config.spatial_order, + "rk_order": cfd.config.rk_order, + "final_time": cfd.config.final_time + } + } \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06g/run_eno_weno.py b/example/1d-linear-convection/weno3/python/06g/run_eno_weno.py new file mode 100644 index 000000000..ff46f2267 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06g/run_eno_weno.py @@ -0,0 +1,54 @@ +# run_eno_weno.py + +from solver import Cfd +from config import CfdConfig +from mesh import Mesh +from plotter import plot_eno_weno_comparison, CFDPlotter + +def performEnoWenoAnalysis(): + # 1. 初始化网格 + #mesh = Mesh(ncells=100, L=2.0) + mesh = Mesh() + plotter = CFDPlotter() + + # 2. 配置并运行ENO3求解(使用你的链式调用) + print("Running ENO3 solver...") + config_eno3 = CfdConfig() # 初始化默认配置 + config_eno3.with_reconstruction("eno", 3) # 显式指定3阶(也可省略,ENO默认3阶) + # 可选:覆盖默认值(如dt) + config_eno3.dt = 0.0025 + config_eno3.rk_order = 2 + + cfd_eno3 = Cfd(config_eno3, mesh) + cfd_eno3.run() # 求解并生成result字典 + + # 可选:快速验证ENO3结果 + # plotter.plot_quick(cfd_eno3.result, title="ENO3 Quick Check") + + # 3. 配置并运行WENO3求解(注意:WENO默认5阶,这里显式指定3阶) + print("Running WENO3 solver...") + config_weno3 = CfdConfig() + config_weno3.with_reconstruction("weno", 3) # 显式指定3阶(默认是5阶) + # 可选:覆盖默认值 + config_weno3.dt = 0.0025 + config_weno3.rk_order = 2 + + cfd_weno3 = Cfd(config_weno3, mesh) + cfd_weno3.run() + + # 4. 可选:保存结果(供离线绘图) + # cfd_eno3.save_result("eno3_result.npz") + # cfd_weno3.save_result("weno3_result.npz") + + # 5. 绘制ENO/WENO对比图 + print("Plotting comparison results...") + plot_eno_weno_comparison( + eno_result=cfd_eno3.result, + weno_result=cfd_weno3.result, + save_path="eno_weno_comparison.png" # 可选:保存图片 + ) + +if __name__ == "__main__": + # 主程序入口 + performEnoWenoAnalysis() + print("Analysis completed!") \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06g/solution.py b/example/1d-linear-convection/weno3/python/06g/solution.py new file mode 100644 index 000000000..3d4cba569 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06g/solution.py @@ -0,0 +1,40 @@ +# solution.py +import numpy as np +from initial_condition import InitialConditionFactory + +class Solution: + def __init__(self, config, domain): + """ + 初始化求解过程中的动态数据 + :param domain: Domain实例(用于确定数组尺寸) + """ + self.domain = domain + mesh = domain.mesh + + # 界面值和通量(维度依赖mesh.nnodes) + self.q_face_left = np.zeros(mesh.nnodes) # 左界面值 + self.q_face_right = np.zeros(mesh.nnodes) # 右界面值 + self.flux = np.zeros(mesh.nnodes) # 通量 + + # 残差(维度依赖mesh.ncells) + self.res = np.zeros(mesh.ncells) # 残差 + + # 解数组(维度依赖ntcells,含ghost层) + self.u = np.zeros(domain.ntcells) # 当前解 + self.un = np.zeros(domain.ntcells) # 上一时间步解 + + self.initialize_from_config(config) + + def reset_solution(self): + """重置解数组为初始状态""" + self.u.fill(0.0) + self.un.fill(0.0) + + def initialize_from_config(self, config): + """根据配置初始化场""" + ic = InitialConditionFactory.create(config) + ic.apply(self) + + def update_old_field(self): + """更新旧场""" + self.un[:] = self.u[:] \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06g/solver.py b/example/1d-linear-convection/weno3/python/06g/solver.py new file mode 100644 index 000000000..6a320e26a --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06g/solver.py @@ -0,0 +1,42 @@ +from boundary import BoundaryConditionFactory +from initial_condition import InitialConditionFactory +from time_integration import TimeIntegrator,TimeIntegratorFactory +from mesh import Mesh +from domain import Domain +from solution import Solution +from config import CfdConfig +from result import ResultAssembler + +# Cfd class: main data structure containing all CFD data +class Cfd: + def __init__(self, config, mesh): + self.config = config + self.domain = Domain(config, mesh) + self.solution = Solution(config, self.domain) + self.integrator = TimeIntegratorFactory.create(self) + self.boundary_condition = BoundaryConditionFactory.create(self) + + def run(self): + # 应用初始边界条件并同步 old field + self.boundary_condition.apply(self.solution.u) + self.solution.update_old_field() + + t = 0.0 + dt_old = self.config.dt + dt = dt_old + + domain = self.domain + solution = self.solution + config = self.config + + while t < config.final_time: + if t + dt > config.final_time: + dt = config.final_time - t + config.dt = dt # temporary adjustment for last step + self.integrator.step(dt) + t += dt + config.dt = dt_old + + self.result = ResultAssembler.assemble(self) + return self.result["numerical"] + \ No newline at end of file diff --git a/example/1d-linear-convection/weno3/python/06g/time_integration.py b/example/1d-linear-convection/weno3/python/06g/time_integration.py new file mode 100644 index 000000000..51b2b20c1 --- /dev/null +++ b/example/1d-linear-convection/weno3/python/06g/time_integration.py @@ -0,0 +1,126 @@ +# time_integration.py + +""" +时间推进器模块 (已集成注册系统) +使用装饰器 @register_component 自动注册 +""" + +from abc import ABC, abstractmethod +from cfd_registry import ComponentRegistry, register_component +from residual import ResidualCalculator + +# ---------------------- 1. 抽象时间推进器基类(统一接口) ---------------------- +class TimeIntegrator(ABC): + """时间推进器抽象基类:定义一维CFD时间推进的核心接口""" + def __init__(self, cfd): + self.cfd = cfd # 持有CFD实例,获取配置/域/求解数据 + self.config = cfd.config + self.domain = cfd.domain + self.solution = cfd.solution + self.residual_calculator = ResidualCalculator(cfd) + + @abstractmethod + def step(self, dt): + """ + 单次时间步推进(核心接口) + :param dt: 时间步长 + :return: None + """ + pass + + # 公共逻辑:复用残差计算、边界条件、数组索引映射 + def compute_residual(self): + """计算残差(所有RK方法都需要,封装为公共方法)""" + self.residual_calculator.compute() + + def apply_boundary(self): + """应用边界条件(公共逻辑)""" + self.cfd.boundary_condition.apply(self.solution.u) + + def map_idx(self, i): + """物理网格索引 → 残差数组索引(公共映射逻辑)""" + return i - self.domain.ist + +# ---------------------- 2. 具体RK时间推进器实现(使用装饰器注册) ---------------------- + +@register_component('integrator', 'rk1') +class RK1Integrator(TimeIntegrator): + """1阶显式欧拉(RK1)""" + def step(self, dt): + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] += dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk2') +class RK2Integrator(TimeIntegrator): + """2阶Heun方法(RK2)""" + def step(self, dt): + # 阶段1:预测步 + self.compute_residual() + u_pred = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u_pred[i] += dt * self.solution.res[j] + self.solution.u[:] = u_pred + self.apply_boundary() + + # 阶段2:校正步 + self.compute_residual() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = 0.5 * self.solution.un[i] + 0.5 * self.solution.u[i] + 0.5 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +@register_component('integrator', 'rk3') +class RK3Integrator(TimeIntegrator): + """3阶SSPRK3(强稳定保号RK3)""" + def step(self, dt): + # 阶段1 + self.compute_residual() + u1 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u1[i] += dt * self.solution.res[j] + self.solution.u[:] = u1 + self.apply_boundary() + + # 阶段2 + self.compute_residual() + u2 = self.solution.u.copy() + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + u2[i] = 0.75 * self.solution.un[i] + 0.25 * self.solution.u[i] + 0.25 * dt * self.solution.res[j] + self.solution.u[:] = u2 + self.apply_boundary() + + # 阶段3 + self.compute_residual() + c1, c2, c3 = 1.0/3.0, 2.0/3.0, 2.0/3.0 + for i in range(self.domain.ist, self.domain.ied): + j = self.map_idx(i) + self.solution.u[i] = c1 * self.solution.un[i] + c2 * self.solution.u[i] + c3 * dt * self.solution.res[j] + self.apply_boundary() + self.solution.update_old_field() + +class TimeIntegratorFactory: + """时间推进器工厂""" + + @staticmethod + def create(cfd) -> 'TimeIntegrator': + """ + 创建时间推进器实例 + + Args: + cfd: CFD对象 + + Returns: + 时间推进器实例 + """ + from factories.base_factory import BaseFactory + rk_order = cfd.config.rk_order + integrator_name = f'rk{rk_order}' + return BaseFactory.create_component('integrator', integrator_name, cfd) \ No newline at end of file diff --git a/example/codegen/weno_reconstructor/01/eno_weno_coefficients.yaml b/example/codegen/weno_reconstructor/01/eno_weno_coefficients.yaml new file mode 100644 index 000000000..10219b2a8 --- /dev/null +++ b/example/codegen/weno_reconstructor/01/eno_weno_coefficients.yaml @@ -0,0 +1,75 @@ +# ENO reconstruction coefficients + +eno: + 1: + - [ 1] # r = 0 + - [ 1] # r = -1 + + 2: + - [ -1/2, 3/2] # r = 1 + - [ 1/2, 1/2] # r = 0 + - [ 3/2, -1/2] # r = -1 + + 3: + - [ 1/3, -7/6, 11/6] # r = 2 + - [ -1/6, 5/6, 1/3] # r = 1 + - [ 1/3, 5/6, -1/6] # r = 0 + - [ 11/6, -7/6, 1/3] # r = -1 + + 4: + - [ -1/4, 13/12, -23/12, 25/12] # r = 3 + - [ 1/12, -5/12, 13/12, 1/4] # r = 2 + - [ -1/12, 7/12, 7/12, -1/12] # r = 1 + - [ 1/4, 13/12, -5/12, 1/12] # r = 0 + - [ 25/12, -23/12, 13/12, -1/4] # r = -1 + + 5: + - [ 1/5, -21/20, 137/60, -163/60, 137/60] # r = 4 + - [ -1/20, 17/60, -43/60, 77/60, 1/5] # r = 3 + - [ 1/30, -13/60, 47/60, 9/20, -1/20] # r = 2 + - [ -1/20, 9/20, 47/60, -13/60, 1/30] # r = 1 + - [ 1/5, 77/60, -43/60, 17/60, -1/20] # r = 0 + - [ 137/60, -163/60, 137/60, -21/20, 1/5] # r = -1 + + 6: + - [ -1/6, 31/30, -163/60, 79/20, -71/20, 49/20] # r = 5 + - [ 1/30, -13/60, 37/60, -21/20, 29/20, 1/6] # r = 4 + - [ -1/60, 7/60, -23/60, 19/20, 11/30, -1/30] # r = 3 + - [ 1/60, -2/15, 37/60, 37/60, -2/15, 1/60] # r = 2 + - [ -1/30, 11/30, 19/20, -23/60, 7/60, -1/60] # r = 1 + - [ 1/6, 29/20, -21/20, 37/60, -13/60, 1/30] # r = 0 + - [ 49/20, -71/20, 79/20, -163/60, 31/30, -1/6] # r = -1 + + 7: + - [ 1/7, -43/42, 667/210, -2341/420, 853/140, -617/140, 363/140] # r = 6 + - [ -1/42, 37/210, -241/420, 153/140, -197/140, 223/140, 1/7] # r = 5 + - [ 1/105, -31/420, 109/420, -241/420, 153/140, 13/42, -1/42] # r = 4 + - [ -1/140, 5/84, -101/420, 319/420, 107/210, -19/210, 1/105] # r = 3 + - [ 1/105, -19/210, 107/210, 319/420, -101/420, 5/84, -1/140] # r = 2 + - [ -1/42, 13/42, 153/140, -241/420, 109/420, -31/420, 1/105] # r = 1 + - [ 1/7, 223/140, -197/140, 153/140, -241/420, 37/210, -1/42] # r = 0 + - [ 363/140, -617/140, 853/140, -2341/420, 667/210, -43/42, 1/7] # r = -1 + +weno_schemes: + 1: # 1st-order WENO (trivial) + inputs: [v1] # v1=u_i + betas: + beta0: "0" # r=1 + weights: + d: [1] + 2: # 3rd-order WENO (k=2 → 2k-1=3) + inputs: [v1, v2, v3] # v1=u_im1, v2=u_i, v3=u_ip1 + betas: + beta0: "(v2 - v1)**2" # r=1 + beta1: "(v3 - v2)**2" + weights: + d: [1/3, 2/3] + 3: # 5th-order WENO (k=3 → 2k-1=5) + inputs: [v1, v2, v3, v4, v5] # v1=u_im2, v2=u_im1, v3=u_i, v4=u_ip1, v5=u_ip2 + betas: + beta0: "(13.0/12.0)*(v1 - 2*v2 + v3)**2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)**2" # r=2 + beta1: "(13.0/12.0)*(v2 - 2*v3 + v4)**2 + (1.0/4.0)*(v2 - v4)**2" # r=1 + beta2: "(13.0/12.0)*(v3 - 2*v4 + v5)**2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)**2" # r=0 + weights: + d: [1/10, 3/5, 3/10] + diff --git a/example/codegen/weno_reconstructor/01/generate_weno_coef.py b/example/codegen/weno_reconstructor/01/generate_weno_coef.py new file mode 100644 index 000000000..452d4f7ed --- /dev/null +++ b/example/codegen/weno_reconstructor/01/generate_weno_coef.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import yaml +from pathlib import Path +import sys + +def fraction_str_to_code(frac) -> str: + """Convert '3/2' or 1 to '3.0/2.0' or '1.0'.""" + if isinstance(frac, str): + if '/' in frac: + a, b = frac.split('/') + return f"{float(a):g}.0/{float(b):g}.0" + else: + return f"{float(frac):g}.0" + else: + # Handle int/float from YAML + return f"{float(frac):g}.0" + +def build_equation_str(idx, coeffs, fraction_converter): + """ + 构建格式正确的方程字符串(避免多余的+号) + + 参数: + idx: 当前索引(用于计算变量名v的编号) + coeffs: 当前索引对应的系数列表(字符串形式) + fraction_converter: 分数字符串转代码格式的函数 + + 返回: + 格式化后的方程右侧字符串(如 "1/2*v1-1/3*v2+2/5*v3") + """ + # 转换系数格式 + code_coeffs = [fraction_converter(c) for c in coeffs] + + # 生成带正确符号的项 + terms = [] + for i in range(len(code_coeffs)): + coeff = code_coeffs[i] + var_name = f"v{idx + i + 1}" # 计算变量名(v1/v2/v3...) + + if coeff.startswith('-'): + # 负数项:直接拼接,保留负号 + terms.append(f"{coeff}*{var_name}") + else: + # 正数项:添加+号(第一项后续处理) + terms.append(f"+{coeff}*{var_name}") + + # 处理第一项的多余+号 + if terms and terms[0].startswith('+'): + terms[0] = terms[0][1:] + + # 拼接最终表达式 + return "".join(terms) + +def generate_weno_function_code(func_name: str, yaml_path: str, weno_order: int, lr: int) -> list: + """Generate Python function code from YAML coefficient file.""" + with open(yaml_path, 'r', encoding='utf-8') as f: + data = yaml.safe_load(f) + + if not data: + raise ValueError(f"YAML file {yaml_path} is empty") + + + eno_order = weno_order // 2 + 1 + print(f"weno_order,eno_order={weno_order,eno_order}") + + k = eno_order # spatial order = k + + scheme = data["weno_schemes"][k] + print(f"scheme={scheme}") + + inputs = scheme["inputs"] + input_str = ", ".join(inputs) + + lines = [] + lines.append(f"def {func_name}(self, {input_str}):") + + is_reversed = False + if not is_reversed: + # r = k-1, k-2, ..., 0, -1 + r_vals = list(range(k - 1, -2, -1)) + else: + # r = -1, 0, 1, ..., k-1 + r_vals = list(range(-1, k)) + + + print(f"r_vals={r_vals}") + lines.append(f" eps = 1e-6") + + + # Compute betas + betas = scheme["betas"] + for name, expr in betas.items(): + lines.append(f" {name} = {expr}") + + # Linear weights + weights = scheme["weights"]["d"] + weights_iter = reversed(weights) if lr == 1 else weights + + for i, d in enumerate(weights_iter): + d_code = fraction_str_to_code(d) + lines.append(f" d{i} = {d_code}") + + for idx in range( eno_order ): + lines.append(f" alpha{idx} = d{idx} / (eps + beta{idx})**2") + + alpha = " + ".join(f"alpha{i}" for i in range(eno_order) ) + + lines.append(f" alpha = {alpha}") + for idx in range( eno_order ): + lines.append(f" w{idx} = alpha{idx} / alpha") + + # 获取 ENO 系数 + eno_data = data["eno"] # ENO + coeffs_list = eno_data[eno_order] + + ishift = 1 if lr == 1 else 0 + + for idx in range( eno_order ): + coeffs = coeffs_list[idx+ishift] + + code_coeffs = [fraction_str_to_code(c) for c in coeffs] + #print(f"idx,coeffs={idx,coeffs}") + + result = "+".join(f"{code_coeffs[i]}*v{idx+i+1}" for i in range(len(coeffs))) + + equation_right = build_equation_str(idx, coeffs, fraction_str_to_code) + #print(f"q{idx} = {result}") + print(f"q{idx} = {equation_right}") + r = r_vals[idx] + + lines.append(f" q{idx} = {equation_right} # r={r}") + + f = " + ".join(f"w{i} * q{i}" for i in range(eno_order) ) + lines.append(f" return {f}") + + return lines + +def main(): + input_yaml = "eno_weno_coefficients.yaml" + output_file = "weno_reconstructors.py" + + weno_order = 3 + + # Write combined output + full_lines = [ + "# Auto-generated WENO coefficient initializers", + "# DO NOT EDIT MANUALLY", + "" + ] + + for weno_order in [1,3,5]: + left_lines = generate_weno_function_code( + f"_reconstruct_weno{weno_order}_left", + input_yaml, + weno_order=weno_order, + lr=-1 + ) + + right_lines = generate_weno_function_code( + f"_reconstruct_weno{weno_order}_right", + input_yaml, + weno_order=weno_order, + lr=1 + ) + + full_lines.extend(left_lines) + full_lines.append("") + full_lines.extend(right_lines) + full_lines.append("") + + with open(output_file, 'w', encoding='utf-8') as f: + f.write("\n".join(full_lines)) + + print(f"Generated {output_file} from:") + print(f" - {input_yaml}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/example/codegen/weno_reconstructor/01/weno_reconstructors.py b/example/codegen/weno_reconstructor/01/weno_reconstructors.py new file mode 100644 index 000000000..15a9972e0 --- /dev/null +++ b/example/codegen/weno_reconstructor/01/weno_reconstructors.py @@ -0,0 +1,92 @@ +# Auto-generated WENO coefficient initializers +# DO NOT EDIT MANUALLY + +def _reconstruct_weno1_left(self, v1): + eps = 1e-6 + beta0 = 0 + d0 = 1.0 + alpha0 = d0 / (eps + beta0)**2 + alpha = alpha0 + w0 = alpha0 / alpha + q0 = 1.0*v1 # r=0 + return w0 * q0 + +def _reconstruct_weno1_right(self, v1): + eps = 1e-6 + beta0 = 0 + d0 = 1.0 + alpha0 = d0 / (eps + beta0)**2 + alpha = alpha0 + w0 = alpha0 / alpha + q0 = 1.0*v1 # r=0 + return w0 * q0 + +def _reconstruct_weno3_left(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 1.0/3.0 + d1 = 2.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = -1.0/2.0*v1+3.0/2.0*v2 # r=1 + q1 = 1.0/2.0*v2+1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + +def _reconstruct_weno3_right(self, v1, v2, v3): + eps = 1e-6 + beta0 = (v2 - v1)**2 + beta1 = (v3 - v2)**2 + d0 = 2.0/3.0 + d1 = 1.0/3.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha = alpha0 + alpha1 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + q0 = 1.0/2.0*v1+1.0/2.0*v2 # r=1 + q1 = 3.0/2.0*v2-1.0/2.0*v3 # r=0 + return w0 * q0 + w1 * q1 + +def _reconstruct_weno5_left(self, v1, v2, v3, v4, v5): + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)**2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)**2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)**2 + (1.0/4.0)*(v2 - v4)**2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)**2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)**2 + d0 = 1.0/10.0 + d1 = 3.0/5.0 + d2 = 3.0/10.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha2 = d2 / (eps + beta2)**2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = 1.0/3.0*v1-7.0/6.0*v2+11.0/6.0*v3 # r=2 + q1 = -1.0/6.0*v2+5.0/6.0*v3+1.0/3.0*v4 # r=1 + q2 = 1.0/3.0*v3+5.0/6.0*v4-1.0/6.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 + +def _reconstruct_weno5_right(self, v1, v2, v3, v4, v5): + eps = 1e-6 + beta0 = (13.0/12.0)*(v1 - 2*v2 + v3)**2 + (1.0/4.0)*(v1 - 4*v2 + 3*v3)**2 + beta1 = (13.0/12.0)*(v2 - 2*v3 + v4)**2 + (1.0/4.0)*(v2 - v4)**2 + beta2 = (13.0/12.0)*(v3 - 2*v4 + v5)**2 + (1.0/4.0)*(3*v3 - 4*v4 + v5)**2 + d0 = 3.0/10.0 + d1 = 3.0/5.0 + d2 = 1.0/10.0 + alpha0 = d0 / (eps + beta0)**2 + alpha1 = d1 / (eps + beta1)**2 + alpha2 = d2 / (eps + beta2)**2 + alpha = alpha0 + alpha1 + alpha2 + w0 = alpha0 / alpha + w1 = alpha1 / alpha + w2 = alpha2 / alpha + q0 = -1.0/6.0*v1+5.0/6.0*v2+1.0/3.0*v3 # r=2 + q1 = 1.0/3.0*v2+5.0/6.0*v3-1.0/6.0*v4 # r=1 + q2 = 11.0/6.0*v3-7.0/6.0*v4+1.0/3.0*v5 # r=0 + return w0 * q0 + w1 * q1 + w2 * q2 diff --git a/modern-cfd/src/cgns/include/Cgns_t.h b/modern-cfd/src/cgns/include/Cgns_t.h index 329bc985e..e77cf3c0f 100644 --- a/modern-cfd/src/cgns/include/Cgns_t.h +++ b/modern-cfd/src/cgns/include/Cgns_t.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/src/cgns/include/cgnstest.h b/modern-cfd/src/cgns/include/cgnstest.h index 6e793a1a0..b7bcf9d50 100644 --- a/modern-cfd/src/cgns/include/cgnstest.h +++ b/modern-cfd/src/cgns/include/cgnstest.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/src/cgns/src/Cgns_t.cpp b/modern-cfd/src/cgns/src/Cgns_t.cpp index fd02fdeb7..4d7596c73 100644 --- a/modern-cfd/src/cgns/src/Cgns_t.cpp +++ b/modern-cfd/src/cgns/src/Cgns_t.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/src/cgns/src/cgnstest.cpp b/modern-cfd/src/cgns/src/cgnstest.cpp index 3c0c1f6a2..172540901 100644 --- a/modern-cfd/src/cgns/src/cgnstest.cpp +++ b/modern-cfd/src/cgns/src/cgnstest.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/src/geometry/include/Geom.h b/modern-cfd/src/geometry/include/Geom.h index e3ee2422a..6439008ad 100644 --- a/modern-cfd/src/geometry/include/Geom.h +++ b/modern-cfd/src/geometry/include/Geom.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/src/geometry/include/Grid.h b/modern-cfd/src/geometry/include/Grid.h index e3311da7a..4549920ab 100644 --- a/modern-cfd/src/geometry/include/Grid.h +++ b/modern-cfd/src/geometry/include/Grid.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/src/geometry/src/Geom.cpp b/modern-cfd/src/geometry/src/Geom.cpp index 316766ddc..7d07d85db 100644 --- a/modern-cfd/src/geometry/src/Geom.cpp +++ b/modern-cfd/src/geometry/src/Geom.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/src/geometry/src/Grid.cpp b/modern-cfd/src/geometry/src/Grid.cpp index e4fafad26..0e78f0f17 100644 --- a/modern-cfd/src/geometry/src/Grid.cpp +++ b/modern-cfd/src/geometry/src/Grid.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/src/main/include/Simu.h b/modern-cfd/src/main/include/Simu.h index 490ccfabc..134af7bd5 100644 --- a/modern-cfd/src/main/include/Simu.h +++ b/modern-cfd/src/main/include/Simu.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/src/main/src/Simu.cpp b/modern-cfd/src/main/src/Simu.cpp index c7e912505..aac41900e 100644 --- a/modern-cfd/src/main/src/Simu.cpp +++ b/modern-cfd/src/main/src/Simu.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/src/main/src/main.cpp b/modern-cfd/src/main/src/main.cpp index 504554e42..61179b957 100644 --- a/modern-cfd/src/main/src/main.cpp +++ b/modern-cfd/src/main/src/main.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/src/parallel/include/Cmpi.h b/modern-cfd/src/parallel/include/Cmpi.h index 642b72694..48eb9aff4 100644 --- a/modern-cfd/src/parallel/include/Cmpi.h +++ b/modern-cfd/src/parallel/include/Cmpi.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/src/parallel/src/Cmpi.cpp b/modern-cfd/src/parallel/src/Cmpi.cpp index 6772b2ad3..efda4d1dd 100644 --- a/modern-cfd/src/parallel/src/Cmpi.cpp +++ b/modern-cfd/src/parallel/src/Cmpi.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/src/project/include/Project.h b/modern-cfd/src/project/include/Project.h index 918ea936b..82d5a6b81 100644 --- a/modern-cfd/src/project/include/Project.h +++ b/modern-cfd/src/project/include/Project.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/src/project/src/Project.cpp b/modern-cfd/src/project/src/Project.cpp index 16606d341..ceea839cc 100644 --- a/modern-cfd/src/project/src/Project.cpp +++ b/modern-cfd/src/project/src/Project.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/src/solver/include/CfdPara.h b/modern-cfd/src/solver/include/CfdPara.h index 0a4eb16d4..6ba06a87b 100644 --- a/modern-cfd/src/solver/include/CfdPara.h +++ b/modern-cfd/src/solver/include/CfdPara.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/src/solver/include/Solver.h b/modern-cfd/src/solver/include/Solver.h index f554c2bcd..a4582f210 100644 --- a/modern-cfd/src/solver/include/Solver.h +++ b/modern-cfd/src/solver/include/Solver.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/src/solver/include/SolverDetail.h b/modern-cfd/src/solver/include/SolverDetail.h index 55e478044..b524173ea 100644 --- a/modern-cfd/src/solver/include/SolverDetail.h +++ b/modern-cfd/src/solver/include/SolverDetail.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/src/solver/include/SolverDetailCpu.h b/modern-cfd/src/solver/include/SolverDetailCpu.h index d983d4f28..433e81ae7 100644 --- a/modern-cfd/src/solver/include/SolverDetailCpu.h +++ b/modern-cfd/src/solver/include/SolverDetailCpu.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/src/solver/include/SolverDetailCuda.h b/modern-cfd/src/solver/include/SolverDetailCuda.h index ec4e98871..f17c319bc 100644 --- a/modern-cfd/src/solver/include/SolverDetailCuda.h +++ b/modern-cfd/src/solver/include/SolverDetailCuda.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/src/solver/src/CfdPara.cpp b/modern-cfd/src/solver/src/CfdPara.cpp index c34621602..bc19dbd67 100644 --- a/modern-cfd/src/solver/src/CfdPara.cpp +++ b/modern-cfd/src/solver/src/CfdPara.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/src/solver/src/Solver.cpp b/modern-cfd/src/solver/src/Solver.cpp index 32412bb6c..e61501135 100644 --- a/modern-cfd/src/solver/src/Solver.cpp +++ b/modern-cfd/src/solver/src/Solver.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/src/solver/src/SolverDetail.cpp b/modern-cfd/src/solver/src/SolverDetail.cpp index fccf4bf43..a11073270 100644 --- a/modern-cfd/src/solver/src/SolverDetail.cpp +++ b/modern-cfd/src/solver/src/SolverDetail.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/src/solver/src/SolverDetailCpu.cpp b/modern-cfd/src/solver/src/SolverDetailCpu.cpp index 8b8e5984b..be5e522b3 100644 --- a/modern-cfd/src/solver/src/SolverDetailCpu.cpp +++ b/modern-cfd/src/solver/src/SolverDetailCpu.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/src/tools/include/tools.h b/modern-cfd/src/tools/include/tools.h index 83e15c34b..8e03adcdd 100644 --- a/modern-cfd/src/tools/include/tools.h +++ b/modern-cfd/src/tools/include/tools.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/src/visualize/include/Visual.h b/modern-cfd/src/visualize/include/Visual.h index 2a0c8d9a5..bfc9adff4 100644 --- a/modern-cfd/src/visualize/include/Visual.h +++ b/modern-cfd/src/visualize/include/Visual.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/src/visualize/src/Visual.cpp b/modern-cfd/src/visualize/src/Visual.cpp index 7dab32cd3..cd47f04ef 100644 --- a/modern-cfd/src/visualize/src/Visual.cpp +++ b/modern-cfd/src/visualize/src/Visual.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/ui/MyDataBase.cpp b/modern-cfd/ui/MyDataBase.cpp index 4b47a2812..cc23dbce2 100644 --- a/modern-cfd/ui/MyDataBase.cpp +++ b/modern-cfd/ui/MyDataBase.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/ui/MyDataBase.h b/modern-cfd/ui/MyDataBase.h index 156f92f3c..1e1160bbd 100644 --- a/modern-cfd/ui/MyDataBase.h +++ b/modern-cfd/ui/MyDataBase.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/ui/main.cpp b/modern-cfd/ui/main.cpp index 7f68aab81..c5f553d94 100644 --- a/modern-cfd/ui/main.cpp +++ b/modern-cfd/ui/main.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. diff --git a/modern-cfd/ui/mainwindow.cpp b/modern-cfd/ui/mainwindow.cpp index 605a2be1f..711eec1cc 100644 --- a/modern-cfd/ui/mainwindow.cpp +++ b/modern-cfd/ui/mainwindow.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------*\ OneFLOW - LargeScale Multiphysics Scientific Simulation Environment -Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. +Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW. @@ -119,7 +119,7 @@ void MainWindow::initMenu() this->treeWidget->setGeometry(QRect(100, 50, 600, 300)); this->treeWidget->setColumnCount( 1 ); QStringList labels; - labels << QString::fromLocal8Bit("CFD"); + labels << QString::fromLocal8Bit("CFD参数"); this->treeWidget->setHeaderLabels( labels ); this->treeWidget->header()->setSectionResizeMode(QHeaderView::Stretch); this->treeWidget->setContextMenuPolicy(Qt::CustomContextMenu); diff --git a/test/test.py b/test/test.py index 2b1df9e53..382f30e28 100644 --- a/test/test.py +++ b/test/test.py @@ -2,7 +2,7 @@ ''' --------------------------------------------------------------------------- OneFLOW - LargeScale Multiphysics Scientific Simulation Environment - Copyright (C) 2017-2025 He Xin and the OneFLOW contributors. + Copyright (C) 2017-2026 He Xin and the OneFLOW contributors. ------------------------------------------------------------------------------- License This file is part of OneFLOW.